Compare commits

...

26 Commits

Author SHA1 Message Date
Andras Bacsai
767fd334dd Merge pull request #1310 from coollabsio/next
v4.0.0-beta.80
2023-10-12 09:47:39 +02:00
Andras Bacsai
972223f01b disable docker_compose deployments 2023-10-12 09:30:27 +02:00
Andras Bacsai
9318cac189 fix: service status check
fix: containerStatusJob
fix: service form
2023-10-12 09:12:46 +02:00
Andras Bacsai
7aa991fd7c fix: service check status 10 sec 2023-10-12 08:58:08 +02:00
Andras Bacsai
5c27f43b3d move autoupdate job to actions 2023-10-12 08:56:29 +02:00
Andras Bacsai
a2f4d4ed6d fix: make sure proxy wont start in NONE mode 2023-10-12 08:51:32 +02:00
Andras Bacsai
6aca2740fb fix 2023-10-11 15:46:59 +02:00
Andras Bacsai
cd13b5b83e version++ 2023-10-11 15:39:27 +02:00
Andras Bacsai
758dbafbf1 Merge pull request #1308 from coollabsio/next
v4.0.0-beta.79
2023-10-11 15:27:21 +02:00
Andras Bacsai
f6663661df disallow robots 2023-10-11 15:07:00 +02:00
Andras Bacsai
9666099408 commit 2023-10-11 14:43:34 +02:00
Andras Bacsai
d382af6860 fix 2023-10-11 14:40:41 +02:00
Andras Bacsai
4905454269 hm 2023-10-11 14:33:18 +02:00
Andras Bacsai
ed8bd37230 disallow robots 2023-10-11 14:31:59 +02:00
Andras Bacsai
ec1a7aa893 Merge pull request #1307 from coollabsio/next
v4.0.0-beta.78
2023-10-11 14:25:27 +02:00
Andras Bacsai
62adf2c5dc fix: boarding + verification 2023-10-11 14:24:19 +02:00
Andras Bacsai
3e4538de98 update command 2023-10-11 14:12:02 +02:00
Andras Bacsai
5a7b16ea5f command: delete server 2023-10-11 14:04:21 +02:00
Andras Bacsai
aa7bc40f85 fix: send unreachable/revived notifications 2023-10-11 13:52:46 +02:00
Andras Bacsai
4fd83dc727 version++ 2023-10-11 13:51:30 +02:00
Andras Bacsai
0e451f87a9 Merge pull request #1305 from coollabsio/next
v4.0.0-beta.77
2023-10-11 13:50:56 +02:00
Andras Bacsai
0b0ae55f0b fix 2023-10-11 13:47:14 +02:00
Andras Bacsai
40ec3d9753 fix 2023-10-11 13:34:51 +02:00
Andras Bacsai
9535c8df29 fix: check localhost connection 2023-10-11 13:30:36 +02:00
Andras Bacsai
6ca1d36d5d fix: cannot remove localhost 2023-10-11 13:12:29 +02:00
Andras Bacsai
f5d16c46cb version++ 2023-10-11 13:11:52 +02:00
30 changed files with 233 additions and 157 deletions

View File

