Merge pull request #1729 from coollabsio/next

v4.0.0-beta.213
This commit is contained in:
Andras Bacsai 2024-02-12 11:56:40 +01:00 committed by GitHub
commit 74c126c731
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
41 changed files with 487 additions and 323 deletions

View File

@ -198,7 +198,7 @@ Files:
}
$restart_command = [
"echo 'Stopping old Fluent Bit'",
"cd $config_path && docker rm -f coolify-log-drain || true",
"cd $config_path && docker compose down --remove-orphans || true",
"echo 'Starting Fluent Bit'",
"cd $config_path && docker compose up -d --remove-orphans",
];

View File

@ -618,6 +618,9 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
}
} else {
$this->dockerImageTag = str($this->commit)->substr(0, 128);
if ($this->application->docker_registry_image_tag) {
$this->dockerImageTag = $this->application->docker_registry_image_tag;
}
if ($this->application->docker_registry_image_name) {
$this->build_image_name = Str::lower("{$this->application->docker_registry_image_name}:{$this->dockerImageTag}-build");
$this->production_image_name = Str::lower("{$this->application->docker_registry_image_name}:{$this->dockerImageTag}");

View File

@ -11,6 +11,9 @@ class Index extends Component
public $users = [];
public function mount()
{
if (!isCloud()) {
return redirect()->route('dashboard');
}
if (auth()->user()->id !== 0 && session('adminToken') === null) {
return redirect()->route('dashboard');
}

View File

@ -3,6 +3,7 @@
namespace App\Livewire\Boarding;
use App\Actions\Server\InstallDocker;
use App\Enums\ProxyTypes;
use App\Models\PrivateKey;
use App\Models\Project;
use App\Models\Server;
@ -121,15 +122,16 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
}
$this->selectedExistingPrivateKey = $this->createdServer->privateKey->id;
$this->serverPublicKey = $this->createdServer->privateKey->publicKey();
$this->validateServer();
$this->installServer();
}
public function getProxyType()
{
$proxyTypeSet = $this->createdServer->proxy->type;
if (!$proxyTypeSet) {
$this->currentState = 'select-proxy';
return;
}
$this->selectProxy(ProxyTypes::TRAEFIK_V2->value);
// $proxyTypeSet = $this->createdServer->proxy->type;
// if (!$proxyTypeSet) {
// $this->currentState = 'select-proxy';
// return;
// }
$this->getProjects();
}
public function selectExistingPrivateKey()
@ -193,7 +195,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
$this->createdServer->settings->is_cloudflare_tunnel = $this->isCloudflareTunnel;
$this->createdServer->settings->save();
$this->createdServer->addInitialNetwork();
$this->validateServer();
$this->currentState = 'validate-server';
}
public function installServer()
{
@ -219,7 +221,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
$dockerVersion = instant_remote_process(["docker version|head -2|grep -i version| awk '{print $2}'"], $this->createdServer, true);
$dockerVersion = checkMinimumDockerEngineVersion($dockerVersion);
if (is_null($dockerVersion)) {
$this->currentState = 'install-docker';
$this->currentState = 'validate-server';
throw new \Exception('Docker not found or old version is installed.');
}
$this->createdServer->settings()->update([
@ -227,27 +229,10 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
]);
$this->getProxyType();
} catch (\Throwable $e) {
// $this->dockerInstallationStarted = false;
return handleError(error: $e, livewire: $this);
}
}
public function installDocker()
{
try {
$this->dockerInstallationStarted = true;
$activity = InstallDocker::run($this->createdServer);
$this->dispatch('installDocker');
$this->dispatch('activityMonitor', $activity->id);
} catch (\Throwable $e) {
$this->dockerInstallationStarted = false;
return handleError(error: $e, livewire: $this);
}
}
public function dockerInstalledOrSkipped()
{
$this->validateServer();
}
public function selectProxy(string|null $proxyType = null)
public function selectProxy(?string $proxyType = null)
{
if (!$proxyType) {
return $this->getProjects();

View File

@ -22,6 +22,7 @@ class Dashboard extends Component
}
public function cleanup_queue()
{
$this->dispatch('success', 'Cleanup started.');
Artisan::queue('app:init', [
'--cleanup-deployments' => 'true'
]);

View File

@ -56,15 +56,15 @@ class Heading extends Component
return;
}
if ($this->application->destination->server->isSwarm() && str($this->application->docker_registry_image_name)->isEmpty()) {
$this->dispatch('error', 'Failed to deploy', 'To deploy to a Swarm cluster you must set a Docker image name first.');
$this->dispatch('error', 'Failed to deploy.', 'To deploy to a Swarm cluster you must set a Docker image name first.');
return;
}
if (data_get($this->application, 'settings.is_build_server_enabled') && str($this->application->docker_registry_image_name)->isEmpty()) {
$this->dispatch('error', 'Failed to deploy', 'To use a build server, you must first set a Docker image.<br>More information here: <a target="_blank" class="underline" href="https://coolify.io/docs/server/build-server">documentation</a>');
$this->dispatch('error', 'Failed to deploy.', 'To use a build server, you must first set a Docker image.<br>More information here: <a target="_blank" class="underline" href="https://coolify.io/docs/server/build-server">documentation</a>');
return;
}
if ($this->application->additional_servers->count() > 0 && str($this->application->docker_registry_image_name)->isEmpty()) {
$this->dispatch('error', 'Failed to deploy', 'To deploy to more than one server, you must first set a Docker image.<br>More information here: <a target="_blank" class="underline" href="https://coolify.io/docs/server/build-server">documentation</a>');
$this->dispatch('error', 'Failed to deploy.', 'Before deploying to multiple servers, you must first set a Docker image in the General tab.<br>More information here: <a target="_blank" class="underline" href="https://coolify.io/docs/server/multiple-servers">documentation</a>');
return;
}
$this->setDeploymentUuid();
@ -103,7 +103,7 @@ class Heading extends Component
public function restart()
{
if ($this->application->additional_servers->count() > 0 && str($this->application->docker_registry_image_name)->isEmpty()) {
$this->dispatch('error', 'Failed to deploy', 'To deploy to more than one server, you must first set a Docker image.<br>More information here: <a target="_blank" class="underline" href="https://coolify.io/docs/server/build-server">documentation</a>');
$this->dispatch('error', 'Failed to deploy', 'Before deploying to multiple servers, you must first set a Docker image in the General tab.<br>More information here: <a target="_blank" class="underline" href="https://coolify.io/docs/server/multiple-servers">documentation</a>');
return;
}
$this->setDeploymentUuid();

View File

@ -27,12 +27,12 @@ class Index extends Component
$this->parameters = get_route_parameters();
$this->query = request()->query();
$this->service = Service::whereUuid($this->parameters['service_uuid'])->firstOrFail();
$service = $this->service->applications()->whereName($this->parameters['service_name'])->first();
$service = $this->service->applications()->whereUuid($this->parameters['stack_service_uuid'])->first();
if ($service) {
$this->serviceApplication = $service;
$this->serviceApplication->getFilesFromServer();
} else {
$this->serviceDatabase = $this->service->databases()->whereName($this->parameters['service_name'])->first();
$this->serviceDatabase = $this->service->databases()->whereUuid($this->parameters['stack_service_uuid'])->first();
$this->serviceDatabase->getFilesFromServer();
}
$this->s3s = currentTeam()->s3s;

View File

@ -37,10 +37,13 @@ class Destination extends Component
$this->networks = $this->networks->reject(function ($network) use ($all_networks) {
return $all_networks->pluck('id')->contains($network->id);
});
}
public function redeploy(int $network_id, int $server_id)
{
if ($this->resource->additional_servers->count() > 0 && str($this->resource->docker_registry_image_name)->isEmpty()) {
$this->dispatch('error', 'Failed to deploy.', 'Before deploying to multiple servers, you must first set a Docker image in the General tab.<br>More information here: <a target="_blank" class="underline" href="https://coolify.io/docs/server/multiple-servers">documentation</a>');
return;
}
$deployment_uuid = new Cuid2(7);
$server = Server::find($server_id);
$destination = StandaloneDocker::find($network_id);

View File

@ -12,7 +12,7 @@ class Status extends Component
public Server $server;
public bool $polling = false;
public int $numberOfPolls = 0;
protected $listeners = ['proxyStatusUpdated', 'startProxyPolling'];
protected $listeners = ['proxyStatusUpdated' => '$refresh', 'startProxyPolling'];
public function startProxyPolling()
{

View File

@ -2,6 +2,7 @@
namespace App\Livewire\Server;
use App\Actions\Proxy\StartProxy;
use App\Models\Server;
use Livewire\Component;
@ -14,18 +15,23 @@ class ValidateAndInstall extends Component
public $uptime = null;
public $supported_os_type = null;
public $docker_installed = null;
public $docker_compose_installed = null;
public $docker_version = null;
public $proxy_started = false;
public $error = null;
protected $listeners = ['validateServer' => 'init', 'validateDockerEngine', 'validateServerNow' => 'validateServer'];
public function init(bool $install = true)
{
$this->install = $install;
$this->uptime = null;
$this->supported_os_type = null;
$this->docker_installed = null;
$this->docker_version = null;
$this->docker_compose_installed = null;
$this->proxy_started = null;
$this->error = null;
$this->number_of_tries = 0;
$this->dispatch('validateServerNow');
@ -43,6 +49,11 @@ class ValidateAndInstall extends Component
if ($swarmInstalled) {
$this->dispatch('success', 'Docker Swarm is initiated.');
}
} else {
$proxy = StartProxy::run($this->server);
if ($proxy) {
$this->proxy_started = true;
}
}
} catch (\Throwable $e) {
return handleError($e, $this);
@ -67,9 +78,9 @@ class ValidateAndInstall extends Component
public function validateDockerEngine()
{
$this->docker_installed = $this->server->validateDockerEngine();
if (!$this->docker_installed) {
$this->docker_compose_installed = $this->server->validateDockerCompose();
if (!$this->docker_installed || !$this->docker_compose_installed) {
if ($this->install) {
ray($this->number_of_tries, $this->max_tries);
if ($this->number_of_tries == $this->max_tries) {
$this->error = 'Docker Engine could not be installed. Please install Docker manually before continuing: <a target="_blank" class="underline" href="https://docs.docker.com/engine/install/#server">documentation</a>.';
return;

View File

@ -200,9 +200,6 @@ class Application extends BaseModel
set: fn ($value) => $value === "" ? null : $value,
);
}
// Normal Deployments
public function portsMappingsArray(): Attribute
{
return Attribute::make(
@ -214,7 +211,7 @@ class Application extends BaseModel
}
public function realStatus()
{
return $this->getRawOriginal('status');
return $this->getRawOriginal('status');
}
public function status(): Attribute
{

View File

@ -443,6 +443,21 @@ class Server extends BaseModel
$this->validateCoolifyNetwork(isSwarm: false, isBuildServer: $this->settings->is_build_server);
return true;
}
public function validateDockerCompose($throwError = false)
{
$dockerCompose = instant_remote_process(["docker compose version"], $this, false);
if (is_null($dockerCompose)) {
$this->settings->is_usable = false;
$this->settings->save();
if ($throwError) {
throw new \Exception('Server is not usable. Docker Compose is not installed.');
}
return false;
}
$this->settings->is_usable = true;
$this->settings->save();
return true;
}
public function validateDockerSwarm()
{
$swarmStatus = instant_remote_process(["docker info|grep -i swarm"], $this, false);

View File

@ -13,6 +13,11 @@ const VALID_CRON_STRINGS = [
const RESTART_MODE = 'unless-stopped';
const DATABASE_DOCKER_IMAGES = [
'bitnami/mariadb',
'bitnami/mongodb',
'bitnami/mysql',
'bitnami/postgresql',
'bitnami/redis',
'mysql',
'mariadb',
'postgres',

View File

@ -349,18 +349,8 @@ function convert_docker_run_to_compose(?string $custom_docker_run_options = null
foreach ($matches as $match) {
$option = $match[1];
$value = isset($match[2]) && $match[2] !== '' ? $match[2] : true;
if ($list_options->contains($option)) {
$value = explode(',', $value);
}
if (array_key_exists($option, $options)) {
if (is_array($options[$option])) {
$options[$option][] = $value;
} else {
$options[$option] = [$options[$option], $value];
}
} else {
$options[$option] = $value;
}
$options[$option][] = $value;
$options[$option] = array_unique($options[$option]);
}
$options = collect($options);
// Easily get mappings from https://github.com/composerize/composerize/blob/master/packages/composerize/src/mappings.js
@ -370,7 +360,7 @@ function convert_docker_run_to_compose(?string $custom_docker_run_options = null
}
if ($option === '--ulimit') {
$ulimits = collect([]);
collect($value)->map(function ($ulimit) use ($ulimits){
collect($value)->map(function ($ulimit) use ($ulimits) {
$ulimit = explode('=', $ulimit);
$type = $ulimit[0];
$limits = explode(':', $ulimit[1]);
@ -381,7 +371,6 @@ function convert_docker_run_to_compose(?string $custom_docker_run_options = null
'soft' => $soft_limit,
'hard' => $hard_limit
]);
} else {
$soft_limit = $ulimit[1];
$ulimits->put($type, [

View File

@ -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.212',
'release' => '4.0.0-beta.213',
// When left empty or `null` the Laravel environment will be used
'environment' => config('app.env'),

View File

@ -1,3 +1,3 @@
<?php
return '4.0.0-beta.212';
return '4.0.0-beta.213';

View File

@ -28,8 +28,8 @@
<a class="text-xs text-white rounded-none hover:no-underline hover:bg-coollabs hover:text-white"
target="_blank" href="{{ getFqdnWithoutPort($domain) }}">
<svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6" viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
stroke-linejoin="round">
stroke-width="1.5" stroke="currentColor" fill="none"
stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M9 15l6 -6" />
<path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" />
@ -84,7 +84,7 @@
@endif
@if (data_get($application, 'ports_mappings_array'))
@foreach ($application->ports_mappings_array as $port)
@if (isDev())
@if ($application->destination->server->id === 0)
<li>
<a class="text-xs text-white rounded-none hover:no-underline hover:bg-coollabs hover:text-white"
target="_blank" href="http://localhost:{{ explode(':', $port)[0] }}">
@ -114,9 +114,29 @@
<path
d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" />
</svg>
Port {{ $port }}
{{ $application->destination->server->ip }}:{{ explode(':', $port)[0] }}
</a>
</li>
@if (count($application->additional_servers) > 0)
@foreach ($application->additional_servers as $server)
<li>
<a class="text-xs text-white rounded-none hover:no-underline hover:bg-coollabs hover:text-white"
target="_blank"
href="http://{{ $server->ip }}:{{ explode(':', $port)[0] }}">
<svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6" 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 d="M9 15l6 -6" />
<path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" />
<path
d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" />
</svg>
{{ $server->ip }}:{{ explode(':', $port)[0] }}
</a>
</li>
@endforeach
@endif
@endif
@endforeach
@endif

View File

@ -40,109 +40,139 @@
</svg>
</a>
</li>
<details x-data="{ open: false }" class="dropdown dropdown-right" x-bind:open="open">
<summary class="bg-transparent border-none btn hover:bg-transparent no-animation"
x-on:click.prevent="open = !open" x-on:click.away="open = false"> <svg class="icon"
viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg">
<path fill="currentColor"
d="M224 128a8 8 0 0 1-8 8h-80v80a8 8 0 0 1-16 0v-80H40a8 8 0 0 1 0-16h80V40a8 8 0 0 1 16 0v80h80a8 8 0 0 1 8 8" />
</svg></summary>
<ul tabindex="0" class="w-64 p-2 border border-coolgray-200 dropdown-content menu bg-coolgray-100 ">
<li title="Tags" class="border-transparent hover:bg-coolgray-200 ">
<a class=" hover:bg-transparent hover:no-underline" href="{{ route('tags.index') }}">
<svg class="icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
stroke-width="2">
<path
d="M3 8v4.172a2 2 0 0 0 .586 1.414l5.71 5.71a2.41 2.41 0 0 0 3.408 0l3.592-3.592a2.41 2.41 0 0 0 0-3.408l-5.71-5.71A2 2 0 0 0 9.172 6H5a2 2 0 0 0-2 2" />
<path d="m18 19l1.592-1.592a4.82 4.82 0 0 0 0-6.816L15 6m-8 4h-.01" />
</g>
</svg>
Tags
</a>
</li>
<li title="Command Center" class="hover:bg-coolgray-200">
<a class="hover:bg-transparent hover:no-underline" href="{{ route('command-center') }}">
<svg xmlns="http://www.w3.org/2000/svg"
class="{{ request()->is('command-center') ? ' icon' : 'icon' }}" viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M5 7l5 5l-5 5" />
<path d="M12 19l7 0" />
</svg>
Command Center
</a>
</li>
<li title="Source" class="hover:bg-coolgray-200">
<a class="hover:bg-transparent hover:no-underline" href="{{ route('source.all') }}">
<svg class="icon" viewBox="0 0 15 15" xmlns="http://www.w3.org/2000/svg">
<path fill="currentColor"
d="m6.793 1.207l.353.354l-.353-.354ZM1.207 6.793l-.353-.354l.353.354Zm0 1.414l.354-.353l-.354.353Zm5.586 5.586l-.354.353l.354-.353Zm1.414 0l-.353-.354l.353.354Zm5.586-5.586l.353.354l-.353-.354Zm0-1.414l-.354.353l.354-.353ZM8.207 1.207l.354-.353l-.354.353ZM6.44.854L.854 6.439l.707.707l5.585-5.585L6.44.854ZM.854 8.56l5.585 5.585l.707-.707l-5.585-5.585l-.707.707Zm7.707 5.585l5.585-5.585l-.707-.707l-5.585 5.585l.707.707Zm5.585-7.707L8.561.854l-.707.707l5.585 5.585l.707-.707Zm0 2.122a1.5 1.5 0 0 0 0-2.122l-.707.707a.5.5 0 0 1 0 .708l.707.707ZM6.44 14.146a1.5 1.5 0 0 0 2.122 0l-.707-.707a.5.5 0 0 1-.708 0l-.707.707ZM.854 6.44a1.5 1.5 0 0 0 0 2.122l.707-.707a.5.5 0 0 1 0-.708L.854 6.44Zm6.292-4.878a.5.5 0 0 1 .708 0L8.56.854a1.5 1.5 0 0 0-2.122 0l.707.707Zm-2 1.293l1 1l.708-.708l-1-1l-.708.708ZM7.5 5a.5.5 0 0 1-.5-.5H6A1.5 1.5 0 0 0 7.5 6V5Zm.5-.5a.5.5 0 0 1-.5.5v1A1.5 1.5 0 0 0 9 4.5H8ZM7.5 4a.5.5 0 0 1 .5.5h1A1.5 1.5 0 0 0 7.5 3v1Zm0-1A1.5 1.5 0 0 0 6 4.5h1a.5.5 0 0 1 .5-.5V3Zm.646 2.854l1.5 1.5l.707-.708l-1.5-1.5l-.707.708ZM10.5 8a.5.5 0 0 1-.5-.5H9A1.5 1.5 0 0 0 10.5 9V8Zm.5-.5a.5.5 0 0 1-.5.5v1A1.5 1.5 0 0 0 12 7.5h-1Zm-.5-.5a.5.5 0 0 1 .5.5h1A1.5 1.5 0 0 0 10.5 6v1Zm0-1A1.5 1.5 0 0 0 9 7.5h1a.5.5 0 0 1 .5-.5V6ZM7 5.5v4h1v-4H7Zm.5 5.5a.5.5 0 0 1-.5-.5H6A1.5 1.5 0 0 0 7.5 12v-1Zm.5-.5a.5.5 0 0 1-.5.5v1A1.5 1.5 0 0 0 9 10.5H8Zm-.5-.5a.5.5 0 0 1 .5.5h1A1.5 1.5 0 0 0 7.5 9v1Zm0-1A1.5 1.5 0 0 0 6 10.5h1a.5.5 0 0 1 .5-.5V9Z" />
</svg>
Sources
</a>
</li>
<li title="Security" class="hover:bg-coolgray-200">
<a class="hover:bg-transparent hover:no-underline" href="{{ route('security.private-key.index') }}">
<svg class="icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
stroke-width="2"
d="m16.555 3.843l3.602 3.602a2.877 2.877 0 0 1 0 4.069l-2.643 2.643a2.877 2.877 0 0 1-4.069 0l-.301-.301l-6.558 6.558a2 2 0 0 1-1.239.578L5.172 21H4a1 1 0 0 1-.993-.883L3 20v-1.172a2 2 0 0 1 .467-1.284l.119-.13L4 17h2v-2h2v-2l2.144-2.144l-.301-.301a2.877 2.877 0 0 1 0-4.069l2.643-2.643a2.877 2.877 0 0 1 4.069 0zM15 9h.01" />
</svg>
Security
</a>
</li>
<li title="Profile" class="hover:bg-coolgray-200">
<a class="hover:bg-transparent hover:no-underline" href="{{ route('profile') }}">
<svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M12 12m-9 0a9 9 0 1 0 18 0a9 9 0 1 0 -18 0" />
<path d="M12 10m-3 0a3 3 0 1 0 6 0a3 3 0 1 0 -6 0" />
<path d="M6.168 18.849a4 4 0 0 1 3.832 -2.849h4a4 4 0 0 1 3.834 2.855" />
</svg>
Profile
</a>
</li>
<li title="Teams" class="hover:bg-coolgray-200">
<a class="hover:bg-transparent hover:no-underline" href="{{ route('team.index') }}">
<svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M10 13a2 2 0 1 0 4 0a2 2 0 0 0 -4 0" />
<path d="M8 21v-1a2 2 0 0 1 2 -2h4a2 2 0 0 1 2 2v1" />
<path d="M15 5a2 2 0 1 0 4 0a2 2 0 0 0 -4 0" />
<path d="M17 10h2a2 2 0 0 1 2 2v1" />
<path d="M5 5a2 2 0 1 0 4 0a2 2 0 0 0 -4 0" />
<path d="M3 13v-1a2 2 0 0 1 2 -2h2" />
</svg>
Teams
</a>
</li>
@if (isInstanceAdmin())
<li title="Settings" class="hover:bg-coolgray-200">
<a class="hover:bg-transparent hover:no-underline" href="/settings">
<div class="inline-block text-left " x-data="{ open: false }">
<div>
<button x-on:click.prevent="open = !open" x-on:click.away="open = false" type="button"
class="py-4 mx-4" id="menu-button" aria-expanded="true" aria-haspopup="true">
<svg class="icon" viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg">
<path fill="currentColor"
d="M224 128a8 8 0 0 1-8 8h-80v80a8 8 0 0 1-16 0v-80H40a8 8 0 0 1 0-16h80V40a8 8 0 0 1 16 0v80h80a8 8 0 0 1 8 8" />
</svg>
</button>
</div>
<div x-show="open" x-cloak
class="absolute left-0 z-10 w-56 mx-4 mt-2 origin-top-right rounded shadow-lg bg-coolgray-100 ring-1 ring-black ring-opacity-5 focus:outline-none"
role="menu" aria-orientation="vertical" aria-labelledby="menu-button" tabindex="-1">
<div class="py-1" role="none">
<li title="Tags" class="border-transparent hover:bg-coolgray-200 ">
<a class=" hover:bg-transparent hover:no-underline" href="{{ route('tags.index') }}">
<svg class="{{ request()->is('tags*') ? 'text-warning icon' : 'icon' }}"viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg">
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
stroke-width="2">
<path
d="M3 8v4.172a2 2 0 0 0 .586 1.414l5.71 5.71a2.41 2.41 0 0 0 3.408 0l3.592-3.592a2.41 2.41 0 0 0 0-3.408l-5.71-5.71A2 2 0 0 0 9.172 6H5a2 2 0 0 0-2 2" />
<path d="m18 19l1.592-1.592a4.82 4.82 0 0 0 0-6.816L15 6m-8 4h-.01" />
</g>
</svg>
Tags
</a>
</li>
<li title="Command Center" class="hover:bg-coolgray-200">
<a class="hover:bg-transparent hover:no-underline" href="{{ route('command-center') }}">
<svg xmlns="http://www.w3.org/2000/svg"
class="{{ request()->is('settings*') ? 'text-warning icon' : 'icon' }}"
class="{{ request()->is('command-center*') ? 'text-warning icon' : 'icon' }}"
viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none"
stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path
d="M10.325 4.317c.426 -1.756 2.924 -1.756 3.35 0a1.724 1.724 0 0 0 2.573 1.066c1.543 -.94 3.31 .826 2.37 2.37a1.724 1.724 0 0 0 1.065 2.572c1.756 .426 1.756 2.924 0 3.35a1.724 1.724 0 0 0 -1.066 2.573c.94 1.543 -.826 3.31 -2.37 2.37a1.724 1.724 0 0 0 -2.572 1.065c-.426 1.756 -2.924 1.756 -3.35 0a1.724 1.724 0 0 0 -2.573 -1.066c-1.543 .94 -3.31 -.826 -2.37 -2.37a1.724 1.724 0 0 0 -1.065 -2.572c-1.756 -.426 -1.756 -2.924 0 -3.35a1.724 1.724 0 0 0 1.066 -2.573c-.94 -1.543 .826 -3.31 2.37 -2.37c1 .608 2.296 .07 2.572 -1.065z" />
<path d="M9 12a3 3 0 1 0 6 0a3 3 0 0 0 -6 0" />
<path d="M5 7l5 5l-5 5" />
<path d="M12 19l7 0" />
</svg>
Settings
Command Center
</a>
</li>
@endif
<li title="Source" class="hover:bg-coolgray-200">
<a class="hover:bg-transparent hover:no-underline" href="{{ route('source.all') }}">
<svg class="{{ request()->is('source*') ? 'text-warning icon' : 'icon' }}"
viewBox="0 0 15 15" xmlns="http://www.w3.org/2000/svg">
<path fill="currentColor"
d="m6.793 1.207l.353.354l-.353-.354ZM1.207 6.793l-.353-.354l.353.354Zm0 1.414l.354-.353l-.354.353Zm5.586 5.586l-.354.353l.354-.353Zm1.414 0l-.353-.354l.353.354Zm5.586-5.586l.353.354l-.353-.354Zm0-1.414l-.354.353l.354-.353ZM8.207 1.207l.354-.353l-.354.353ZM6.44.854L.854 6.439l.707.707l5.585-5.585L6.44.854ZM.854 8.56l5.585 5.585l.707-.707l-5.585-5.585l-.707.707Zm7.707 5.585l5.585-5.585l-.707-.707l-5.585 5.585l.707.707Zm5.585-7.707L8.561.854l-.707.707l5.585 5.585l.707-.707Zm0 2.122a1.5 1.5 0 0 0 0-2.122l-.707.707a.5.5 0 0 1 0 .708l.707.707ZM6.44 14.146a1.5 1.5 0 0 0 2.122 0l-.707-.707a.5.5 0 0 1-.708 0l-.707.707ZM.854 6.44a1.5 1.5 0 0 0 0 2.122l.707-.707a.5.5 0 0 1 0-.708L.854 6.44Zm6.292-4.878a.5.5 0 0 1 .708 0L8.56.854a1.5 1.5 0 0 0-2.122 0l.707.707Zm-2 1.293l1 1l.708-.708l-1-1l-.708.708ZM7.5 5a.5.5 0 0 1-.5-.5H6A1.5 1.5 0 0 0 7.5 6V5Zm.5-.5a.5.5 0 0 1-.5.5v1A1.5 1.5 0 0 0 9 4.5H8ZM7.5 4a.5.5 0 0 1 .5.5h1A1.5 1.5 0 0 0 7.5 3v1Zm0-1A1.5 1.5 0 0 0 6 4.5h1a.5.5 0 0 1 .5-.5V3Zm.646 2.854l1.5 1.5l.707-.708l-1.5-1.5l-.707.708ZM10.5 8a.5.5 0 0 1-.5-.5H9A1.5 1.5 0 0 0 10.5 9V8Zm.5-.5a.5.5 0 0 1-.5.5v1A1.5 1.5 0 0 0 12 7.5h-1Zm-.5-.5a.5.5 0 0 1 .5.5h1A1.5 1.5 0 0 0 10.5 6v1Zm0-1A1.5 1.5 0 0 0 9 7.5h1a.5.5 0 0 1 .5-.5V6ZM7 5.5v4h1v-4H7Zm.5 5.5a.5.5 0 0 1-.5-.5H6A1.5 1.5 0 0 0 7.5 12v-1Zm.5-.5a.5.5 0 0 1-.5.5v1A1.5 1.5 0 0 0 9 10.5H8Zm-.5-.5a.5.5 0 0 1 .5.5h1A1.5 1.5 0 0 0 7.5 9v1Zm0-1A1.5 1.5 0 0 0 6 10.5h1a.5.5 0 0 1 .5-.5V9Z" />
</svg>
Sources
</a>
</li>
<li title="Security" class="hover:bg-coolgray-200">
<a class="hover:bg-transparent hover:no-underline"
href="{{ route('security.private-key.index') }}">
<svg class="{{ request()->is('security*') ? 'text-warning icon' : 'icon' }}"
viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path fill="none" stroke="currentColor" stroke-linecap="round"
stroke-linejoin="round" stroke-width="2"
d="m16.555 3.843l3.602 3.602a2.877 2.877 0 0 1 0 4.069l-2.643 2.643a2.877 2.877 0 0 1-4.069 0l-.301-.301l-6.558 6.558a2 2 0 0 1-1.239.578L5.172 21H4a1 1 0 0 1-.993-.883L3 20v-1.172a2 2 0 0 1 .467-1.284l.119-.13L4 17h2v-2h2v-2l2.144-2.144l-.301-.301a2.877 2.877 0 0 1 0-4.069l2.643-2.643a2.877 2.877 0 0 1 4.069 0zM15 9h.01" />
</svg>
Security
</a>
</li>
<li title="Profile" class="hover:bg-coolgray-200">
<a class="hover:bg-transparent hover:no-underline" href="{{ route('profile') }}">
<svg xmlns="http://www.w3.org/2000/svg"
class="{{ request()->is('profile*') ? 'text-warning icon' : 'icon' }}"
viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none"
stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M12 12m-9 0a9 9 0 1 0 18 0a9 9 0 1 0 -18 0" />
<path d="M12 10m-3 0a3 3 0 1 0 6 0a3 3 0 1 0 -6 0" />
<path d="M6.168 18.849a4 4 0 0 1 3.832 -2.849h4a4 4 0 0 1 3.834 2.855" />
</svg>
Profile
</a>
</li>
<li title="Teams" class="hover:bg-coolgray-200">
<a class="hover:bg-transparent hover:no-underline" href="{{ route('team.index') }}">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"
class="{{ request()->is('team*') ? 'text-warning icon' : 'icon' }}"
stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M10 13a2 2 0 1 0 4 0a2 2 0 0 0 -4 0" />
<path d="M8 21v-1a2 2 0 0 1 2 -2h4a2 2 0 0 1 2 2v1" />
<path d="M15 5a2 2 0 1 0 4 0a2 2 0 0 0 -4 0" />
<path d="M17 10h2a2 2 0 0 1 2 2v1" />
<path d="M5 5a2 2 0 1 0 4 0a2 2 0 0 0 -4 0" />
<path d="M3 13v-1a2 2 0 0 1 2 -2h2" />
</svg>
Teams @if (isCloud())
/ Subscription
@endif
</a>
</li>
@if (isInstanceAdmin())
<li title="Settings" class="hover:bg-coolgray-200">
<a class="hover:bg-transparent hover:no-underline" href="/settings">
<svg xmlns="http://www.w3.org/2000/svg"
class="{{ request()->is('settings*') ? 'text-warning icon' : 'icon' }}"
viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none"
stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path
d="M10.325 4.317c.426 -1.756 2.924 -1.756 3.35 0a1.724 1.724 0 0 0 2.573 1.066c1.543 -.94 3.31 .826 2.37 2.37a1.724 1.724 0 0 0 1.065 2.572c1.756 .426 1.756 2.924 0 3.35a1.724 1.724 0 0 0 -1.066 2.573c.94 1.543 -.826 3.31 -2.37 2.37a1.724 1.724 0 0 0 -2.572 1.065c-.426 1.756 -2.924 1.756 -3.35 0a1.724 1.724 0 0 0 -2.573 -1.066c-1.543 .94 -3.31 -.826 -2.37 -2.37a1.724 1.724 0 0 0 -1.065 -2.572c-1.756 -.426 -1.756 -2.924 0 -3.35a1.724 1.724 0 0 0 1.066 -2.573c-.94 -1.543 .826 -3.31 2.37 -2.37c1 .608 2.296 .07 2.572 -1.065z" />
<path d="M9 12a3 3 0 1 0 6 0a3 3 0 0 0 -6 0" />
</svg>
Settings
</a>
</li>
@endif
<li title="Boarding" class="hover:bg-coolgray-200">
<a class="hover:bg-transparent hover:no-underline" href="{{ route('boarding') }}">
<svg class="{{ request()->is('boarding*') ? 'text-warning icon' : 'icon' }}"
viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg">
<path fill="currentColor"
d="M224 128a8 8 0 0 1-8 8h-88a8 8 0 0 1 0-16h88a8 8 0 0 1 8 8m-96-56h88a8 8 0 0 0 0-16h-88a8 8 0 0 0 0 16m88 112h-88a8 8 0 0 0 0 16h88a8 8 0 0 0 0-16M82.34 42.34L56 68.69L45.66 58.34a8 8 0 0 0-11.32 11.32l16 16a8 8 0 0 0 11.32 0l32-32a8 8 0 0 0-11.32-11.32m0 64L56 132.69l-10.34-10.35a8 8 0 0 0-11.32 11.32l16 16a8 8 0 0 0 11.32 0l32-32a8 8 0 0 0-11.32-11.32m0 64L56 196.69l-10.34-10.35a8 8 0 0 0-11.32 11.32l16 16a8 8 0 0 0 11.32 0l32-32a8 8 0 0 0-11.32-11.32" />
</svg>
Boarding
</a>
</li>
</div>
</div>
</div>
<details class="dropdown dropdown-right" x-bind:open="open">
<summary class="bg-transparent border-none btn hover:bg-transparent no-animation"> </summary>
<ul tabindex="0" class="w-64 p-2 border border-coolgray-200 dropdown-content menu bg-coolgray-100 ">
</ul>
</details>
@if (isCloud())
@if (isCloud() && isInstanceAdmin())
<li title="Admin">
<a class="hover:bg-transparent" href="/admin">
<svg class="text-pink-600 icon" viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg">
@ -180,11 +210,12 @@
</div>
</li>
<form action="/logout" method="POST" class="hover:bg-transparent">
<li title="Logout" class="mb-6 hover:transparent">
<li title="Logout" class="mb-6 hover:transparent">
@csrf
<button type="submit" class="rounded-none hover:text-white hover:bg-transparent">
<svg class="icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path fill="currentColor" d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2a9.985 9.985 0 0 1 8 4h-2.71a8 8 0 1 0 .001 12h2.71A9.985 9.985 0 0 1 12 22m7-6v-3h-8v-2h8V8l5 4z"/>
<path fill="currentColor"
d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2a9.985 9.985 0 0 1 8 4h-2.71a8 8 0 1 0 .001 12h2.71A9.985 9.985 0 0 1 12 22m7-6v-3h-8v-2h8V8l5 4z" />
</svg>
</button>
</li>

View File

@ -1,4 +1,4 @@
@props(['closeWithX' => 'false', 'fullScreen' => 'false'])
@props(['closeWithX' => false, 'fullScreen' => false])
<div x-data="{
slideOverOpen: false
}" class="relative w-auto h-auto">
@ -19,7 +19,7 @@
x-transition:leave-start="translate-x-0" x-transition:leave-end="translate-x-full"
@class([
'max-w-md w-screen' => !$fullScreen,
'max-w-7xl w-screen' => $fullScreen,
'max-w-4xl w-screen' => $fullScreen,
])>
<div
class="flex flex-col h-full py-6 overflow-hidden border-l shadow-lg bg-base-100 border-neutral-800">

View File

@ -7,7 +7,7 @@
<div class="pl-2 pr-1 text-xs font-bold tracking-widerr text-warning">
{{ str($status)->before(':')->headline() }}
</div>
@if (!str($status)->startsWith('Proxy'))
@if (!str($status)->startsWith('Proxy') && !str($status)->contains('('))
<div class="text-xs text-warning">({{ str($status)->after(':') }})</div>
@endif
</div>

View File

@ -7,7 +7,7 @@
<div class="pl-2 pr-1 text-xs font-bold tracking-wider text-success">
{{ str($status)->before(':')->headline() }}
</div>
@if (!str($status)->startsWith('Proxy'))
@if (!str($status)->startsWith('Proxy') && !str($status)->contains('('))
<div class="text-xs text-success">({{ str($status)->after(':') }})</div>
@endif
</div>

View File

@ -121,18 +121,24 @@
There are already servers available for your Team. Do you want to use one of them?
</x-slot:question>
<x-slot:actions>
<x-forms.button class="justify-center w-64 box" wire:click="createNewServer">No (create one for me)
</x-forms.button>
<div>
<form wire:submit='selectExistingServer' class="flex flex-col w-full gap-4 lg:w-96">
<x-forms.select label="Existing servers" class="w-96" id='selectedExistingServer'>
@foreach ($servers as $server)
<option wire:key="{{ $loop->index }}" value="{{ $server->id }}">
{{ $server->name }}</option>
@endforeach
</x-forms.select>
<x-forms.button type="submit">Use this Server</x-forms.button>
</form>
<div class="flex flex-col gap-4">
<div>
<x-forms.button class="justify-center w-64 box" wire:click="createNewServer">No (create one
for
me)
</x-forms.button>
</div>
<div>
<form wire:submit='selectExistingServer' class="flex flex-col w-full gap-4 lg:w-96">
<x-forms.select label="Existing servers" class="w-96" id='selectedExistingServer'>
@foreach ($servers as $server)
<option wire:key="{{ $loop->index }}" value="{{ $server->id }}">
{{ $server->name }}</option>
@endforeach
</x-forms.select>
<x-forms.button type="submit">Use this Server</x-forms.button>
</form>
</div>
</div>
@if (!$serverReachable)
This server is not reachable with the following public key.
@ -216,7 +222,7 @@
helper="If you are using Cloudflare Tunnels, enable this. It will proxy all ssh requests to your server through Cloudflare.<br><span class='text-warning'>Coolify does not install/setup Cloudflare (cloudflared) on your server.</span>"
id="isCloudflareTunnel" label="Cloudflare Tunnel" />
</div>
<x-forms.button type="submit">Check Connection</x-forms.button>
<x-forms.button type="submit">Continue</x-forms.button>
</form>
</x-slot:actions>
<x-slot:explanation>
@ -227,14 +233,15 @@
@endif
</div>
<div>
@if ($currentState === 'install-docker')
<x-boarding-step title="Install Docker">
@if ($currentState === 'validate-server')
<x-boarding-step title="Validate & Configure Server">
<x-slot:question>
Could not find Docker Engine on your server. Do you want me to install it for you?
I need to validate your server (connection, Docker Engine, etc) and configure if something is
missing for me. Are you okay with this?
</x-slot:question>
<x-slot:actions>
<x-slide-over closeWithX fullScreen>
<x-slot:title>Configuring Server</x-slot:title>
<x-slot:title>Validating & Configuring</x-slot:title>
<x-slot:content>
<livewire:server.validate-and-install :server="$this->createdServer" />
</x-slot:content>
@ -254,7 +261,7 @@
</x-boarding-step>
@endif
</div>
<div>
{{-- <div>
@if ($currentState === 'select-proxy')
<x-boarding-step title="Select a Proxy">
<x-slot:question>
@ -281,7 +288,7 @@
</x-slot:explanation>
</x-boarding-step>
@endif
</div>
</div> --}}
<div>
@if ($currentState === 'create-project')
<x-boarding-step title="Project">

View File

@ -1,5 +1,9 @@
<div>
<livewire:project.application.preview.form :application="$application" />
@if (count($application->additional_servers) > 0)
<div class="pb-4">Previews will be deployed on <span
class="text-warning">{{ $application->destination->server->name }}</span>.</div>
@endif
<div>
@if ($application->is_github_based())
<div class="flex items-center gap-2">
@ -51,7 +55,7 @@
</div>
@if ($application->previews->count() > 0)
<div class="pb-4">Previews</div>
<div class="flex gap-6 ">
<div class="flex flex-wrap gap-6">
@foreach ($application->previews as $preview)
<div class="flex flex-col p-4 bg-coolgray-200">
<div class="flex gap-2">PR #{{ data_get($preview, 'pull_request_id') }} |

View File

@ -213,9 +213,9 @@
@endif --}}
<div class="flex flex-col justify-center gap-4 text-left xl:flex-row xl:flex-wrap">
@forelse($servers as $server)
<div class="box group" wire:click="setServer({{ $server }})">
<div class="w-64 box group" wire:click="setServer({{ $server }})">
<div class="flex flex-col mx-6">
<div class="group-hover:text-white">
<div class="font-bold group-hover:text-white">
{{ $server->name }}
</div>
<div class="text-xs group-hover:text-white">
@ -263,13 +263,13 @@
Standalone Docker <span class="text-xs">({{ $standaloneDocker->name }})</span>
</div>
<div class="text-xs group-hover:text-white">
network: {{ $standaloneDocker->network }}</div>
Network: {{ $standaloneDocker->network }}</div>
</div>
</div>
@endforeach
@endif
<a href="{{ route('destination.new', ['server_id' => $server_id]) }}"
class="items-center justify-center pb-10 text-center box-without-bg group bg-coollabs hover:bg-coollabs-100">
class="items-center justify-center text-center box-without-bg group bg-coollabs hover:bg-coollabs-100">
<div class="flex flex-col mx-6 ">
<div class="font-bold text-white">
+ Add New

View File

@ -48,25 +48,27 @@
<x-forms.input placeholder="Search for name, fqdn..." class="w-full" x-model="search" />
<div class="grid grid-cols-1 gap-4 pt-4 lg:grid-cols-2 xl:grid-cols-3">
<template x-for="item in filteredApplications" :key="item.id">
<span class="relative">
<span>
<a class="h-24 box group" :href="item.hrefLink">
<div class="flex flex-col mx-6">
<div class="pb-2 font-bold text-white" x-text="item.name"></div>
<div class="flex gap-2">
<div class="pb-2 font-bold text-white" x-text="item.name"></div>
<template x-if="item.status.startsWith('running')">
<div title="running" class="mt-1 bg-success badge badge-xs"></div>
</template>
<template x-if="item.status.startsWith('exited')">
<div title="exited" class="mt-1 bg-error badge badge-xs"></div>
</template>
<template x-if="item.status.startsWith('restarting')">
<div title="restarting" class="mt-1 bg-warningbadge badge-xs"></div>
</template>
<template x-if="item.status.startsWith('degraded')">
<div title="degraded" class="mt-1 bg-warning badge badge-xs"></div>
</template>
</div>
<div class="description" x-text="item.description"></div>
<div class="description" x-text="item.fqdn"></div>
</div>
<template x-if="item.status.startsWith('running')">
<div class="absolute bg-success -top-1 -left-1 badge badge-xs"></div>
</template>
<template x-if="item.status.startsWith('exited')">
<div class="absolute bg-error -top-1 -left-1 badge badge-xs"></div>
</template>
<template x-if="item.status.startsWith('restarting')" >
<div class="absolute bg-warning -top-1 -left-1 badge badge-xs"></div>
</template>
<template x-if="item.status.startsWith('degraded')">
<div class="absolute bg-warning -top-1 -left-1 badge badge-xs"></div>
</template>
</a>
<div class="flex gap-1 pt-1 group-hover:text-white group min-h-6">
<template x-for="tag in item.tags">
@ -79,21 +81,26 @@
</span>
</template>
<template x-for="item in filteredPostgresqls" :key="item.id">
<span class="relative">
<span>
<a class="h-24 box group" :href="item.hrefLink">
<div class="flex flex-col mx-6">
<div class="font-bold text-white" x-text="item.name"></div>
<div class="flex gap-2">
<div class="pb-2 font-bold text-white" x-text="item.name"></div>
<template x-if="item.status.startsWith('running')">
<div title="running" class="mt-1 bg-success badge badge-xs"></div>
</template>
<template x-if="item.status.startsWith('exited')">
<div title="exited" class="mt-1 bg-error badge badge-xs"></div>
</template>
<template x-if="item.status.startsWith('restarting')">
<div title="restarting" class="mt-1 bg-warningbadge badge-xs"></div>
</template>
<template x-if="item.status.startsWith('degraded')">
<div title="degraded" class="mt-1 bg-warning badge badge-xs"></div>
</template>
</div>
<div class="description" x-text="item.description"></div>
</div>
<template x-if="item.status.startsWith('running')">
<div class="absolute bg-success -top-1 -left-1 badge badge-xs"></div>
</template>
<template x-if="item.status.startsWith('exited')">
<div class="absolute bg-error -top-1 -left-1 badge badge-xs"></div>
</template>
<template x-if="item.status.startsWith('restarting')">
<div class="absolute bg-warning -top-1 -left-1 badge badge-xs"></div>
</template>
</a>
<div class="flex gap-1 pt-1 group-hover:text-white group min-h-6">
<template x-for="tag in item.tags">
@ -106,22 +113,26 @@
</span>
</template>
<template x-for="item in filteredRedis" :key="item.id">
<span class="relative">
<span>
<a class="h-24 box group" :href="item.hrefLink">
<div class="flex flex-col mx-6">
<div class="font-bold text-white" x-text="item.name"></div>
<div class="flex gap-2">
<div class="pb-2 font-bold text-white" x-text="item.name"></div>
<template x-if="item.status.startsWith('running')">
<div title="running" class="mt-1 bg-success badge badge-xs"></div>
</template>
<template x-if="item.status.startsWith('exited')">
<div title="exited" class="mt-1 bg-error badge badge-xs"></div>
</template>
<template x-if="item.status.startsWith('restarting')">
<div title="restarting" class="mt-1 bg-warningbadge badge-xs"></div>
</template>
<template x-if="item.status.startsWith('degraded')">
<div title="degraded" class="mt-1 bg-warning badge badge-xs"></div>
</template>
</div>
<div class="description" x-text="item.description"></div>
</div>
<template x-if="item.status.startsWith('running')">
<div class="absolute bg-success -top-1 -left-1 badge badge-xs"></div>
</template>
<template x-if="item.status.startsWith('exited')">
<div class="absolute bg-error -top-1 -left-1 badge badge-xs"></div>
</template>
<template x-if="item.status.startsWith('restarting')">
<div class="absolute bg-warning -top-1 -left-1 badge badge-xs"></div>
</template>
</a>
<div class="flex gap-1 pt-1 group-hover:text-white group min-h-6">
<template x-for="tag in item.tags">
@ -134,21 +145,26 @@
</span>
</template>
<template x-for="item in filteredMongodbs" :key="item.id">
<span class="relative">
<span>
<a class="h-24 box group" :href="item.hrefLink">
<div class="flex flex-col mx-6">
<div class="font-bold text-white" x-text="item.name"></div>
<div class="flex gap-2">
<div class="pb-2 font-bold text-white" x-text="item.name"></div>
<template x-if="item.status.startsWith('running')">
<div title="running" class="mt-1 bg-success badge badge-xs"></div>
</template>
<template x-if="item.status.startsWith('exited')">
<div title="exited" class="mt-1 bg-error badge badge-xs"></div>
</template>
<template x-if="item.status.startsWith('restarting')">
<div title="restarting" class="mt-1 bg-warningbadge badge-xs"></div>
</template>
<template x-if="item.status.startsWith('degraded')">
<div title="degraded" class="mt-1 bg-warning badge badge-xs"></div>
</template>
</div>
<div class="description" x-text="item.description"></div>
</div>
<template x-if="item.status.startsWith('running')">
<div class="absolute bg-success -top-1 -left-1 badge badge-xs"></div>
</template>
<template x-if="item.status.startsWith('exited')">
<div class="absolute bg-error -top-1 -left-1 badge badge-xs"></div>
</template>
<template x-if="item.status.startsWith('restarting')">
<div class="absolute bg-warning -top-1 -left-1 badge badge-xs"></div>
</template>
</a>
<div class="flex gap-1 pt-1 group-hover:text-white group min-h-6">
<template x-for="tag in item.tags">
@ -161,21 +177,26 @@
</span>
</template>
<template x-for="item in filteredMysqls" :key="item.id">
<span class="relative">
<span>
<a class="h-24 box group" :href="item.hrefLink">
<div class="flex flex-col mx-6">
<div class="font-bold text-white" x-text="item.name"></div>
<div class="flex gap-2">
<div class="pb-2 font-bold text-white" x-text="item.name"></div>
<template x-if="item.status.startsWith('running')">
<div title="running" class="mt-1 bg-success badge badge-xs"></div>
</template>
<template x-if="item.status.startsWith('exited')">
<div title="exited" class="mt-1 bg-error badge badge-xs"></div>
</template>
<template x-if="item.status.startsWith('restarting')">
<div title="restarting" class="mt-1 bg-warningbadge badge-xs"></div>
</template>
<template x-if="item.status.startsWith('degraded')">
<div title="degraded" class="mt-1 bg-warning badge badge-xs"></div>
</template>
</div>
<div class="description" x-text="item.description"></div>
</div>
<template x-if="item.status.startsWith('running')">
<div class="absolute bg-success -top-1 -left-1 badge badge-xs"></div>
</template>
<template x-if="item.status.startsWith('exited')">
<div class="absolute bg-error -top-1 -left-1 badge badge-xs"></div>
</template>
<template x-if="item.status.startsWith('restarting')">
<div class="absolute bg-warning -top-1 -left-1 badge badge-xs"></div>
</template>
</a>
<div class="flex gap-1 pt-1 group-hover:text-white group min-h-6">
<template x-for="tag in item.tags">
@ -188,21 +209,26 @@
</span>
</template>
<template x-for="item in filteredMariadbs" :key="item.id">
<span class="relative">
<span>
<a class="h-24 box group" :href="item.hrefLink">
<div class="flex flex-col mx-6">
<div class="font-bold text-white" x-text="item.name"></div>
<div class="flex gap-2">
<div class="pb-2 font-bold text-white" x-text="item.name"></div>
<template x-if="item.status.startsWith('running')">
<div title="running" class="mt-1 bg-success badge badge-xs"></div>
</template>
<template x-if="item.status.startsWith('exited')">
<div title="exited" class="mt-1 bg-error badge badge-xs"></div>
</template>
<template x-if="item.status.startsWith('restarting')">
<div title="restarting" class="mt-1 bg-warningbadge badge-xs"></div>
</template>
<template x-if="item.status.startsWith('degraded')">
<div title="degraded" class="mt-1 bg-warning badge badge-xs"></div>
</template>
</div>
<div class="description" x-text="item.description"></div>
</div>
<template x-if="item.status.startsWith('running')">
<div class="absolute bg-success -top-1 -left-1 badge badge-xs"></div>
</template>
<template x-if="item.status.startsWith('exited')">
<div class="absolute bg-error -top-1 -left-1 badge badge-xs"></div>
</template>
<template x-if="item.status.startsWith('restarting')">
<div class="absolute bg-warning -top-1 -left-1 badge badge-xs"></div>
</template>
</a>
<div class="flex gap-1 pt-1 group-hover:text-white group min-h-6">
<template x-for="tag in item.tags">
@ -215,21 +241,26 @@
</span>
</template>
<template x-for="item in filteredServices" :key="item.id">
<span class="relative">
<span>
<a class="h-24 box group" :href="item.hrefLink">
<div class="flex flex-col mx-6">
<div class="font-bold text-white" x-text="item.name"></div>
<div class="flex gap-2">
<div class="pb-2 font-bold text-white" x-text="item.name"></div>
<template x-if="item.status.startsWith('running')">
<div title="running" class="mt-1 bg-success badge badge-xs"></div>
</template>
<template x-if="item.status.startsWith('exited')">
<div title="exited" class="mt-1 bg-error badge badge-xs"></div>
</template>
<template x-if="item.status.startsWith('restarting')">
<div title="restarting" class="mt-1 bg-warningbadge badge-xs"></div>
</template>
<template x-if="item.status.startsWith('degraded')">
<div title="degraded" class="mt-1 bg-warning badge badge-xs"></div>
</template>
</div>
<div class="description" x-text="item.description"></div>
</div>
<template x-if="item.status.startsWith('running')">
<div class="absolute bg-success -top-1 -left-1 badge badge-xs"></div>
</template>
<template x-if="item.status.startsWith('exited')">
<div class="absolute bg-error -top-1 -left-1 badge badge-xs"></div>
</template>
<template x-if="item.status.startsWith('degraded')">
<div class="absolute bg-warning -top-1 -left-1 badge badge-xs"></div>
</template>
</a>
<div class="flex gap-1 pt-1 group-hover:text-white group min-h-6">
<template x-for="tag in item.tags">

View File

@ -76,7 +76,7 @@
</div>
<div class="flex items-center px-4">
<a class="flex flex-col flex-1 group-hover:text-white hover:no-underline"
href="{{ route('project.service.index', [...$parameters, 'service_name' => $application->name]) }}">
href="{{ route('project.service.index', [...$parameters, 'stack_service_uuid' => $application->uuid]) }}">
<svg xmlns="http://www.w3.org/2000/svg" class="icon hover:text-warning"
viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none"
stroke-linecap="round" stroke-linejoin="round">
@ -122,7 +122,7 @@
</div>
<div class="flex items-center px-4">
<a class="flex flex-col flex-1 group-hover:text-white hover:no-underline"
href="{{ route('project.service.index', [...$parameters, 'service_name' => $database->name]) }}">
href="{{ route('project.service.index', [...$parameters, 'stack_service_uuid' => $database->uuid]) }}">
<svg xmlns="http://www.w3.org/2000/svg" class="icon hover:text-warning"
viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none"
stroke-linecap="round" stroke-linejoin="round">

View File

@ -3,7 +3,7 @@
<div class="flex h-full pt-6">
<div class="flex flex-col gap-4 min-w-fit">
<a class="{{ request()->routeIs('project.service.configuration') ? 'text-white' : '' }}"
href="{{ route('project.service.configuration', [...$parameters, 'service_name' => null]) }}">
href="{{ route('project.service.configuration', [...$parameters, 'stack_service_uuid' => null]) }}">
<button><- Back</button>
</a>
<a :class="activeTab === 'general' && 'text-white'"

View File

@ -55,21 +55,25 @@
@endforeach
@endif
</div>
{{-- @if ($resource->getMorphClass() === 'App\Models\Application')
@if ($resource->getMorphClass() === 'App\Models\Application')
@if (count($networks) > 0)
<h4>Choose another server</h4>
<div class="pb-4 description">(experimental) </div>
<div class="grid grid-cols-1 gap-4 ">
<div class="grid grid-cols-1 gap-4">
@foreach ($networks as $network)
<div wire:click="addServer('{{ $network->id }}','{{ data_get($network, 'server.id') }}')"
class="box w-96">
{{ data_get($network, 'server.name') }}
{{ $network->name }}
class="relative flex flex-col text-white cursor-default box w-96">
<div>
Server: {{ data_get($network, 'server.name') }}
</div>
<div>
Network: {{ data_get($network, 'name') }}
</div>
</div>
@endforeach
</div>
@else
<div class="text-neutral-500">No additional servers available to attach.</div>
@endif
@endif --}}
@endif
</div>

View File

@ -19,7 +19,7 @@
@endif
@if ((!$server->settings->is_reachable || !$server->settings->is_usable) && $server->id !== 0)
<x-slide-over closeWithX fullScreen>
<x-slot:title>Configuring Server</x-slot:title>
<x-slot:title>Validating & Configuring</x-slot:title>
<x-slot:content>
<livewire:server.validate-and-install :server="$server" />
</x-slot:content>

View File

@ -12,12 +12,21 @@
</div>
<div class="flex flex-col gap-4">
<div class="flex flex-col w-full gap-2 xl:flex-row">
<x-forms.input type="password" required id="server.settings.logdrain_newrelic_license_key"
label="License Key" />
<x-forms.input required id="server.settings.logdrain_newrelic_base_uri"
placeholder="https://log-api.eu.newrelic.com/log/v1"
helper="For EU use: https://log-api.eu.newrelic.com/log/v1<br>For US use: https://log-api.newrelic.com/log/v1"
label="Endpoint" />
@if ($server->isLogDrainEnabled())
<x-forms.input disabled type="password" required
id="server.settings.logdrain_newrelic_license_key" label="License Key" />
<x-forms.input disabled required id="server.settings.logdrain_newrelic_base_uri"
placeholder="https://log-api.eu.newrelic.com/log/v1"
helper="For EU use: https://log-api.eu.newrelic.com/log/v1<br>For US use: https://log-api.newrelic.com/log/v1"
label="Endpoint" />
@else
<x-forms.input type="password" required id="server.settings.logdrain_newrelic_license_key"
label="License Key" />
<x-forms.input required id="server.settings.logdrain_newrelic_base_uri"
placeholder="https://log-api.eu.newrelic.com/log/v1"
helper="For EU use: https://log-api.eu.newrelic.com/log/v1<br>For US use: https://log-api.newrelic.com/log/v1"
label="Endpoint" />
@endif
</div>
</div>
<div class="flex justify-end gap-4 pt-6">
@ -35,9 +44,17 @@
<form wire:submit='submit("axiom")' class="flex flex-col">
<div class="flex flex-col gap-4">
<div class="flex flex-col w-full gap-2 xl:flex-row">
<x-forms.input type="password" required id="server.settings.logdrain_axiom_api_key"
label="API Key" />
<x-forms.input required id="server.settings.logdrain_axiom_dataset_name" label="Dataset Name" />
@if ($server->isLogDrainEnabled())
<x-forms.input disabled type="password" required id="server.settings.logdrain_axiom_api_key"
label="API Key" />
<x-forms.input disabled required id="server.settings.logdrain_axiom_dataset_name"
label="Dataset Name" />
@else
<x-forms.input type="password" required id="server.settings.logdrain_axiom_api_key"
label="API Key" />
<x-forms.input required id="server.settings.logdrain_axiom_dataset_name"
label="Dataset Name" />
@endif
</div>
</div>
<div class="flex justify-end gap-4 pt-6">
@ -71,10 +88,18 @@
</div>
<form wire:submit='submit("custom")' class="flex flex-col">
<div class="flex flex-col gap-4">
<x-forms.textarea rows="6" required id="server.settings.logdrain_custom_config"
label="Custom FluentBit Configuration" />
<x-forms.textarea id="server.settings.logdrain_custom_config_parser"
label="Custom Parser Configuration" />
@if ($server->isLogDrainEnabled())
<x-forms.textarea disabled rows="6" required id="server.settings.logdrain_custom_config"
label="Custom FluentBit Configuration" />
<x-forms.textarea disabled id="server.settings.logdrain_custom_config_parser"
label="Custom Parser Configuration" />
@else
<x-forms.textarea rows="6" required id="server.settings.logdrain_custom_config"
label="Custom FluentBit Configuration" />
<x-forms.textarea id="server.settings.logdrain_custom_config_parser"
label="Custom Parser Configuration" />
@endif
</div>
<div class="flex justify-end gap-4 pt-6">
<x-forms.button type="submit">

View File

@ -51,7 +51,7 @@
</x-forms.button>
</div>
@else
<button onclick="checkProxy()"
<button x-on:click="$wire.dispatch('checkProxy')"
class="flex items-center gap-2 cursor-pointer hover:text-white text-neutral-400">
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-warning" viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
@ -64,9 +64,6 @@
@endif
@endif
<script>
function checkProxy() {
window.Livewire.dispatch('checkProxy')
}
Livewire.on('proxyChecked', () => {
startProxy.showModal();
window.Livewire.dispatch('startProxy');

View File

@ -68,6 +68,51 @@
<div class="w-64"><x-loading text="Docker is installed:" /></div>
@endif
@endif
@if ($docker_compose_installed)
<div class="flex w-64 gap-2">Docker Compose is installed: <svg class="w-5 h-5 text-success"
viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg">
<g fill="currentColor">
<path
d="m237.66 85.26l-128.4 128.4a8 8 0 0 1-11.32 0l-71.6-72a8 8 0 0 1 0-11.31l24-24a8 8 0 0 1 11.32 0l36.68 35.32a8 8 0 0 0 11.32 0l92.68-91.32a8 8 0 0 1 11.32 0l24 23.6a8 8 0 0 1 0 11.31"
opacity=".2" />
<path
d="m243.28 68.24l-24-23.56a16 16 0 0 0-22.58 0L104 136l-.11-.11l-36.64-35.27a16 16 0 0 0-22.57.06l-24 24a16 16 0 0 0 0 22.61l71.62 72a16 16 0 0 0 22.63 0l128.4-128.38a16 16 0 0 0-.05-22.67M103.62 208L32 136l24-24l.11.11l36.64 35.27a16 16 0 0 0 22.52 0L208.06 56L232 79.6Z" />
</g>
</svg></div>
@if ($proxy_started)
<div class="flex w-64 gap-2">Proxy Started: <svg class="w-5 h-5 text-success" viewBox="0 0 256 256"
xmlns="http://www.w3.org/2000/svg">
<g fill="currentColor">
<path
d="m237.66 85.26l-128.4 128.4a8 8 0 0 1-11.32 0l-71.6-72a8 8 0 0 1 0-11.31l24-24a8 8 0 0 1 11.32 0l36.68 35.32a8 8 0 0 0 11.32 0l92.68-91.32a8 8 0 0 1 11.32 0l24 23.6a8 8 0 0 1 0 11.31"
opacity=".2" />
<path
d="m243.28 68.24l-24-23.56a16 16 0 0 0-22.58 0L104 136l-.11-.11l-36.64-35.27a16 16 0 0 0-22.57.06l-24 24a16 16 0 0 0 0 22.61l71.62 72a16 16 0 0 0 22.63 0l128.4-128.38a16 16 0 0 0-.05-22.67M103.62 208L32 136l24-24l.11.11l36.64 35.27a16 16 0 0 0 22.52 0L208.06 56L232 79.6Z" />
</g>
</svg></div>
@else
@if ($error)
<div class="flex w-64 gap-2">Proxy Started: <svg class="w-5 h-5 text-error" viewBox="0 0 256 256"
xmlns="http://www.w3.org/2000/svg">
<path fill="currentColor"
d="M208.49 191.51a12 12 0 0 1-17 17L128 145l-63.51 63.49a12 12 0 0 1-17-17L111 128L47.51 64.49a12 12 0 0 1 17-17L128 111l63.51-63.52a12 12 0 0 1 17 17L145 128Z" />
</svg></div>
@else
<div class="w-64"><x-loading text="Proxy Started:" /></div>
@endif
@endif
@else
@if ($error)
<div class="flex w-64 gap-2">Docker Compose is installed: <svg class="w-5 h-5 text-error"
viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg">
<path fill="currentColor"
d="M208.49 191.51a12 12 0 0 1-17 17L128 145l-63.51 63.49a12 12 0 0 1-17-17L111 128L47.51 64.49a12 12 0 0 1 17-17L128 111l63.51-63.52a12 12 0 0 1 17 17L145 128Z" />
</svg></div>
@else
<div class="w-64"><x-loading text="Docker Compose is installed:" /></div>
@endif
@endif
@endif
@isset($docker_version)
<div class="flex w-64 gap-2">Minimum Docker version installed: <svg class="w-5 h-5 text-success"
@ -81,6 +126,7 @@
</g>
</svg></div>
@endisset
<livewire:new-activity-monitor header="Logs" />
@isset($error)
<pre class="font-bold whitespace-pre-line text-error">{!! $error !!}</pre>

View File

@ -79,9 +79,7 @@ use App\Livewire\Waitlist\Index as WaitlistIndex;
if (isDev()) {
Route::get('/dev/compose', Compose::class)->name('dev.compose');
}
if (isCloud()) {
Route::get('/admin', AdminIndex::class)->name('admin.index');
}
Route::get('/admin', AdminIndex::class)->name('admin.index');
Route::post('/forgot-password', [Controller::class, 'forgot_password'])->name('password.forgot');
Route::get('/api/v1/test/realtime', [Controller::class, 'realtime_test'])->middleware('auth');
@ -165,7 +163,7 @@ Route::middleware(['auth', 'verified'])->group(function () {
});
Route::prefix('project/{project_uuid}/{environment_name}/service/{service_uuid}')->group(function () {
Route::get('/', ServiceConfiguration::class)->name('project.service.configuration');
Route::get('/{service_name}', ServiceIndex::class)->name('project.service.index');
Route::get('/{stack_service_uuid}', ServiceIndex::class)->name('project.service.index');
Route::get('/command', ExecuteContainerCommand::class)->name('project.service.command');
Route::get('/tasks/{task_uuid}', ScheduledTaskShow::class)->name('project.service.scheduled-tasks');
});

View File

@ -861,7 +861,11 @@ Route::post('/payments/stripe/events', function () {
$subscription = Subscription::where('stripe_customer_id', $customerId)->first();
if (!$subscription) {
Sleep::for(5)->seconds();
$subscription = Subscription::where('stripe_customer_id', $customerId)->firstOrFail();
$subscription = Subscription::where('stripe_customer_id', $customerId)->first();
}
if (!$subscription) {
send_internal_notification('No subscription found for: ' . $customerId);
return response("No subscription found", 400);
}
$trialEndedAlready = data_get($subscription, 'stripe_trial_already_ended');
$cancelAtPeriodEnd = data_get($data, 'cancel_at_period_end');

View File

@ -0,0 +1,9 @@
<?php
it('ConvertDockerTunCommand', function () {
$input = '--cap-add=NET_ADMIN --cap-add=NET_RAW --cap-add SYS_ADMIN';
$output = convert_docker_run_to_compose($input);
expect($output)->toBe([
'cap_add' => ['NET_ADMIN', 'NET_RAW', 'SYS_ADMIN'],
])->ray();
});

View File

@ -1,7 +0,0 @@
<?php
it('returns a successful response', function () {
$response = $this->get('/api/health');
$response->assertStatus(200);
});

View File

@ -1,7 +0,0 @@
<?php
test('parse', function () {
expect($result)->toBe(3);
});

View File

@ -12,7 +12,7 @@
*/
uses(
Tests\TestCase::class,
// Tests\TestCase::class,
// Illuminate\Foundation\Testing\RefreshDatabase::class,
)->in('Feature');

View File

@ -1,5 +0,0 @@
<?php
test('that true is true', function () {
expect(true)->toBeTrue();
});

View File

@ -1,5 +0,0 @@
<?php
test('globals')
->expect(['dd', 'dump'])
->not->toBeUsed();

View File

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