Merge pull request #1295 from coollabsio/next

v4.0.0-beta.70
This commit is contained in:
Andras Bacsai 2023-10-09 11:23:32 +02:00 committed by GitHub
commit 0de042dbac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
49 changed files with 536 additions and 182 deletions

View File

@ -1,11 +1,3 @@
############################################################################################################
# Development Environment
# User and group id for the user that will run the application inside the container
# Run in your terminal: `id -u` and `id -g` and that's the results
USERID=
GROUPID=
############################################################################################################
APP_NAME=Coolify-localhost APP_NAME=Coolify-localhost
APP_ID=development APP_ID=development
APP_ENV=local APP_ENV=local
@ -13,6 +5,7 @@ APP_KEY=
APP_DEBUG=true APP_DEBUG=true
APP_URL=http://localhost APP_URL=http://localhost
APP_PORT=8000 APP_PORT=8000
MUX_ENABLED=false
DUSK_DRIVER_URL=http://selenium:4444 DUSK_DRIVER_URL=http://selenium:4444

View File

@ -15,11 +15,12 @@ ## 1) Setup your development environment
## 2) Set your environment variables ## 2) Set your environment variables
- Copy [.env.development.example](./.env.development.example) to .env. - Copy [.env.development.example](./.env.development.example) to .env.
- If necessary, set `USERID` & `GROUPID` accordingly (read in .env file).
## 3) Start & setup Coolify ## 3) Start & setup Coolify
- Run `spin up` - You can notice that errors will be thrown. Don't worry. - Run `spin up` - You can notice that errors will be thrown. Don't worry.
- If you see weird permission errors, especially on Mac, run `sudo spin up` instead.
- 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. - 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 ## 4) Start development

View File