@@ -2,6 +2,7 @@
namespace App\Actions\Proxy; namespace App\Actions\Proxy;
use App\Enums\ProxyTypes;
use App\Models\Server; use App\Models\Server;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Lorisleiva\Actions\Concerns\AsAction; use Lorisleiva\Actions\Concerns\AsAction;
@@ -14,7 +15,7 @@ class StartProxy
{ {
$commands = collect([]); $commands = collect([]);
$proxyType = $server->proxyType(); $proxyType = $server->proxyType();
if ($proxyType === 'none') { if ($proxyType === ProxyTypes::NONE->value) {
return 'OK'; return 'OK';
} }
$proxy_path = get_proxy_path(); $proxy_path = get_proxy_path();

View File

@@ -2,16 +2,18 @@
namespace App\Actions\Server; namespace App\Actions\Server;
use Lorisleiva\Actions\Concerns\AsAction;
use App\Models\InstanceSettings; use App\Models\InstanceSettings;
use App\Models\Server; use App\Models\Server;
class UpdateCoolify class UpdateCoolify
{ {
use AsAction;
public ?Server $server = null; public ?Server $server = null;
public ?string $latestVersion = null; public ?string $latestVersion = null;
public ?string $currentVersion = null; public ?string $currentVersion = null;
public function __invoke(bool $force) public function handle(bool $force)
{ {
try { try {
$settings = InstanceSettings::get(); $settings = InstanceSettings::get();

View File

@@ -3,6 +3,7 @@
namespace App\Console\Commands; namespace App\Console\Commands;
use App\Models\Application; use App\Models\Application;
use App\Models\Server;
use App\Models\Service; use App\Models\Service;
use App\Models\StandalonePostgresql; use App\Models\StandalonePostgresql;
use Illuminate\Console\Command; use Illuminate\Console\Command;
@@ -34,7 +35,7 @@ class ResourcesDelete extends Command
{ {
$resource = select( $resource = select(
'What resource do you want to delete?', 'What resource do you want to delete?',
['Application', 'Database', 'Service'], ['Application', 'Database', 'Service', 'Server'],
); );
if ($resource === 'Application') { if ($resource === 'Application') {
$this->deleteApplication(); $this->deleteApplication();
@@ -42,6 +43,29 @@ class ResourcesDelete extends Command
$this->deleteDatabase(); $this->deleteDatabase();
} elseif ($resource === 'Service') { } elseif ($resource === 'Service') {
$this->deleteService(); $this->deleteService();
} elseif($resource === 'Server') {
$this->deleteServer();
}
}
private function deleteServer() {
$servers = Server::all();
if ($servers->count() === 0) {
$this->error('There are no applications to delete.');
return;
}
$serversToDelete = multiselect(
'What server do you want to delete?',
$servers->pluck('id')->sort()->toArray(),
);
foreach ($serversToDelete as $id) {
$toDelete = Server::find($id);
$this->info($toDelete);
$confirmed = confirm("Are you sure you want to delete all selected resources?");
if (!$confirmed) {
break;
}
$toDelete->delete();
} }
} }
private function deleteApplication() private function deleteApplication()
@@ -53,14 +77,16 @@ class ResourcesDelete extends Command
} }
$applicationsToDelete = multiselect( $applicationsToDelete = multiselect(
'What application do you want to delete?', 'What application do you want to delete?',
$applications->pluck('name')->toArray(), $applications->pluck('name')->sort()->toArray(),
); );
$confirmed = confirm("Are you sure you want to delete all selected resources?");
if (!$confirmed) {
return;
}
foreach ($applicationsToDelete as $application) { foreach ($applicationsToDelete as $application) {
$toDelete = $applications->where('name', $application)->first(); $toDelete = $applications->where('name', $application)->first();
$this->info($toDelete);
$confirmed = confirm("Are you sure you want to delete all selected resources? ");
if (!$confirmed) {
break;
}
$toDelete->delete(); $toDelete->delete();
} }
} }
@@ -73,14 +99,16 @@ class ResourcesDelete extends Command
} }
$databasesToDelete = multiselect( $databasesToDelete = multiselect(
'What database do you want to delete?', 'What database do you want to delete?',
$databases->pluck('name')->toArray(), $databases->pluck('name')->sort()->toArray(),
); );
$confirmed = confirm("Are you sure you want to delete all selected resources?");
if (!$confirmed) {
return;
}
foreach ($databasesToDelete as $database) { foreach ($databasesToDelete as $database) {
$toDelete = $databases->where('name', $database)->first(); $toDelete = $databases->where('name', $database)->first();
$this->info($toDelete);
$confirmed = confirm("Are you sure you want to delete all selected resources?");
if (!$confirmed) {
return;
}
$toDelete->delete(); $toDelete->delete();
} }
@@ -94,14 +122,16 @@ class ResourcesDelete extends Command
} }
$servicesToDelete = multiselect( $servicesToDelete = multiselect(
'What service do you want to delete?', 'What service do you want to delete?',
$services->pluck('name')->toArray(), $services->pluck('name')->sort()->toArray(),
); );
$confirmed = confirm("Are you sure you want to delete all selected resources?");
if (!$confirmed) {
return;
}
foreach ($servicesToDelete as $service) { foreach ($servicesToDelete as $service) {
$toDelete = $services->where('name', $service)->first(); $toDelete = $services->where('name', $service)->first();
$this->info($toDelete);
$confirmed = confirm("Are you sure you want to delete all selected resources?");
if (!$confirmed) {
return;
}
$toDelete->delete(); $toDelete->delete();
} }
} }

View File

@@ -13,13 +13,7 @@ class Index extends Component
public $databases; public $databases;
public array $parameters; public array $parameters;
public array $query; public array $query;
protected $rules = [ protected $listeners = ["refreshStacks","checkStatus"];
'service.docker_compose_raw' => 'required',
'service.docker_compose' => 'required',
'service.name' => 'required',
'service.description' => 'nullable',
];
protected $listeners = ["saveCompose"];
public function render() public function render()
{ {
return view('livewire.project.service.index'); return view('livewire.project.service.index');
@@ -32,17 +26,12 @@ class Index extends Component
$this->applications = $this->service->applications->sort(); $this->applications = $this->service->applications->sort();
$this->databases = $this->service->databases->sort(); $this->databases = $this->service->databases->sort();
} }
public function saveCompose($raw)
{
$this->service->docker_compose_raw = $raw;
$this->submit();
}
public function checkStatus() public function checkStatus()
{ {
dispatch_sync(new ContainerStatusJob($this->service->server)); dispatch_sync(new ContainerStatusJob($this->service->server));
$this->refreshStack(); $this->refreshStacks();
} }
public function refreshStack() public function refreshStacks()
{ {
$this->applications = $this->service->applications->sort(); $this->applications = $this->service->applications->sort();
$this->applications->each(function ($application) { $this->applications->each(function ($application) {
@@ -53,21 +42,4 @@ class Index extends Component
$database->refresh(); $database->refresh();
}); });
} }
public function submit()
{
try {
$this->validate();
$this->service->save();
$this->service->parse();
$this->service->refresh();
$this->service->saveComposeConfigs();
$this->refreshStack();
$this->emit('refreshEnvs');
$this->emit('success', 'Service saved successfully.');
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
} }

View File