@ -2,12 +2,14 @@
namespace App\Actions\Server; namespace App\Actions\Server;
use Lorisleiva\Actions\Concerns\AsAction;
use App\Models\Server; use App\Models\Server;
use App\Models\StandaloneDocker; use App\Models\StandaloneDocker;
class InstallDocker class InstallDocker
{ {
public function __invoke(Server $server) use AsAction;
public function handle(Server $server)
{ {
$dockerVersion = '24.0'; $dockerVersion = '24.0';
$config = base64_encode('{ $config = base64_encode('{

View File

@ -0,0 +1,33 @@
<?php
namespace App\Console\Commands;
use App\Models\Server;
use Illuminate\Console\Command;
class Cloud extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'cloud:unused-servers';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Get Unused Servers from Cloud';
/**
* Execute the console command.
*/
public function handle()
{
Server::all()->whereNotNull('team.subscription')->where('team.subscription.stripe_trial_already_ended',true)->each(function($server){
$this->info($server->name);
});
}
}

View File

@ -48,7 +48,11 @@ private function cleanup_servers($schedule)
} }
private function check_resources($schedule) private function check_resources($schedule)
{ {
$servers = Server::all()->where('settings.is_usable', true)->where('settings.is_reachable', true); if (isCloud()) {
$servers = Server::all()->whereNotNull('team.subscription')->where('team.subscription.stripe_trial_already_ended', false);
} else {
$servers = Server::all();
}
foreach ($servers as $server) { foreach ($servers as $server) {
$schedule->job(new ContainerStatusJob($server))->everyMinute()->onOneServer(); $schedule->job(new ContainerStatusJob($server))->everyMinute()->onOneServer();
} }

View File

@ -1,32 +0,0 @@
<?php
namespace App\Http\Controllers;
use App\Models\PrivateKey;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Validation\ValidatesRequests;
class ServerController extends Controller
{
use AuthorizesRequests, ValidatesRequests;
public function new_server()
{
$privateKeys = PrivateKey::ownedByCurrentTeam()->get();
if (!isCloud()) {
return view('server.create', [
'limit_reached' => false,
'private_keys' => $privateKeys,
]);
}
$team = currentTeam();
$servers = $team->servers->count();
['serverLimit' => $serverLimit] = $team->limits;
$limit_reached = $servers >= $serverLimit;
return view('server.create', [
'limit_reached' => $limit_reached,
'private_keys' => $privateKeys,
]);
}
}

View File

@ -220,7 +220,7 @@ public function validateServer()
public function installDocker() public function installDocker()
{ {
$this->dockerInstallationStarted = true; $this->dockerInstallationStarted = true;
$activity = resolve(InstallDocker::class)($this->createdServer); $activity = InstallDocker::run($this->createdServer);
$this->emit('newMonitorActivity', $activity->id); $this->emit('newMonitorActivity', $activity->id);
} }
public function dockerInstalledOrSkipped() public function dockerInstalledOrSkipped()

View File

@ -17,10 +17,10 @@ class General extends Component
public Application $application; public Application $application;
public Collection $services; public Collection $services;
public string $name; public string $name;
public string|null $fqdn; public ?string $fqdn = null;
public string $git_repository; public string $git_repository;
public string $git_branch; public string $git_branch;
public string|null $git_commit_sha; public ?string $git_commit_sha = null;
public string $build_pack; public string $build_pack;
public bool $is_static; public bool $is_static;

View File

@ -144,7 +144,7 @@ public function submit()
if ($this->git_source === 'other') { if ($this->git_source === 'other') {
$application_init = [ $application_init = [
'name' => generate_application_name($this->git_repository, $this->git_branch), 'name' => generate_random_name(),
'git_repository' => $this->git_repository, 'git_repository' => $this->git_repository,
'git_branch' => $this->git_branch, 'git_branch' => $this->git_branch,
'build_pack' => 'nixpacks', 'build_pack' => 'nixpacks',
@ -178,7 +178,6 @@ public function submit()
$fqdn = generateFqdn($destination->server, $application->uuid); $fqdn = generateFqdn($destination->server, $application->uuid);
$application->fqdn = $fqdn; $application->fqdn = $fqdn;
$application->name = generate_application_name($this->git_repository, $this->git_branch, $application->uuid);
$application->save(); $application->save();
return redirect()->route('project.application.configuration', [ return redirect()->route('project.application.configuration', [

View File

@ -0,0 +1,29 @@
<?php
namespace App\Http\Livewire\Server;
use App\Models\PrivateKey;
use Livewire\Component;
class Create extends Component
{
public $private_keys = [];
public bool $limit_reached = false;
public function mount()
{
$this->private_keys = PrivateKey::ownedByCurrentTeam()->get();
if (!isCloud()) {
$this->limit_reached = false;
return;
}
$team = currentTeam();
$servers = $team->servers->count();
['serverLimit' => $serverLimit] = $team->limits;
$this->limit_reached = $servers >= $serverLimit;
}
public function render()
{
return view('livewire.server.create');
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace App\Http\Livewire\Server\Destination;
use App\Models\Server;
use Livewire\Component;
class Show extends Component
{
public ?Server $server = null;
public $parameters = [];
public function mount()
{
$this->parameters = get_route_parameters();
try {
$this->server = Server::ownedByCurrentTeam(['name', 'proxy'])->whereUuid(request()->server_uuid)->first();
if (is_null($this->server)) {
return redirect()->route('server.all');
}
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function render()
{
return view('livewire.server.destination.show');
}
}

View File

@ -11,11 +11,12 @@ class Form extends Component
{ {
use AuthorizesRequests; use AuthorizesRequests;
public Server $server; public Server $server;
public $uptime; public bool $isValidConnection = false;
public $dockerVersion; public bool $isValidDocker = false;
public string|null $wildcard_domain = null; public ?string $wildcard_domain = null;
public int $cleanup_after_percentage; public int $cleanup_after_percentage;
public bool $dockerInstallationStarted = false; public bool $dockerInstallationStarted = false;
protected $listeners = ['serverRefresh'];
protected $rules = [ protected $rules = [
'server.name' => 'required|min:6', 'server.name' => 'required|min:6',
@ -44,37 +45,49 @@ public function mount()
$this->wildcard_domain = $this->server->settings->wildcard_domain; $this->wildcard_domain = $this->server->settings->wildcard_domain;
$this->cleanup_after_percentage = $this->server->settings->cleanup_after_percentage; $this->cleanup_after_percentage = $this->server->settings->cleanup_after_percentage;
} }
public function instantSave() { public function serverRefresh() {
$this->validateServer();
}
public function instantSave()
{
refresh_server_connection($this->server->privateKey); refresh_server_connection($this->server->privateKey);
$this->validateServer(); $this->validateServer();
$this->server->settings->save(); $this->server->settings->save();
} }
public function installDocker() public function installDocker()
{ {
$this->emit('installDocker');
$this->dockerInstallationStarted = true; $this->dockerInstallationStarted = true;
$activity = resolve(InstallDocker::class)($this->server); $activity = InstallDocker::run($this->server);
$this->emit('newMonitorActivity', $activity->id); $this->emit('newMonitorActivity', $activity->id);
} }
public function validateServer() public function validateServer($install = true)
{ {
try { try {
['uptime' => $uptime, 'dockerVersion' => $dockerVersion] = validateServer($this->server, true); $uptime = $this->server->validateConnection();
if ($uptime) { if ($uptime) {
$this->uptime = $uptime; $install && $this->emit('success', 'Server is reachable.');
$this->emit('success', 'Server is reachable.');
} else { } else {
$this->emit('error', 'Server is not reachable.'); $install &&$this->emit('error', 'Server is not reachable. Please check your connection and private key configuration.');
return; return;
} }
if ($dockerVersion) { $dockerInstalled = $this->server->validateDockerEngine();
$this->dockerVersion = $dockerVersion; if ($dockerInstalled) {
$this->emit('success', 'Docker Engine 23+ is installed!'); $install && $this->emit('success', 'Docker Engine is installed.<br> Checking version.');
} else { } else {
$this->emit('error', 'No Docker Engine or older than 23 version installed.'); $install && $this->installDocker();
return;
}
$dockerVersion = $this->server->validateDockerEngineVersion();
if ($dockerVersion) {
$install && $this->emit('success', 'Docker Engine version is 23+.');
} else {
$install && $this->installDocker();
return;
} }
} catch (\Throwable $e) { } catch (\Throwable $e) {
return handleError($e, $this, customErrorMessage: "Server is not reachable: "); return handleError($e, $this);
} finally { } finally {
$this->emit('proxyStatusUpdated'); $this->emit('proxyStatusUpdated');
} }

View File

@ -0,0 +1,31 @@
<?php
namespace App\Http\Livewire\Server\PrivateKey;
use App\Models\PrivateKey;
use App\Models\Server;
use Livewire\Component;
class Show extends Component
{
public ?Server $server = null;
public $privateKeys = [];
public $parameters = [];
public function mount()
{
$this->parameters = get_route_parameters();
try {
$this->server = Server::ownedByCurrentTeam(['name', 'proxy'])->whereUuid(request()->server_uuid)->first();
if (is_null($this->server)) {
return redirect()->route('server.all');
}
$this->privateKeys = PrivateKey::ownedByCurrentTeam()->get()->where('is_git_related', false);
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function render()
{
return view('livewire.server.private-key.show');
}
}

View File

@ -11,7 +11,7 @@ class Deploy extends Component
public Server $server; public Server $server;
public bool $traefikDashboardAvailable = false; public bool $traefikDashboardAvailable = false;
public ?string $currentRoute = null; public ?string $currentRoute = null;
protected $listeners = ['proxyStatusUpdated', 'traefikDashboardAvailable']; protected $listeners = ['proxyStatusUpdated', 'traefikDashboardAvailable', 'serverRefresh' => 'proxyStatusUpdated'];
public function mount() { public function mount() {
$this->currentRoute = request()->route()->getName(); $this->currentRoute = request()->route()->getName();

View File

@ -0,0 +1,28 @@
<?php
namespace App\Http\Livewire\Server\Proxy;
use App\Models\Server;
use Livewire\Component;
class Show extends Component
{
public ?Server $server = null;
public $parameters = [];
public function mount()
{
$this->parameters = get_route_parameters();
try {
$this->server = Server::ownedByCurrentTeam(['name', 'proxy'])->whereUuid(request()->server_uuid)->first();
if (is_null($this->server)) {
return redirect()->route('server.all');
}
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function render()
{
return view('livewire.server.proxy.show');
}
}

View File

@ -26,7 +26,9 @@ public function getProxyStatus()
} }
public function getProxyStatusWithNoti() public function getProxyStatusWithNoti()
{ {
$this->emit('success', 'Refreshed proxy status.'); if ($this->server->isFunctional()) {
$this->getProxyStatus(); $this->emit('success', 'Refreshed proxy status.');
$this->getProxyStatus();
}
} }
} }

View File

@ -10,8 +10,10 @@ class Show extends Component
{ {
use AuthorizesRequests; use AuthorizesRequests;
public ?Server $server = null; public ?Server $server = null;
public $parameters = [];
public function mount() public function mount()
{ {
$this->parameters = get_route_parameters();
try { try {
$this->server = Server::ownedByCurrentTeam(['name', 'description', 'ip', 'port', 'user', 'proxy'])->whereUuid(request()->server_uuid)->first(); $this->server = Server::ownedByCurrentTeam(['name', 'description', 'ip', 'port', 'user', 'proxy'])->whereUuid(request()->server_uuid)->first();
if (is_null($this->server)) { if (is_null($this->server)) {
@ -21,6 +23,10 @@ public function mount()
return handleError($e, $this); return handleError($e, $this);
} }
} }
public function submit()
{
$this->emit('serverRefresh');
}
public function render() public function render()
{ {
return view('livewire.server.show'); return view('livewire.server.show');

View File

@ -32,36 +32,34 @@ public function setPrivateKey($newPrivateKeyId)
} }
} }
public function checkConnection() public function checkConnection($install = false)
{ {
try { try {
['uptime' => $uptime, 'dockerVersion' => $dockerVersion] = validateServer($this->server, true); $uptime = $this->server->validateConnection();
if ($uptime) { if ($uptime) {
$this->server->settings->update([ $install && $this->emit('success', 'Server is reachable.');
'is_reachable' => true
]);
$this->emit('success', 'Server is reachable with this private key.');
} else { } else {
$this->server->settings->update([ $install && $this->emit('error', 'Server is not reachable. Please check your connection and private key configuration.');
'is_reachable' => false,
'is_usable' => false
]);
$this->emit('error', 'Server is not reachable with this private key.');
return; return;
} }
if ($dockerVersion) { $dockerInstalled = $this->server->validateDockerEngine();
$this->server->settings->update([ if ($dockerInstalled) {
'is_usable' => true $install && $this->emit('success', 'Docker Engine is installed.<br> Checking version.');
]);
$this->emit('success', 'Server is usable for Coolify.');
} else { } else {
$this->server->settings->update([ $install && $this->installDocker();
'is_usable' => false return;
]); }
$this->emit('error', 'Old (lower than 23) or no Docker version detected. Install Docker Engine on the General tab.'); $dockerVersion = $this->server->validateDockerEngineVersion();
if ($dockerVersion) {
$install && $this->emit('success', 'Docker Engine version is 23+.');
} else {
$install && $this->installDocker();
return;
} }
} catch (\Throwable $e) { } catch (\Throwable $e) {
return handleError($e, $this); return handleError($e, $this);
} finally {
$this->emit('proxyStatusUpdated');
} }
} }

View File

@ -254,7 +254,7 @@ private function deploy()
); );
$this->prepare_builder_image(); $this->prepare_builder_image();
$this->clone_repository(); $this->clone_repository();
$this->set_base_dir();
$tag = Str::of("{$this->commit}-{$this->application->id}-{$this->pull_request_id}"); $tag = Str::of("{$this->commit}-{$this->application->id}-{$this->pull_request_id}");
if (strlen($tag) > 128) { if (strlen($tag) > 128) {
$tag = $tag->substr(0, 128); $tag = $tag->substr(0, 128);
@ -364,6 +364,7 @@ private function deploy_pull_request()
]); ]);
$this->prepare_builder_image(); $this->prepare_builder_image();
$this->clone_repository(); $this->clone_repository();
$this->set_base_dir();
$this->cleanup_git(); $this->cleanup_git();
if ($this->application->build_pack === 'nixpacks') { if ($this->application->build_pack === 'nixpacks') {
$this->generate_nixpacks_confs(); $this->generate_nixpacks_confs();
@ -400,7 +401,13 @@ private function prepare_builder_image()
); );
} }
private function set_base_dir() {
$this->execute_remote_command(
[
"echo -n 'Setting base directory to {$this->workdir}.'"
],
);
}
private function clone_repository() private function clone_repository()
{ {
@ -452,7 +459,7 @@ private function importing_git_repository()
} }
if ($this->application->deploymentType() === 'deploy_key') { if ($this->application->deploymentType() === 'deploy_key') {
$private_key = base64_encode($this->application->private_key->private_key); $private_key = base64_encode($this->application->private_key->private_key);
$git_clone_command = "GIT_SSH_COMMAND=\"ssh -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" {$git_clone_command} {$this->application->git_full_url} {$this->workdir}"; $git_clone_command = "GIT_SSH_COMMAND=\"ssh -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" {$git_clone_command} {$this->application->git_full_url} {$this->basedir}";
$git_clone_command = $this->set_git_import_settings($git_clone_command); $git_clone_command = $this->set_git_import_settings($git_clone_command);
$commands = collect([ $commands = collect([
executeInDocker($this->deployment_uuid, "mkdir -p /root/.ssh"), executeInDocker($this->deployment_uuid, "mkdir -p /root/.ssh"),

View File

@ -7,6 +7,7 @@
use App\Models\Server; use App\Models\Server;
use App\Notifications\Container\ContainerRestarted; use App\Notifications\Container\ContainerRestarted;
use App\Notifications\Container\ContainerStopped; use App\Notifications\Container\ContainerStopped;
use App\Notifications\Server\Revived;
use App\Notifications\Server\Unreachable; use App\Notifications\Server\Unreachable;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeEncrypted; use Illuminate\Contracts\Queue\ShouldBeEncrypted;
@ -40,33 +41,49 @@ public function uniqueId(): string
{ {
return $this->server->uuid; return $this->server->uuid;
} }
public function handle()
private function checkServerConnection()
{
$uptime = instant_remote_process(['uptime'], $this->server, false);
if (!is_null($uptime)) {
return true;
}
}
public function handle(): void
{ {
try { try {
ray("checking server status for {$this->server->name}");
// ray()->clearAll(); // ray()->clearAll();
$serverUptimeCheckNumber = 0; $serverUptimeCheckNumber = 0;
$serverUptimeCheckNumberMax = 3; $serverUptimeCheckNumberMax = 3;
while (true) { while (true) {
ray('checking # ' . $serverUptimeCheckNumber);
if ($serverUptimeCheckNumber >= $serverUptimeCheckNumberMax) { if ($serverUptimeCheckNumber >= $serverUptimeCheckNumberMax) {
$this->server->settings()->update(['is_reachable' => false]); send_internal_notification('Server unreachable: ' . $this->server->name);
$this->server->team->notify(new Unreachable($this->server)); if ($this->server->unreachable_email_sent === false) {
ray('Server unreachable, sending notification...');
$this->server->team->notify(new Unreachable($this->server));
}
$this->server->settings()->update([
'is_reachable' => false,
]);
$this->server->update(['unreachable_email_sent' => true]);
return; return;
} }
$result = $this->checkServerConnection(); $result = $this->server->validateConnection();
if ($result) { if ($result) {
break; break;
} }
$serverUptimeCheckNumber++; $serverUptimeCheckNumber++;
sleep(5); sleep(5);
} }
if (data_get($this->server, 'unreachable_email_sent') === true) {
ray('Server is reachable again, sending notification...');
$this->server->team->notify(new Revived($this->server));
$this->server->update(['unreachable_email_sent' => false]);
}
if (
data_get($this->server, 'settings.is_reachable') === false ||
data_get($this->server, 'settings.is_usable') === false
) {
$this->server->settings()->update([
'is_reachable' => true,
'is_usable' => true
]);
}
$this->server->validateDockerEngine(true);
$containers = instant_remote_process(["docker container ls -q"], $this->server); $containers = instant_remote_process(["docker container ls -q"], $this->server);
if (!$containers) { if (!$containers) {
return; return;
@ -266,7 +283,7 @@ public function handle(): void
} catch (\Throwable $e) { } catch (\Throwable $e) {
send_internal_notification('ContainerStatusJob failed with: ' . $e->getMessage()); send_internal_notification('ContainerStatusJob failed with: ' . $e->getMessage());
ray($e->getMessage()); ray($e->getMessage());
throw $e; return handleError($e);
} }
} }
} }

View File

@ -5,6 +5,7 @@
use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
use Spatie\Activitylog\Models\Activity; use Spatie\Activitylog\Models\Activity;
use Illuminate\Support\Str;
class Application extends BaseModel class Application extends BaseModel
{ {
@ -12,6 +13,19 @@ class Application extends BaseModel
protected static function booted() protected static function booted()
{ {
static::saving(function ($application) {
if ($application->fqdn == '') {
$application->fqdn = null;
}
$application->forceFill([
'fqdn' => $application->fqdn,
'install_command' => Str::of($application->install_command)->trim(),
'build_command' => Str::of($application->build_command)->trim(),
'start_command' => Str::of($application->start_command)->trim(),
'base_directory' => Str::of($application->base_directory)->trim(),
'publish_directory' => Str::of($application->publish_directory)->trim(),
]);
});
static::created(function ($application) { static::created(function ($application) {
ApplicationSetting::create([ ApplicationSetting::create([
'application_id' => $application->id, 'application_id' => $application->id,

View File

@ -8,6 +8,7 @@
use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Casts\Attribute;
use Spatie\SchemalessAttributes\Casts\SchemalessAttributes; use Spatie\SchemalessAttributes\Casts\SchemalessAttributes;
use Spatie\SchemalessAttributes\SchemalessAttributesTrait; use Spatie\SchemalessAttributes\SchemalessAttributesTrait;
use Illuminate\Support\Str;
class Server extends BaseModel class Server extends BaseModel
{ {
@ -15,6 +16,13 @@ class Server extends BaseModel
protected static function booted() protected static function booted()
{ {
static::saving(function ($server) {
$server->forceFill([
'ip' => Str::of($server->ip)->trim(),
'user' => Str::of($server->user)->trim(),
]);
});
static::created(function ($server) { static::created(function ($server) {
ServerSetting::create([ ServerSetting::create([
'server_id' => $server->id, 'server_id' => $server->id,
@ -199,4 +207,48 @@ public function isFunctional()
{ {
return $this->settings->is_reachable && $this->settings->is_usable; return $this->settings->is_reachable && $this->settings->is_usable;
} }
public function validateConnection()
{
$uptime = instant_remote_process(['uptime'], $this, false);
if (!$uptime) {
$this->settings->is_reachable = false;
$this->settings->save();
return false;
}
$this->settings->is_reachable = true;
$this->settings->save();
return true;
}
public function validateDockerEngine($throwError = false)
{
$dockerBinary = instant_remote_process(["command -v docker"], $this, false);
if (is_null($dockerBinary)) {
$this->settings->is_usable = false;
$this->settings->save();
if ($throwError) {
throw new \Exception('Server is not usable.');
}
return false;
}
$this->settings->is_usable = true;
$this->settings->save();
$this->validateCoolifyNetwork();
return true;
}
public function validateDockerEngineVersion()
{
$dockerVersion = instant_remote_process(["docker version|head -2|grep -i version| awk '{print $2}'"], $this, false);
$dockerVersion = checkMinimumDockerEngineVersion($dockerVersion);
if (is_null($dockerVersion)) {
$this->settings->is_usable = false;
$this->settings->save();
return false;
}
$this->settings->is_usable = true;
$this->settings->save();
return true;
}
public function validateCoolifyNetwork() {
return instant_remote_process(["docker network create coolify --attachable >/dev/null 2>&1 || true"], $this, false);
}
} }

View File

@ -0,0 +1,66 @@
<?php
namespace App\Notifications\Server;
use App\Models\Server;
use Illuminate\Bus\Queueable;
use App\Notifications\Channels\DiscordChannel;
use App\Notifications\Channels\EmailChannel;
use App\Notifications\Channels\TelegramChannel;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
class Revived extends Notification implements ShouldQueue
{
use Queueable;
public $tries = 1;
public function __construct(public Server $server)
{
if ($this->server->unreachable_email_sent === false) {
return;
}
}
public function via(object $notifiable): array
{
$channels = [];
$isEmailEnabled = isEmailEnabled($notifiable);
$isDiscordEnabled = data_get($notifiable, 'discord_enabled');
$isTelegramEnabled = data_get($notifiable, 'telegram_enabled');
if ($isDiscordEnabled) {
$channels[] = DiscordChannel::class;
}
if ($isEmailEnabled ) {
$channels[] = EmailChannel::class;
}
if ($isTelegramEnabled) {
$channels[] = TelegramChannel::class;
}
return $channels;
}
public function toMail(): MailMessage
{
$mail = new MailMessage();
$mail->subject("✅ Server ({$this->server->name}) revived.");
$mail->view('emails.server-revived', [
'name' => $this->server->name,
]);
return $mail;
}
public function toDiscord(): string
{
$message = "✅ Server '{$this->server->name}' revived. All automations & integrations are turned on again!";
return $message;
}
public function toTelegram(): array
{
return [
"message" => "✅ Server '{$this->server->name}' revived. All automations & integrations are turned on again!"
];
}
}

View File

@ -3,6 +3,9 @@
namespace App\Notifications\Server; namespace App\Notifications\Server;
use App\Models\Server; use App\Models\Server;
use App\Notifications\Channels\DiscordChannel;
use App\Notifications\Channels\EmailChannel;
use App\Notifications\Channels\TelegramChannel;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Messages\MailMessage;
@ -20,7 +23,21 @@ public function __construct(public Server $server)
public function via(object $notifiable): array public function via(object $notifiable): array
{ {
return setNotificationChannels($notifiable, 'status_changes'); $channels = [];
$isEmailEnabled = isEmailEnabled($notifiable);
$isDiscordEnabled = data_get($notifiable, 'discord_enabled');
$isTelegramEnabled = data_get($notifiable, 'telegram_enabled');
if ($isDiscordEnabled) {
$channels[] = DiscordChannel::class;
}
if ($isEmailEnabled ) {
$channels[] = EmailChannel::class;
}
if ($isTelegramEnabled) {
$channels[] = TelegramChannel::class;
}
return $channels;
} }
public function toMail(): MailMessage public function toMail(): MailMessage
@ -35,13 +52,13 @@ public function toMail(): MailMessage
public function toDiscord(): string public function toDiscord(): string
{ {
$message = "⛔ Server '{$this->server->name}' is unreachable after trying to connect to it 5 times. All automations & integrations are turned off! Please check your server! IMPORTANT: You have to validate your server again after you fix the issue."; $message = "⛔ Server '{$this->server->name}' is unreachable after trying to connect to it 5 times. All automations & integrations are turned off! Please check your server! IMPORTANT: We automatically try to revive your server. If your server is back online, we will automatically turn on all automations & integrations.";
return $message; return $message;
} }
public function toTelegram(): array public function toTelegram(): array
{ {
return [ return [
"message" => "⛔ Server '{$this->server->name}' is unreachable after trying to connect to it 5 times. All automations & integrations are turned off! Please check your server! IMPORTANT: You have to validate your server again after you fix the issue." "message" => "⛔ Server '{$this->server->name}' is unreachable after trying to connect to it 5 times. All automations & integrations are turned off! Please check your server! IMPORTANT: We automatically try to revive your server. If your server is back online, we will automatically turn on all automations & integrations."
]; ];
} }
} }

View File

@ -7,6 +7,8 @@
use App\Models\ApplicationDeploymentQueue; use App\Models\ApplicationDeploymentQueue;
use App\Models\PrivateKey; use App\Models\PrivateKey;
use App\Models\Server; use App\Models\Server;
use App\Notifications\Server\Revived;
use App\Notifications\Server\Unreachable;
use Carbon\Carbon; use Carbon\Carbon;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
@ -85,7 +87,7 @@ function generateSshCommand(Server $server, string $command, bool $isMux = true)
if ($isMux && config('coolify.mux_enabled')) { if ($isMux && config('coolify.mux_enabled')) {
$ssh_command .= '-o ControlMaster=auto -o ControlPersist=1m -o ControlPath=/var/www/html/storage/app/ssh/mux/%h_%p_%r '; $ssh_command .= '-o ControlMaster=auto -o ControlPersist=1m -o ControlPath=/var/www/html/storage/app/ssh/mux/%h_%p_%r ';
} }
if (data_get($server,'settings.is_cloudflare_tunnel')) { if (data_get($server, 'settings.is_cloudflare_tunnel')) {
$ssh_command .= '-o ProxyCommand="/usr/local/bin/cloudflared access ssh --hostname %h" '; $ssh_command .= '-o ProxyCommand="/usr/local/bin/cloudflared access ssh --hostname %h" ';
} }
$command = "PATH=\$PATH:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/host/usr/local/sbin:/host/usr/local/bin:/host/usr/sbin:/host/usr/bin:/host/sbin:/host/bin && $command"; $command = "PATH=\$PATH:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/host/usr/local/sbin:/host/usr/local/bin:/host/usr/sbin:/host/usr/bin:/host/sbin:/host/bin && $command";
@ -122,13 +124,14 @@ function instant_remote_process(Collection|array $command, Server $server, $thro
} }
return $output; return $output;
} }
function excludeCertainErrors(string $errorOutput, ?int $exitCode = null) { function excludeCertainErrors(string $errorOutput, ?int $exitCode = null)
{
$ignoredErrors = collect([ $ignoredErrors = collect([
'Permission denied (publickey', 'Permission denied (publickey',
'Could not resolve hostname', 'Could not resolve hostname',
]); ]);
$ignored = false; $ignored = false;
foreach ($ignoredErrors as $ignoredError) { foreach ($ignoredErrors as $ignoredError) {
if (Str::contains($errorOutput, $ignoredError)) { if (Str::contains($errorOutput, $ignoredError)) {
$ignored = true; $ignored = true;
break; break;
@ -183,6 +186,9 @@ function validateServer(Server $server, bool $throwError = false)
$uptime = instant_remote_process(['uptime'], $server, $throwError); $uptime = instant_remote_process(['uptime'], $server, $throwError);
if (!$uptime) { if (!$uptime) {
$server->settings->is_reachable = false; $server->settings->is_reachable = false;
$server->team->notify(new Unreachable($server));
$server->unreachable_email_sent = true;
$server->save();
return [ return [
"uptime" => null, "uptime" => null,
"dockerVersion" => null, "dockerVersion" => null,
@ -203,6 +209,11 @@ function validateServer(Server $server, bool $throwError = false)
$server->settings->is_usable = false; $server->settings->is_usable = false;
} else { } else {
$server->settings->is_usable = true; $server->settings->is_usable = true;
if (data_get($server, 'unreachable_email_sent') === true) {
$server->team->notify(new Revived($server));
$server->unreachable_email_sent = false;
$server->save();
}
} }
return [ return [
"uptime" => $uptime, "uptime" => $uptime,
@ -213,7 +224,9 @@ function validateServer(Server $server, bool $throwError = false)
$server->settings->is_usable = false; $server->settings->is_usable = false;
throw $e; throw $e;
} finally { } finally {
if (data_get($server, 'settings')) $server->settings->save(); if (data_get($server, 'settings')) {
$server->settings->save();
}
} }
} }

View File

@ -310,6 +310,7 @@ function send_internal_notification(string $message): void
$baseUrl = config('app.name'); $baseUrl = config('app.name');
$team = Team::find(0); $team = Team::find(0);
$team->notify(new GeneralNotification("👀 {$baseUrl}: " . $message)); $team->notify(new GeneralNotification("👀 {$baseUrl}: " . $message));
ray("👀 {$baseUrl}: " . $message);
} catch (\Throwable $e) { } catch (\Throwable $e) {
ray($e->getMessage()); ray($e->getMessage());
} }

View File

@ -7,7 +7,7 @@
// 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.69', 'release' => '4.0.0-beta.70',
// 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.69'; return '4.0.0-beta.70';

View File

@ -0,0 +1,31 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('servers', function (Blueprint $table) {
$table->boolean('unreachable_email_sent')->default(false);
$table->dropColumn('unreachable_count');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('servers', function (Blueprint $table) {
$table->dropColumn('unreachable_email_sent');
$table->integer('unreachable_count')->default(0);
});
}
};

View File

@ -56,7 +56,7 @@ .box {
@apply flex items-center p-2 transition-colors cursor-pointer min-h-16 bg-coolgray-200 hover:bg-coollabs-100 hover:text-white hover:no-underline min-w-[24rem]; @apply flex items-center p-2 transition-colors cursor-pointer min-h-16 bg-coolgray-200 hover:bg-coollabs-100 hover:text-white hover:no-underline min-w-[24rem];
} }
.box-without-bg { .box-without-bg {
@apply flex items-center p-2 transition-colors min-h-16 hover:text-white hover:no-underline min-w-[24rem]; @apply flex items-center p-2 transition-colors min-h-16 hover:text-white hover:no-underline min-w-[24rem];
} }
.lds-heart { .lds-heart {

View File

@ -8,25 +8,25 @@
<nav class="navbar-main"> <nav class="navbar-main">
<a class="{{ request()->routeIs('server.show') ? 'text-white' : '' }}" <a class="{{ request()->routeIs('server.show') ? 'text-white' : '' }}"
href="{{ route('server.show', [ href="{{ route('server.show', [
'server_uuid' => Route::current()->parameters()['server_uuid'], 'server_uuid' => data_get($parameters, 'server_uuid'),
]) }}"> ]) }}">
<button>General</button> <button>General</button>
</a> </a>
<a class="{{ request()->routeIs('server.private-key') ? 'text-white' : '' }}" <a class="{{ request()->routeIs('server.private-key') ? 'text-white' : '' }}"
href="{{ route('server.private-key', [ href="{{ route('server.private-key', [
'server_uuid' => Route::current()->parameters()['server_uuid'], 'server_uuid' => data_get($parameters, 'server_uuid'),
]) }}"> ]) }}">
<button>Private Key</button> <button>Private Key</button>
</a> </a>
<a class="{{ request()->routeIs('server.proxy') ? 'text-white' : '' }}" <a class="{{ request()->routeIs('server.proxy') ? 'text-white' : '' }}"
href="{{ route('server.proxy', [ href="{{ route('server.proxy', [
'server_uuid' => Route::current()->parameters()['server_uuid'], 'server_uuid' => data_get($parameters, 'server_uuid'),
]) }}"> ]) }}">
<button>Proxy</button> <button>Proxy</button>
</a> </a>
<a class="{{ request()->routeIs('server.destinations') ? 'text-white' : '' }}" <a class="{{ request()->routeIs('server.destinations') ? 'text-white' : '' }}"
href="{{ route('server.destinations', [ href="{{ route('server.destinations', [
'server_uuid' => Route::current()->parameters()['server_uuid'], 'server_uuid' => data_get($parameters, 'server_uuid'),
]) }}"> ]) }}">
<button>Destinations</button> <button>Destinations</button>
</a> </a>

View File

@ -5,10 +5,6 @@
@if ($containerName === 'coolify-proxy') @if ($containerName === 'coolify-proxy')
Coolify Proxy should run on your server as you have FQDNs set up in one of your resources. Coolify Proxy should run on your server as you have FQDNs set up in one of your resources.
Note: The proxy should not stop unexpectedly, so please check what is going on your server.
If you don't want to use Coolify Proxy, please remove FQDN from your resources or set Proxy type to Custom(None). If you don't want to use Coolify Proxy, please remove FQDN from your resources or set Proxy type to Custom(None).
@endif @endif

View File

@ -4,7 +4,7 @@
All automations & integrations are turned off! All automations & integrations are turned off!
IMPORTANT: You have to validate your server again after you fix the issue. IMPORTANT: We automatically try to revive your server. If your server is back online, we will automatically turn on all automations & integrations.
If you have any questions, please contact us. If you have any questions, please contact us.

View File

@ -0,0 +1,6 @@
<x-emails.layout>
Your server ({{$name}}) was offline for a while, but it is back online now. All automations & integrations are turned on again.
</x-emails.layout>

View File

@ -41,25 +41,28 @@
</div> </div>
@endif @endif
<h3>Build</h3>
@if ($application->could_set_build_commands()) @if ($application->could_set_build_commands())
<h3>Build</h3>
<div class="flex flex-col gap-2 xl:flex-row"> <div class="flex flex-col gap-2 xl:flex-row">
<x-forms.input placeholder="pnpm install" id="application.install_command" <x-forms.input placeholder="pnpm install" id="application.install_command"
label="Install Command" /> label="Install Command" />
<x-forms.input placeholder="pnpm build" id="application.build_command" label="Build Command" /> <x-forms.input placeholder="pnpm build" id="application.build_command" label="Build Command" />
<x-forms.input placeholder="pnpm start" id="application.start_command" label="Start Command" /> <x-forms.input placeholder="pnpm start" id="application.start_command" label="Start Command" />
</div> </div>
<div class="flex flex-col gap-2 xl:flex-row"> @endif
<x-forms.input placeholder="/" id="application.base_directory" label="Base Directory"
helper="Directory to use as root. Useful for monorepos." /> <div class="flex flex-col gap-2 xl:flex-row">
<x-forms.input placeholder="/" id="application.base_directory" label="Base Directory"
helper="Directory to use as root. Useful for monorepos." />
@if ($application->could_set_build_commands())
@if ($application->settings->is_static) @if ($application->settings->is_static)
<x-forms.input placeholder="/dist" id="application.publish_directory" label="Publish Directory" <x-forms.input placeholder="/dist" id="application.publish_directory" label="Publish Directory"
required /> required />
@else @else
<x-forms.input placeholder="/" id="application.publish_directory" label="Publish Directory" /> <x-forms.input placeholder="/" id="application.publish_directory" label="Publish Directory" />
@endif @endif
</div> @endif
@endif </div>
@if ($application->dockerfile) @if ($application->dockerfile)
<x-forms.textarea label="Dockerfile" id="application.dockerfile" rows="6"> </x-forms.textarea> <x-forms.textarea label="Dockerfile" id="application.dockerfile" rows="6"> </x-forms.textarea>
@endif @endif

View File

@ -130,7 +130,7 @@
<li class="step step-secondary">Select a Server</li> <li class="step step-secondary">Select a Server</li>
<li class="step">Select a Destination</li> <li class="step">Select a Destination</li>
</ul> </ul>
<div class="flex flex-col justify-center gap-2 text-left xl:flex-row"> <div class="flex flex-col justify-center gap-2 text-left xl:flex-row xl:flex-wrap">
@forelse($servers as $server) @forelse($servers as $server)
<div class="box group" wire:click="setServer({{ $server }})"> <div class="box group" wire:click="setServer({{ $server }})">
<div class="flex flex-col mx-6"> <div class="flex flex-col mx-6">
@ -158,7 +158,7 @@
<li class="step step-secondary">Select a Server</li> <li class="step step-secondary">Select a Server</li>
<li class="step step-secondary">Select a Destination</li> <li class="step step-secondary">Select a Destination</li>
</ul> </ul>
<div class="flex flex-col justify-center gap-2 text-left xl:flex-row"> <div class="flex flex-col justify-center gap-2 text-left xl:flex-row xl:flex-wrap">
@foreach ($standaloneDockers as $standaloneDocker) @foreach ($standaloneDockers as $standaloneDocker)
<div class="box group" wire:click="setDestination('{{ $standaloneDocker->uuid }}')"> <div class="box group" wire:click="setDestination('{{ $standaloneDocker->uuid }}')">
<div class="flex flex-col mx-6"> <div class="flex flex-col mx-6">

View File

@ -0,0 +1,9 @@
<div>
@if ($private_keys->count() === 0)
<h1>Create Private Key</h1>
<div class="subtitle">You need to create a private key before you can create a server.</div>
<livewire:private-key.create from="server" />
@else
<livewire:server.new.by-ip :private_keys="$private_keys" :limit_reached="$limit_reached" />
@endif
</div>

View File

@ -0,0 +1,4 @@
<div>
<x-server.navbar :server="$server" :parameters="$parameters" />
<livewire:destination.show :server="$server" />
</div>

View File

@ -1,4 +1,4 @@
<div> <div x-init="$wire.validateServer(false)">
<x-modal yesOrNo modalId="deleteServer" modalTitle="Delete Server"> <x-modal yesOrNo modalId="deleteServer" modalTitle="Delete Server">
<x-slot:modalBody> <x-slot:modalBody>
<p>This server will be deleted. It is not reversible. <br>Please think again..</p> <p>This server will be deleted. It is not reversible. <br>Please think again..</p>
@ -25,6 +25,11 @@
@else @else
Server is reachable and validated. Server is reachable and validated.
@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='validateServer' isHighlighted>
Validate Server & Install Docker Engine
</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 />
@ -42,27 +47,12 @@
</div> </div>
</div> </div>
<div class="w-64"> <div class="w-64">
<x-forms.checkbox instantSave helper="If you are using Cloudflare Tunnels, enable this. It will proxy all ssh requests to your server through Cloudflare.<span class='text-warning'>Coolify does not install/setup Cloudflare (cloudflared) on your server.</span>" <x-forms.checkbox instantSave
helper="If you are using Cloudflare Tunnels, enable this. It will proxy all ssh requests to your server through Cloudflare.<span class='text-warning'>Coolify does not install/setup Cloudflare (cloudflared) on your server.</span>"
id="server.settings.is_cloudflare_tunnel" label="Cloudflare Tunnel" /> id="server.settings.is_cloudflare_tunnel" label="Cloudflare Tunnel" />
</div> </div>
</div> </div>
@if (!$server->settings->is_reachable)
<x-forms.button class="mt-8 mb-4 box" wire:click.prevent='validateServer'>
Validate Server
</x-forms.button>
@endif
@if ($server->settings->is_reachable && !$server->settings->is_usable && $server->id !== 0)
@if ($dockerInstallationStarted)
<x-forms.button class="mt-8 mb-4 box" wire:click.prevent='validateServer'>
Validate Server
</x-forms.button>
@else
<x-forms.button class="mt-8 mb-4 box" onclick="installDocker.showModal()"
wire:click.prevent='installDocker' isHighlighted>
Install Docker Engine 24.0
</x-forms.button>
@endif
@endif
@if ($server->isFunctional()) @if ($server->isFunctional())
<h3 class="py-4">Settings</h3> <h3 class="py-4">Settings</h3>
<x-forms.input id="cleanup_after_percentage" label="Disk Cleanup threshold (%)" required <x-forms.input id="cleanup_after_percentage" label="Disk Cleanup threshold (%)" required
@ -80,4 +70,11 @@
Delete Delete
</x-forms.button> </x-forms.button>
@endif @endif
<script>
Livewire.on('installDocker', () => {
console.log('asd');
installDocker.showModal();
})
</script>
</div> </div>

View File

@ -1,4 +1,4 @@
<x-layout> <div>
<x-server.navbar :server="$server" /> <x-server.navbar :server="$server" :parameters="$parameters" />
<livewire:server.show-private-key :server="$server" :privateKeys="$privateKeys" /> <livewire:server.show-private-key :server="$server" :privateKeys="$privateKeys" />
</x-layout> </div>

View File

@ -0,0 +1,4 @@
<div>
<x-server.navbar :server="$server" :parameters="$parameters" />
<livewire:server.proxy :server="$server" />
</div>

View File

@ -1,7 +1,7 @@
<div> <div>
<x-modal noSubmit modalId="installDocker"> <x-modal modalId="installDocker">
<x-slot:modalBody> <x-slot:modalBody>
<livewire:activity-monitor header="Installation Logs" /> <livewire:activity-monitor header="Docker Installation Logs" />
</x-slot:modalBody> </x-slot:modalBody>
<x-slot:modalSubmit> <x-slot:modalSubmit>
<x-forms.button onclick="installDocker.close()" type="submit"> <x-forms.button onclick="installDocker.close()" type="submit">
@ -9,6 +9,6 @@
</x-forms.button> </x-forms.button>
</x-slot:modalSubmit> </x-slot:modalSubmit>
</x-modal> </x-modal>
<x-server.navbar :server="$server" /> <x-server.navbar :server="$server" :parameters="$parameters" />
<livewire:server.form :server="$server" /> <livewire:server.form :server="$server" />
</div> </div>

View File

@ -1,3 +0,0 @@
<x-layout>
</x-layout>

View File

@ -1,4 +0,0 @@
<x-layout>
<x-server.navbar :server="$server" />
<livewire:destination.show :server="$server" />
</x-layout>

View File

@ -1,4 +0,0 @@
<x-layout>
<x-server.navbar :server="$server" />
<livewire:server.proxy :server="$server" />
</x-layout>

View File

@ -1,4 +0,0 @@
<x-layout>
<x-server.navbar :server="$server" />
<livewire:server.form :server="$server" />
</x-layout>

View File

@ -13,6 +13,10 @@
use App\Http\Livewire\Dashboard; use App\Http\Livewire\Dashboard;
use App\Http\Livewire\Project\Shared\Logs; use App\Http\Livewire\Project\Shared\Logs;
use App\Http\Livewire\Server\All; use App\Http\Livewire\Server\All;
use App\Http\Livewire\Server\Create;
use App\Http\Livewire\Server\Destination\Show as DestinationShow;
use App\Http\Livewire\Server\PrivateKey\Show as PrivateKeyShow;
use App\Http\Livewire\Server\Proxy\Show as ProxyShow;
use App\Http\Livewire\Server\Show; use App\Http\Livewire\Server\Show;
use App\Http\Livewire\Waitlist\Index as WaitlistIndex; use App\Http\Livewire\Waitlist\Index as WaitlistIndex;
use App\Models\GithubApp; use App\Models\GithubApp;
@ -102,18 +106,11 @@
Route::middleware(['auth'])->group(function () { Route::middleware(['auth'])->group(function () {
Route::get('/servers', All::class)->name('server.all'); Route::get('/servers', All::class)->name('server.all');
Route::get('/server/new', [ServerController::class, 'new_server'])->name('server.create'); Route::get('/server/new', Create::class)->name('server.create');
Route::get('/server/{server_uuid}', Show::class)->name('server.show'); Route::get('/server/{server_uuid}', Show::class)->name('server.show');
Route::get('/server/{server_uuid}/proxy', fn () => view('server.proxy', [ Route::get('/server/{server_uuid}/proxy', ProxyShow::class)->name('server.proxy');
'server' => Server::ownedByCurrentTeam(['name', 'proxy'])->whereUuid(request()->server_uuid)->firstOrFail(), Route::get('/server/{server_uuid}/private-key', PrivateKeyShow::class)->name('server.private-key');
]))->name('server.proxy'); Route::get('/server/{server_uuid}/destinations', DestinationShow::class)->name('server.destinations');
Route::get('/server/{server_uuid}/private-key', fn () => view('server.private-key', [
'server' => Server::ownedByCurrentTeam()->whereUuid(request()->server_uuid)->firstOrFail(),
'privateKeys' => PrivateKey::ownedByCurrentTeam()->get()->where('is_git_related', false),
]))->name('server.private-key');
Route::get('/server/{server_uuid}/destinations', fn () => view('server.destinations', [
'server' => Server::ownedByCurrentTeam(['name', 'proxy'])->whereUuid(request()->server_uuid)->firstOrFail()
]))->name('server.destinations');
}); });

View File

@ -16,7 +16,7 @@
}, },
"fider": { "fider": {
"documentation": "https://fider.io/docs", "documentation": "https://fider.io/docs",
"slogan": "A platform to collect and oragnize customer feedback.", "slogan": "A platform to collect and organize customer feedback.",
"compose": "c2VydmljZXM6CiAgZmlkZXI6CiAgICBpbWFnZTogZ2V0ZmlkZXIvZmlkZXI6c3RhYmxlCiAgICBlbnZpcm9ubWVudDoKICAgICAgQkFTRV9VUkw6ICRTRVJWSUNFX0ZRRE5fRklERVIKICAgICAgREFUQUJBU0VfVVJMOiBwb3N0Z3JlczovLyRTRVJWSUNFX1VTRVJfTVlTUUw6JFNFUlZJQ0VfUEFTU1dPUkRfTVlTUUxAZGF0YWJhc2U6NTQzMi9maWRlcj9zc2xtb2RlPWRpc2FibGUKICAgICAgSldUX1NFQ1JFVDogJFNFUlZJQ0VfUEFTU1dPUkRfNjRfRklERVIKICAgICAgRU1BSUxfTk9SRVBMWTogJHtFTUFJTF9OT1JFUExZOi1ub3JlcGx5QGV4YW1wbGUuY29tfQogICAgICBFTUFJTF9NQUlMR1VOX0FQSTogJEVNQUlMX01BSUxHVU5fQVBJCiAgICAgIEVNQUlMX01BSUxHVU5fRE9NQUlOOiAkRU1BSUxfTUFJTEdVTl9ET01BSU4KICAgICAgRU1BSUxfTUFJTEdVTl9SRUdJT046ICRFTUFJTF9NQUlMR1VOX1JFR0lPTgogICAgICBFTUFJTF9TTVRQX0hPU1Q6ICR7RU1BSUxfU01UUF9IT1NUOi1zbXRwLm1haWxndW4uY29tfQogICAgICBFTUFJTF9TTVRQX1BPUlQ6ICR7RU1BSUxfU01UUF9QT1JUOi01ODd9CiAgICAgIEVNQUlMX1NNVFBfVVNFUk5BTUU6ICR7RU1BSUxfU01UUF9VU0VSTkFNRTotcG9zdG1hc3RlckBtYWlsZ3VuLmNvbX0KICAgICAgRU1BSUxfU01UUF9QQVNTV09SRDogJEVNQUlMX1NNVFBfUEFTU1dPUkQKICAgICAgRU1BSUxfU01UUF9FTkFCTEVfU1RBUlRUTFM6ICRFTUFJTF9TTVRQX0VOQUJMRV9TVEFSVFRMUwogICAgICBFTUFJTF9BV1NTRVNfUkVHSU9OOiAkRU1BSUxfQVdTU0VTX1JFR0lPTgogICAgICBFTUFJTF9BV1NTRVNfQUNDRVNTX0tFWV9JRDogJEVNQUlMX0FXU1NFU19BQ0NFU1NfS0VZX0lECiAgICAgIEVNQUlMX0FXU1NFU19TRUNSRVRfQUNDRVNTX0tFWTogJEVNQUlMX0FXU1NFU19TRUNSRVRfQUNDRVNTX0tFWQogIGRhdGFiYXNlOgogICAgaW1hZ2U6IHBvc3RncmVzOjEyCiAgICB2b2x1bWVzOgogICAgICAtIHBnX2RhdGE6L3Zhci9saWIvcG9zdGdyZXNxbC9kYXRhCiAgICBlbnZpcm9ubWVudDoKICAgICAgUE9TVEdSRVNfVVNFUjogJFNFUlZJQ0VfVVNFUl9NWVNRTAogICAgICBQT1NUR1JFU19QQVNTV09SRDogJFNFUlZJQ0VfUEFTU1dPUkRfTVlTUUwKICAgICAgUE9TVEdSRVNfREI6ICR7UE9TVEdSRVNfREI6LWZpZGVyfQo=" "compose": "c2VydmljZXM6CiAgZmlkZXI6CiAgICBpbWFnZTogZ2V0ZmlkZXIvZmlkZXI6c3RhYmxlCiAgICBlbnZpcm9ubWVudDoKICAgICAgQkFTRV9VUkw6ICRTRVJWSUNFX0ZRRE5fRklERVIKICAgICAgREFUQUJBU0VfVVJMOiBwb3N0Z3JlczovLyRTRVJWSUNFX1VTRVJfTVlTUUw6JFNFUlZJQ0VfUEFTU1dPUkRfTVlTUUxAZGF0YWJhc2U6NTQzMi9maWRlcj9zc2xtb2RlPWRpc2FibGUKICAgICAgSldUX1NFQ1JFVDogJFNFUlZJQ0VfUEFTU1dPUkRfNjRfRklERVIKICAgICAgRU1BSUxfTk9SRVBMWTogJHtFTUFJTF9OT1JFUExZOi1ub3JlcGx5QGV4YW1wbGUuY29tfQogICAgICBFTUFJTF9NQUlMR1VOX0FQSTogJEVNQUlMX01BSUxHVU5fQVBJCiAgICAgIEVNQUlMX01BSUxHVU5fRE9NQUlOOiAkRU1BSUxfTUFJTEdVTl9ET01BSU4KICAgICAgRU1BSUxfTUFJTEdVTl9SRUdJT046ICRFTUFJTF9NQUlMR1VOX1JFR0lPTgogICAgICBFTUFJTF9TTVRQX0hPU1Q6ICR7RU1BSUxfU01UUF9IT1NUOi1zbXRwLm1haWxndW4uY29tfQogICAgICBFTUFJTF9TTVRQX1BPUlQ6ICR7RU1BSUxfU01UUF9QT1JUOi01ODd9CiAgICAgIEVNQUlMX1NNVFBfVVNFUk5BTUU6ICR7RU1BSUxfU01UUF9VU0VSTkFNRTotcG9zdG1hc3RlckBtYWlsZ3VuLmNvbX0KICAgICAgRU1BSUxfU01UUF9QQVNTV09SRDogJEVNQUlMX1NNVFBfUEFTU1dPUkQKICAgICAgRU1BSUxfU01UUF9FTkFCTEVfU1RBUlRUTFM6ICRFTUFJTF9TTVRQX0VOQUJMRV9TVEFSVFRMUwogICAgICBFTUFJTF9BV1NTRVNfUkVHSU9OOiAkRU1BSUxfQVdTU0VTX1JFR0lPTgogICAgICBFTUFJTF9BV1NTRVNfQUNDRVNTX0tFWV9JRDogJEVNQUlMX0FXU1NFU19BQ0NFU1NfS0VZX0lECiAgICAgIEVNQUlMX0FXU1NFU19TRUNSRVRfQUNDRVNTX0tFWTogJEVNQUlMX0FXU1NFU19TRUNSRVRfQUNDRVNTX0tFWQogIGRhdGFiYXNlOgogICAgaW1hZ2U6IHBvc3RncmVzOjEyCiAgICB2b2x1bWVzOgogICAgICAtIHBnX2RhdGE6L3Zhci9saWIvcG9zdGdyZXNxbC9kYXRhCiAgICBlbnZpcm9ubWVudDoKICAgICAgUE9TVEdSRVNfVVNFUjogJFNFUlZJQ0VfVVNFUl9NWVNRTAogICAgICBQT1NUR1JFU19QQVNTV09SRDogJFNFUlZJQ0VfUEFTU1dPUkRfTVlTUUwKICAgICAgUE9TVEdSRVNfREI6ICR7UE9TVEdSRVNfREI6LWZpZGVyfQo="
}, },
"ghost": { "ghost": {

View File

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