@@ -6,8 +6,8 @@ use Livewire\Component;
class Modal extends Component class Modal extends Component
{ {
public function serviceStatusUpdated() { public function checkStatus() {
$this->emit('serviceStatusUpdated'); $this->emit('checkStatus');
} }
public function render() public function render()
{ {

View File

@@ -13,20 +13,15 @@ class Navbar extends Component
public Service $service; public Service $service;
public array $parameters; public array $parameters;
public array $query; public array $query;
protected $listeners = ['serviceStatusUpdated'];
public function render() public function render()
{ {
return view('livewire.project.service.navbar'); return view('livewire.project.service.navbar');
} }
public function serviceStatusUpdated()
public function checkStatus()
{ {
$this->check_status(); $this->emit('checkStatus');
}
public function check_status()
{
dispatch_sync(new ContainerStatusJob($this->service->server));
$this->service->refresh();
} }
public function deploy() public function deploy()
{ {

View File

@@ -0,0 +1,42 @@
<?php
namespace App\Http\Livewire\Project\Service;
use Livewire\Component;
class StackForm extends Component
{
protected $listeners = ["saveCompose"];
protected $rules = [
'service.docker_compose_raw' => 'required',
'service.docker_compose' => 'required',
'service.name' => 'required',
'service.description' => 'nullable',
];
public $service;
public function saveCompose($raw)
{
$this->service->docker_compose_raw = $raw;
$this->submit();
}
public function submit()
{
try {
$this->validate();
$this->service->save();
$this->service->parse();
$this->service->refresh();
$this->service->saveComposeConfigs();
$this->emit('refreshStacks');
$this->emit('refreshEnvs');
$this->emit('success', 'Service saved successfully.');
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function render()
{
return view('livewire.project.service.stack-form');
}
}

View File

@@ -61,7 +61,18 @@ class Form extends Component
$activity = InstallDocker::run($this->server); $activity = InstallDocker::run($this->server);
$this->emit('newMonitorActivity', $activity->id); $this->emit('newMonitorActivity', $activity->id);
} }
public function checkLocalhostConnection() {
$uptime = $this->server->validateConnection();
if ($uptime) {
$this->emit('success', 'Server is reachable.');
$this->server->settings->is_reachable = true;
$this->server->settings->is_usable = true;
$this->server->settings->save();
} else {
$this->emit('error', 'Server is not reachable. Please check your connection and configuration.');
return;
}
}
public function validateServer($install = true) public function validateServer($install = true)
{ {
try { try {
@@ -69,7 +80,7 @@ class Form extends Component
if ($uptime) { if ($uptime) {
$install && $this->emit('success', 'Server is reachable.'); $install && $this->emit('success', 'Server is reachable.');
} else { } else {
$install &&$this->emit('error', 'Server is not reachable. Please check your connection and private key configuration.'); $install &&$this->emit('error', 'Server is not reachable. Please check your connection and configuration.');
return; return;
} }
$dockerInstalled = $this->server->validateDockerEngine(); $dockerInstalled = $this->server->validateDockerEngine();
@@ -117,6 +128,7 @@ class Form extends Component
$this->emit('error', 'IP address is already in use by another team.'); $this->emit('error', 'IP address is already in use by another team.');
return; return;
} }
refresh_server_connection($this->server->privateKey);
$this->server->settings->wildcard_domain = $this->wildcard_domain; $this->server->settings->wildcard_domain = $this->wildcard_domain;
$this->server->settings->cleanup_after_percentage = $this->cleanup_after_percentage; $this->server->settings->cleanup_after_percentage = $this->cleanup_after_percentage;
$this->server->settings->save(); $this->server->settings->save();

View File

@@ -37,7 +37,7 @@ class Upgrade extends Component
return; return;
} }
$this->showProgress = true; $this->showProgress = true;
resolve(UpdateCoolify::class)(true); UpdateCoolify::run(true);
$this->emit('success', "Upgrading to {$this->latestVersion} version..."); $this->emit('success', "Upgrading to {$this->latestVersion} version...");
} catch (\Throwable $e) { } catch (\Throwable $e) {
return handleError($e, $this); return handleError($e, $this);

View File

@@ -12,6 +12,9 @@ class DecideWhatToDoWithUser
public function handle(Request $request, Closure $next): Response public function handle(Request $request, Closure $next): Response
{ {
if (!auth()->user() || !isCloud() || isInstanceAdmin()) { if (!auth()->user() || !isCloud() || isInstanceAdmin()) {
if (!isCloud() && showBoarding() && !in_array($request->path(), allowedPathsForBoardingAccounts())) {
return redirect('boarding');
}
return $next($request); return $next($request);
} }
if (!auth()->user()->hasVerifiedEmail()) { if (!auth()->user()->hasVerifiedEmail()) {

View File

@@ -184,41 +184,42 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
} }
} }
private function deploy_docker_compose() // private function deploy_docker_compose()
{ // {
$dockercompose_base64 = base64_encode($this->application->dockercompose); // $dockercompose_base64 = base64_encode($this->application->dockercompose);
$this->execute_remote_command( // $this->execute_remote_command(
[ // [
"echo 'Starting deployment of {$this->application->name}.'" // "echo 'Starting deployment of {$this->application->name}.'"
], // ],
); // );
$this->prepare_builder_image(); // $this->prepare_builder_image();
$this->execute_remote_command( // $this->execute_remote_command(
[ // [
executeInDocker($this->deployment_uuid, "echo '$dockercompose_base64' | base64 -d > $this->workdir/docker-compose.yaml") // executeInDocker($this->deployment_uuid, "echo '$dockercompose_base64' | base64 -d > $this->workdir/docker-compose.yaml")
], // ],
); // );
$this->build_image_name = Str::lower("{$this->application->git_repository}:build"); // $this->build_image_name = Str::lower("{$this->application->git_repository}:build");
$this->production_image_name = Str::lower("{$this->application->uuid}:latest"); // $this->production_image_name = Str::lower("{$this->application->uuid}:latest");
$this->save_environment_variables(); // $this->save_environment_variables();
$containers = getCurrentApplicationContainerStatus($this->application->destination->server, $this->application->id); // $containers = getCurrentApplicationContainerStatus($this->application->destination->server, $this->application->id);
if ($containers->count() > 0) { // ray($containers);
foreach ($containers as $container) { // if ($containers->count() > 0) {
$containerName = data_get($container, 'Names'); // foreach ($containers as $container) {
if ($containerName) { // $containerName = data_get($container, 'Names');
instant_remote_process( // if ($containerName) {
["docker rm -f {$containerName}"], // instant_remote_process(
$this->application->destination->server // ["docker rm -f {$containerName}"],
); // $this->application->destination->server
} // );
} // }
} // }
// }
$this->execute_remote_command( // $this->execute_remote_command(
["echo -n 'Starting services (could take a while)...'"], // ["echo -n 'Starting services (could take a while)...'"],
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} up -d"), "hidden" => true], // [executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} up -d"), "hidden" => true],
); // );
} // }
private function save_environment_variables() private function save_environment_variables()
{ {
$envs = collect([]); $envs = collect([]);

View File

@@ -29,7 +29,7 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
public function __construct(public Server $server) public function __construct(public Server $server)
{ {
$this->handle();
} }
public function middleware(): array public function middleware(): array
@@ -44,7 +44,7 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
public function handle() public function handle()
{ {
try { try {
// ray("checking server status for {$this->server->name}"); ray("checking server status for {$this->server->id}");
// ray()->clearAll(); // ray()->clearAll();
$serverUptimeCheckNumber = $this->server->unreachable_count; $serverUptimeCheckNumber = $this->server->unreachable_count;
$serverUptimeCheckNumberMax = 3; $serverUptimeCheckNumberMax = 3;
@@ -53,12 +53,15 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
if ($serverUptimeCheckNumber >= $serverUptimeCheckNumberMax) { if ($serverUptimeCheckNumber >= $serverUptimeCheckNumberMax) {
if ($this->server->unreachable_email_sent === false) { if ($this->server->unreachable_email_sent === false) {
ray('Server unreachable, sending notification...'); ray('Server unreachable, sending notification...');
// $this->server->team->notify(new Unreachable($this->server)); $this->server->team->notify(new Unreachable($this->server));
$this->server->update(['unreachable_email_sent' => true]); $this->server->update(['unreachable_email_sent' => true]);
} }
$this->server->settings()->update([ $this->server->settings()->update([
'is_reachable' => false, 'is_reachable' => false,
]); ]);
$this->server->update([
'unreachable_count' => 0,
]);
return; return;
} }
$result = $this->server->validateConnection(); $result = $this->server->validateConnection();
@@ -82,7 +85,7 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
if (data_get($this->server, 'unreachable_email_sent') === true) { if (data_get($this->server, 'unreachable_email_sent') === true) {
ray('Server is reachable again, sending notification...'); ray('Server is reachable again, sending notification...');
// $this->server->team->notify(new Revived($this->server)); $this->server->team->notify(new Revived($this->server));
$this->server->update(['unreachable_email_sent' => false]); $this->server->update(['unreachable_email_sent' => false]);
} }
if ( if (
@@ -111,7 +114,6 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
return data_get($value, 'Name') === '/coolify-proxy'; return data_get($value, 'Name') === '/coolify-proxy';
})->first(); })->first();
if (!$foundProxyContainer) { if (!$foundProxyContainer) {
ray('Proxy not found, starting it...');
if ($this->server->isProxyShouldRun()) { if ($this->server->isProxyShouldRun()) {
StartProxy::run($this->server, false); StartProxy::run($this->server, false);
$this->server->team->notify(new ContainerRestarted('coolify-proxy', $this->server)); $this->server->team->notify(new ContainerRestarted('coolify-proxy', $this->server));

View File

@@ -23,6 +23,6 @@ class InstanceAutoUpdateJob implements ShouldQueue, ShouldBeUnique, ShouldBeEncr
public function handle(): void public function handle(): void
{ {
resolve(UpdateCoolify::class)($this->force); UpdateCoolify::run($this->force);
} }
} }

View File

@@ -93,8 +93,11 @@ class Server extends BaseModel
public function proxyType() public function proxyType()
{ {
$type = $this->proxy->get('type'); $proxyType = $this->proxy->get('type');
if (is_null($type)) { if ($proxyType === ProxyTypes::NONE->value) {
return $proxyType;
}
if (is_null($proxyType)) {
$this->proxy->type = ProxyTypes::TRAEFIK_V2->value; $this->proxy->type = ProxyTypes::TRAEFIK_V2->value;
$this->proxy->status = ProxyStatus::EXITED->value; $this->proxy->status = ProxyStatus::EXITED->value;
$this->save(); $this->save();

View File

@@ -72,7 +72,7 @@ class User extends Authenticatable implements SendsEmail
$mail->view('emails.email-verification', [ $mail->view('emails.email-verification', [
'url' => $url, 'url' => $url,
]); ]);
$mail->subject('Coolify Cloud: Verify your email.'); $mail->subject('Coolify: Verify your email.');
send_user_an_email($mail, $this->email); send_user_an_email($mail, $this->email);
} }
public function sendPasswordResetNotification($token): void public function sendPasswordResetNotification($token): void

View File

@@ -7,7 +7,7 @@ return [
// The release version of your application // The release version of your application
// Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD')) // Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD'))
'release' => '4.0.0-beta.76', 'release' => '4.0.0-beta.80',
// When left empty or `null` the Laravel environment will be used // When left empty or `null` the Laravel environment will be used
'environment' => config('app.env'), 'environment' => config('app.env'),

View File

@@ -1,3 +1,3 @@
<?php <?php
return '4.0.0-beta.76'; return '4.0.0-beta.80';

View File

@@ -16,10 +16,6 @@ class ServerSeeder extends Seeder
'ip' => "coolify-testing-host", 'ip' => "coolify-testing-host",
'team_id' => 0, 'team_id' => 0,
'private_key_id' => 0, 'private_key_id' => 0,
// 'proxy' => ServerMetadata::from([
// 'type' => ProxyTypes::TRAEFIK_V2->value,
// 'status' => ProxyStatus::EXITED->value
// ]),
]); ]);
} }
} }

View File

@@ -27,4 +27,4 @@ RUN mkdir -p ~/.ssh
RUN echo "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFuGmoeGq/pojrsyP1pszcNVuZx9iFkCELtxrh31QJ68 coolify@coolify-instance" >> ~/.ssh/authorized_keys RUN echo "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFuGmoeGq/pojrsyP1pszcNVuZx9iFkCELtxrh31QJ68 coolify@coolify-instance" >> ~/.ssh/authorized_keys
EXPOSE 22 EXPOSE 22
CMD ["/usr/sbin/sshd", "-D", "-o", "ListenAddress=0.0.0.0"] CMD ["/usr/sbin/sshd", "-D", "-o", "ListenAddress=0.0.0.0", "-o", "Port=22"]

View File

@@ -1,2 +1,2 @@
User-agent: * User-agent: *
Disallow: Disallow: /

View File

@@ -2,7 +2,9 @@
<livewire:server.proxy.modal :server="$server" /> <livewire:server.proxy.modal :server="$server" />
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<h1>Server</h1> <h1>Server</h1>
<livewire:server.proxy.status :server="$server" /> @if ($server->proxyType() !== 'NONE')
<livewire:server.proxy.status :server="$server" />
@endif
</div> </div>
<div class="subtitle ">{{ data_get($server, 'name') }}</div> <div class="subtitle ">{{ data_get($server, 'name') }}</div>
<nav class="navbar-main"> <nav class="navbar-main">

View File

@@ -1,6 +1,5 @@
<!DOCTYPE html> <!DOCTYPE html>
<html data-theme="coollabs" lang="{{ str_replace('_', '-', app()->getLocale()) }}"> <html data-theme="coollabs" lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">

View File

@@ -14,9 +14,12 @@
<span>Your subscription has been activated! Welcome onboard!</span> <span>Your subscription has been activated! Welcome onboard!</span>
</div> </div>
@endif @endif
@if ($projects->count() === 0 && $servers->count() === 0)
<h3 class="pb-4">Projects</h3> No resources found. Add your first server / private key <a class="text-white underline" href="{{route('server.create')}}">here</a>.
@endif
@if ($projects->count() > 0)
<h3 class="pb-4">Projects</h3>
@endif
@if ($projects->count() === 1) @if ($projects->count() === 1)
<div class="grid grid-cols-1 gap-2"> <div class="grid grid-cols-1 gap-2">
@else @else
@@ -58,7 +61,9 @@
</div> </div>
@endforeach @endforeach
</div> </div>
<h3 class="py-4">Servers</h3> @if ($projects->count() > 0)
<h3 class="pb-4">Servers</h3>
@endif
@if ($servers->count() === 1) @if ($servers->count() === 1)
<div class="grid grid-cols-1 gap-2"> <div class="grid grid-cols-1 gap-2">
@else @else

View File

@@ -1,40 +1,24 @@
<div x-data="{ raw: true, activeTab: window.location.hash ? window.location.hash.substring(1) : 'service-stack' }" wire:poll.2000ms="checkStatus"> <div x-data="{ raw: true, activeTab: window.location.hash ? window.location.hash.substring(1) : 'service-stack' }" wire:poll.15000ms="checkStatus">
<livewire:project.service.navbar :service="$service" :parameters="$parameters" :query="$query" /> <livewire:project.service.navbar :service="$service" :parameters="$parameters" :query="$query" />
<livewire:project.service.compose-modal :raw="$service->docker_compose_raw" :actual="$service->docker_compose" /> <livewire:project.service.compose-modal :raw="$service->docker_compose_raw" :actual="$service->docker_compose" />
<div class="flex h-full pt-6"> <div class="flex h-full pt-6">
<div class="flex flex-col items-start gap-4 min-w-fit"> <div class="flex flex-col items-start gap-4 min-w-fit">
<a target="_blank" href="{{ $service->documentation() }}">Documentation <x-external-link /></a> <a target="_blank" href="{{ $service->documentation() }}">Documentation <x-external-link /></a>
<a :class="activeTab === 'service-stack' && 'text-white'" <a :class="activeTab === 'service-stack' && 'text-white'" @click.prevent="activeTab = 'service-stack';
@click.prevent="activeTab = 'service-stack'; window.location.hash = 'service-stack'" window.location.hash = 'service-stack'" href="#">Service Stack</a>
href="#">Service Stack</a> <a :class="activeTab === 'storages' && 'text-white'" @click.prevent="activeTab = 'storages';
<a :class="activeTab === 'storages' && 'text-white'" window.location.hash = 'storages'" href="#">Storages</a>
@click.prevent="activeTab = 'storages'; window.location.hash = 'storages'" href="#">Storages</a>
<a :class="activeTab === 'environment-variables' && 'text-white'" <a :class="activeTab === 'environment-variables' && 'text-white'"
@click.prevent="activeTab = 'environment-variables'; window.location.hash = 'environment-variables'" @click.prevent="activeTab = 'environment-variables'; window.location.hash = 'environment-variables'"
href="#">Environment href="#">Environment
Variables</a> Variables</a>
<a :class="activeTab === 'danger' && 'text-white'" <a :class="activeTab === 'danger' && 'text-white'" @click.prevent="activeTab = 'danger';
@click.prevent="activeTab = 'danger'; window.location.hash = 'danger'" href="#">Danger Zone window.location.hash = 'danger'" href="#">Danger Zone
</a> </a>
</div> </div>
<div class="w-full pl-8"> <div class="w-full pl-8">
<div x-cloak x-show="activeTab === 'service-stack'"> <div x-cloak x-show="activeTab === 'service-stack'">
<form wire:submit.prevent='submit' class="flex flex-col gap-4 pb-2"> <livewire:project.service.stack-form :service="$service" />
<div class="flex gap-2">
<div>
<h2> Service Stack </h2>
<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>
</div>
<div class="flex gap-2">
<x-forms.input id="service.name" required label="Service Name"
placeholder="My super wordpress site" />
<x-forms.input id="service.description" label="Description" />
</div>
</form>
<div class="grid grid-cols-1 gap-2 pt-4 xl:grid-cols-3"> <div class="grid grid-cols-1 gap-2 pt-4 xl:grid-cols-3">
@foreach ($applications as $application) @foreach ($applications as $application)
<div @class([ <div @class([
@@ -66,7 +50,8 @@
<div class="text-xs">{{ $application->status }}</div> <div class="text-xs">{{ $application->status }}</div>
</a> </a>
<a class="flex gap-2 p-1 mx-4 font-bold rounded group-hover:text-white hover:no-underline" <a class="flex gap-2 p-1 mx-4 font-bold rounded group-hover:text-white hover:no-underline"
href="{{ route('project.service.logs', [...$parameters, 'service_name' => $application->name]) }}"><span class="hover:text-warning">Logs</span></a> href="{{ route('project.service.logs', [...$parameters, 'service_name' => $application->name]) }}"><span
class="hover:text-warning">Logs</span></a>
</div> </div>
@endforeach @endforeach
@foreach ($databases as $database) @foreach ($databases as $database)
@@ -95,7 +80,8 @@
<div class="text-xs">{{ $database->status }}</div> <div class="text-xs">{{ $database->status }}</div>
</a> </a>
<a class="flex gap-2 p-1 mx-4 font-bold rounded hover:no-underline group-hover:text-white" <a class="flex gap-2 p-1 mx-4 font-bold rounded hover:no-underline group-hover:text-white"
href="{{ route('project.service.logs', [...$parameters, 'service_name' => $database->name]) }}"><span class="hover:text-warning">Logs</span></a> href="{{ route('project.service.logs', [...$parameters, 'service_name' => $database->name]) }}"><span
class="hover:text-warning">Logs</span></a>
</div> </div>
@endforeach @endforeach
</div> </div>
@@ -126,3 +112,4 @@
</div> </div>
</div> </div>
</div> </div>
</div>

View File

@@ -1,5 +1,5 @@
<div> <div>
<x-modal submitWireAction="serviceStatusUpdated" modalId="startService"> <x-modal submitWireAction="checkStatus" modalId="startService">
<x-slot:modalBody> <x-slot:modalBody>
<livewire:activity-monitor header="Service Startup Logs" /> <livewire:activity-monitor header="Service Startup Logs" />
</x-slot:modalBody> </x-slot:modalBody>

View File

@@ -1,4 +1,4 @@
<div x-init="$wire.check_status"> <div x-init="$wire.checkStatus">
<livewire:project.service.modal /> <livewire:project.service.modal />
<h1>Configuration</h1> <h1>Configuration</h1>
<x-resources.breadcrumbs :resource="$service" :parameters="$parameters" /> <x-resources.breadcrumbs :resource="$service" :parameters="$parameters" />

View File

@@ -0,0 +1,16 @@
<form wire:submit.prevent='submit' class="flex flex-col gap-4 pb-2">
<div class="flex gap-2">
<div>
<h2>Service Stack</h2>
<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>
</div>
<div class="flex gap-2">
<x-forms.input id="service.name" required label="Service Name"
placeholder="My super wordpress site" />
<x-forms.input id="service.description" label="Description" />
</div>
</form>

View File

@@ -26,16 +26,23 @@
Server is reachable and validated. Server is reachable and validated.
@endif @endif
@if ((!$server->settings->is_reachable || !$server->settings->is_usable) && $server->id !== 0) @if ((!$server->settings->is_reachable || !$server->settings->is_usable) && $server->id !== 0)
<x-forms.button class="mt-8 mb-4 font-bold box-without-bg bg-coollabs hover:bg-coollabs-100" wire:click.prevent='validateServer' isHighlighted> <x-forms.button class="mt-8 mb-4 font-bold box-without-bg bg-coollabs hover:bg-coollabs-100"
wire:click.prevent='validateServer' isHighlighted>
Validate Server & Install Docker Engine Validate Server & Install Docker Engine
</x-forms.button> </x-forms.button>
@endif @endif
@if ((!$server->settings->is_reachable || !$server->settings->is_usable) && $server->id === 0)
<x-forms.button class="mt-8 mb-4 font-bold box-without-bg bg-coollabs hover:bg-coollabs-100"
wire:click.prevent='checkLocalhostConnection' isHighlighted>
Validate Server
</x-forms.button>
@endif
<div class="flex flex-col gap-2 pt-4"> <div class="flex flex-col gap-2 pt-4">
<div class="flex flex-col w-full gap-2 lg:flex-row"> <div class="flex flex-col w-full gap-2 lg:flex-row">
<x-forms.input id="server.name" label="Name" required /> <x-forms.input id="server.name" label="Name" required />
<x-forms.input id="server.description" label="Description" /> <x-forms.input id="server.description" label="Description" />
<x-forms.input placeholder="https://example.com" id="wildcard_domain" label="Wildcard Domain" <x-forms.input placeholder="https://example.com" id="wildcard_domain" label="Wildcard Domain"
helper="Wildcard domain for your applications. If you set this, you will get a random generated domain for your new applications.<br><span class='font-bold text-white'>Example</span>In case you set:<span class='text-helper'>https://example.com</span>your applications will get: <span class='text-helper'>https://randomId.example.com</span>" /> helper="Wildcard domain for your applications. If you set this, you will get a random generated domain for your new applications.<br><span class='font-bold text-white'>Example:</span><br>In case you set:<span class='text-helper'>https://example.com</span> your applications will get:<br> <span class='text-helper'>https://randomId.example.com</span>" />
{{-- <x-forms.checkbox disabled type="checkbox" id="server.settings.is_part_of_swarm" {{-- <x-forms.checkbox disabled type="checkbox" id="server.settings.is_part_of_swarm"
label="Is it part of a Swarm cluster?" /> --}} label="Is it part of a Swarm cluster?" /> --}}
</div> </div>
@@ -59,13 +66,13 @@
helper="Disk cleanup job will be executed if disk usage is more than this number." /> helper="Disk cleanup job will be executed if disk usage is more than this number." />
@endif @endif
</form> </form>
<h2 class="pt-4">Danger Zone</h2> @if ($server->id !== 0)
<div class="">Woah. I hope you know what are you doing.</div> <h2 class="pt-4">Danger Zone</h2>
<h4 class="pt-4">Delete Server</h4> <div class="">Woah. I hope you know what are you doing.</div>
<div class="pb-4">This will remove this server from Coolify. Beware! There is no coming <h4 class="pt-4">Delete Server</h4>
back! <div class="pb-4">This will remove this server from Coolify. Beware! There is no coming
</div> back!
@if ($server->id !== 0 || isDev()) </div>
<x-forms.button isError isModal modalId="deleteServer"> <x-forms.button isError isModal modalId="deleteServer">
Delete Delete
</x-forms.button> </x-forms.button>

View File

@@ -73,6 +73,7 @@ Route::get('/verify', function () {
Route::get('/email/verify/{id}/{hash}', function (EmailVerificationRequest $request) { Route::get('/email/verify/{id}/{hash}', function (EmailVerificationRequest $request) {
$request->fulfill(); $request->fulfill();
send_internal_notification("User {$request->user()->name} verified their email address.");
return redirect('/'); return redirect('/');
})->middleware(['auth'])->name('verify.verify'); })->middleware(['auth'])->name('verify.verify');

View File

@@ -4,7 +4,7 @@
"version": "3.12.36" "version": "3.12.36"
}, },
"v4": { "v4": {
"version": "4.0.0-beta.76" "version": "4.0.0-beta.80"
} }
} }
} }