Compare commits

..

33 Commits

Author SHA1 Message Date
Andras Bacsai
ca6543a919 Merge pull request #1741 from coollabsio/next
v4.0.0-beta.214
2024-02-14 08:44:42 +01:00
Andras Bacsai
364a6aa3a2 fix: boolean docker options 2024-02-14 08:42:47 +01:00
Andras Bacsai
82b0667277 Update version numbers 2024-02-12 12:56:04 +01:00
Andras Bacsai
74c126c731 Merge pull request #1729 from coollabsio/next
v4.0.0-beta.213
2024-02-12 11:56:40 +01:00
Andras Bacsai
d87a0fe74f Refactor authentication check in Index.php 2024-02-12 11:53:28 +01:00
Andras Bacsai
9a858f628d Merge pull request #1732 from fipnooone/fix/previews-flex-wrap
Flex-wrap deployment previews
2024-02-12 11:50:41 +01:00
Andras Bacsai
fed01fa9d2 Fix subscription retrieval and handle missing subscriptions 2024-02-12 11:48:28 +01:00
Andras Bacsai
e1468da36a feat: add proxy start to server validation
fix: boarding flow updated
2024-02-12 11:46:36 +01:00
Andras Bacsai
ddfc1440cd fix: menu 2024-02-12 10:05:45 +01:00
Andras Bacsai
5fc46384e6 Refactor status component to exclude parentheses in status message 2024-02-11 18:08:36 +01:00
Andras Bacsai
48d9df1e43 Add conditional display of deployment server name in previews.blade.php 2024-02-11 17:29:14 +01:00
Andras Bacsai
6b62d91f82 Update service_name parameter to stack_service_uuid in index.blade.php 2024-02-11 17:24:20 +01:00
Andras Bacsai
059748ad3b fix: get service stack as uuid, not name 2024-02-11 15:44:02 +01:00
Andras Bacsai
a334f998a2 Add Bitnami Docker images for MariaDB, MongoDB, MySQL, PostgreSQL, and Redis 2024-02-11 15:40:02 +01:00
Andras Bacsai
53a5ccef31 fix: add docker compose check during server validation 2024-02-11 15:32:58 +01:00
Andras Bacsai
9eea73cefb Update Docker command in InstallLogDrain.php 2024-02-11 14:35:07 +01:00
Andras Bacsai
b210e1f243 fix: lock logdrain configuration when one of them are enabled 2024-02-11 14:31:21 +01:00
fipnooone
ef3202101c fix: flex wrap deployment previews 2024-02-10 13:40:35 +07:00
Andras Bacsai
ad54358de7 Refactor resource index.blade.php file 2024-02-09 13:57:37 +01:00
Andras Bacsai
3689b58b92 Add success message for cleanup_queue 2024-02-09 13:51:31 +01:00
Andras Bacsai
5a4180a750 Update navbar dropdown menu styling 2024-02-09 13:50:19 +01:00
Andras Bacsai
047922b13a fix: new menu ui 2024-02-09 13:48:40 +01:00
Andras Bacsai
798d747164 add Docker run command parse test 2024-02-09 13:38:17 +01:00
Andras Bacsai
29676ffb22 Update Teams link in navbar.blade.php 2024-02-09 08:42:39 +01:00
Andras Bacsai
8a50b063d4 fix: user proper image_tag, if set 2024-02-08 15:22:07 +01:00
Andras Bacsai
3d2444ab2e Update version 4 to 4.0.0-beta.213 2024-02-08 14:54:30 +01:00
Andras Bacsai
7c395edab4 Fix conditional statement in navbar.blade.php 2024-02-08 14:06:43 +01:00
Andras Bacsai
7e7f322e21 Refactor admin authentication and routing***
***Add redirect for non-cloud users and instance admins without admin token.***

***Always include admin route, regardless of cloud status.
2024-02-08 14:01:16 +01:00
Andras Bacsai
9350fb4b97 Fix access control in Admin Index and hide Admin link in navbar 2024-02-08 13:54:16 +01:00
Andras Bacsai
59c3cc6ce1 Refactor admin authentication logic in Index component 2024-02-08 13:46:43 +01:00
Andras Bacsai
d7001937ac Fix access control in Admin Index and Navbar components 2024-02-08 13:40:26 +01:00
Andras Bacsai
3fe58ec66b Fix isInstanceAdmin function call in Index.php 2024-02-08 13:33:51 +01:00
Andras Bacsai
ed12f73483 Update admin authentication and version numbers 2024-02-08 13:33:34 +01:00
41 changed files with 509 additions and 324 deletions

View File

@@ -198,7 +198,7 @@ Files:
} }
$restart_command = [ $restart_command = [
"echo 'Stopping old Fluent Bit'", "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'", "echo 'Starting Fluent Bit'",
"cd $config_path && docker compose up -d --remove-orphans", "cd $config_path && docker compose up -d --remove-orphans",
]; ];

View File

@@ -618,6 +618,9 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
} }
} else { } else {
$this->dockerImageTag = str($this->commit)->substr(0, 128); $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) { if ($this->application->docker_registry_image_name) {
$this->build_image_name = Str::lower("{$this->application->docker_registry_image_name}:{$this->dockerImageTag}-build"); $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}"); $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 $users = [];
public function mount() public function mount()
{ {
if (!isCloud()) {
return redirect()->route('dashboard');
}
if (auth()->user()->id !== 0 && session('adminToken') === null) { if (auth()->user()->id !== 0 && session('adminToken') === null) {
return redirect()->route('dashboard'); return redirect()->route('dashboard');
} }

View File

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

View File

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

View File

@@ -56,15 +56,15 @@ class Heading extends Component
return; return;
} }
if ($this->application->destination->server->isSwarm() && str($this->application->docker_registry_image_name)->isEmpty()) { 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; return;
} }
if (data_get($this->application, 'settings.is_build_server_enabled') && str($this->application->docker_registry_image_name)->isEmpty()) { 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; return;
} }
if ($this->application->additional_servers->count() > 0 && str($this->application->docker_registry_image_name)->isEmpty()) { 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; return;
} }
$this->setDeploymentUuid(); $this->setDeploymentUuid();
@@ -103,7 +103,7 @@ class Heading extends Component
public function restart() public function restart()
{ {
if ($this->application->additional_servers->count() > 0 && str($this->application->docker_registry_image_name)->isEmpty()) { 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; return;
} }
$this->setDeploymentUuid(); $this->setDeploymentUuid();

View File

@@ -27,12 +27,12 @@ class Index extends Component
$this->parameters = get_route_parameters(); $this->parameters = get_route_parameters();
$this->query = request()->query(); $this->query = request()->query();
$this->service = Service::whereUuid($this->parameters['service_uuid'])->firstOrFail(); $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) { if ($service) {
$this->serviceApplication = $service; $this->serviceApplication = $service;
$this->serviceApplication->getFilesFromServer(); $this->serviceApplication->getFilesFromServer();
} else { } 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->serviceDatabase->getFilesFromServer();
} }
$this->s3s = currentTeam()->s3s; $this->s3s = currentTeam()->s3s;

View File

@@ -37,10 +37,13 @@ class Destination extends Component
$this->networks = $this->networks->reject(function ($network) use ($all_networks) { $this->networks = $this->networks->reject(function ($network) use ($all_networks) {
return $all_networks->pluck('id')->contains($network->id); return $all_networks->pluck('id')->contains($network->id);
}); });
} }
public function redeploy(int $network_id, int $server_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); $deployment_uuid = new Cuid2(7);
$server = Server::find($server_id); $server = Server::find($server_id);
$destination = StandaloneDocker::find($network_id); $destination = StandaloneDocker::find($network_id);

View File

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

View File

@@ -2,6 +2,7 @@
namespace App\Livewire\Server; namespace App\Livewire\Server;
use App\Actions\Proxy\StartProxy;
use App\Models\Server; use App\Models\Server;
use Livewire\Component; use Livewire\Component;
@@ -14,18 +15,23 @@ class ValidateAndInstall extends Component
public $uptime = null; public $uptime = null;
public $supported_os_type = null; public $supported_os_type = null;
public $docker_installed = null; public $docker_installed = null;
public $docker_compose_installed = null;
public $docker_version = null; public $docker_version = null;
public $proxy_started = false;
public $error = null; public $error = null;
protected $listeners = ['validateServer' => 'init', 'validateDockerEngine', 'validateServerNow' => 'validateServer']; protected $listeners = ['validateServer' => 'init', 'validateDockerEngine', 'validateServerNow' => 'validateServer'];
public function init(bool $install = true) public function init(bool $install = true)
{ {
$this->install = $install; $this->install = $install;
$this->uptime = null; $this->uptime = null;
$this->supported_os_type = null; $this->supported_os_type = null;
$this->docker_installed = null; $this->docker_installed = null;
$this->docker_version = null; $this->docker_version = null;
$this->docker_compose_installed = null;
$this->proxy_started = null;
$this->error = null; $this->error = null;
$this->number_of_tries = 0; $this->number_of_tries = 0;
$this->dispatch('validateServerNow'); $this->dispatch('validateServerNow');
@@ -43,6 +49,11 @@ class ValidateAndInstall extends Component
if ($swarmInstalled) { if ($swarmInstalled) {
$this->dispatch('success', 'Docker Swarm is initiated.'); $this->dispatch('success', 'Docker Swarm is initiated.');
} }
} else {
$proxy = StartProxy::run($this->server);
if ($proxy) {
$this->proxy_started = true;
}
} }
} catch (\Throwable $e) { } catch (\Throwable $e) {
return handleError($e, $this); return handleError($e, $this);
@@ -67,9 +78,9 @@ class ValidateAndInstall extends Component
public function validateDockerEngine() public function validateDockerEngine()
{ {
$this->docker_installed = $this->server->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) { if ($this->install) {
ray($this->number_of_tries, $this->max_tries);
if ($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>.'; $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; return;

View File

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

View File

@@ -443,6 +443,21 @@ class Server extends BaseModel
$this->validateCoolifyNetwork(isSwarm: false, isBuildServer: $this->settings->is_build_server); $this->validateCoolifyNetwork(isSwarm: false, isBuildServer: $this->settings->is_build_server);
return true; 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() public function validateDockerSwarm()
{ {
$swarmStatus = instant_remote_process(["docker info|grep -i swarm"], $this, false); $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 RESTART_MODE = 'unless-stopped';
const DATABASE_DOCKER_IMAGES = [ const DATABASE_DOCKER_IMAGES = [
'bitnami/mariadb',
'bitnami/mongodb',
'bitnami/mysql',
'bitnami/postgresql',
'bitnami/redis',
'mysql', 'mysql',
'mariadb', 'mariadb',
'postgres', 'postgres',

View File

@@ -340,25 +340,20 @@ function convert_docker_run_to_compose(?string $custom_docker_run_options = null
'--cap-drop' => 'cap_drop', '--cap-drop' => 'cap_drop',
'--security-opt' => 'security_opt', '--security-opt' => 'security_opt',
'--sysctl' => 'sysctls', '--sysctl' => 'sysctls',
'--device' => 'devices',
'--ulimit' => 'ulimits', '--ulimit' => 'ulimits',
'--device' => 'devices',
'--init' => 'init', '--init' => 'init',
'--ulimit' => 'ulimits', '--ulimit' => 'ulimits',
'--privileged' => 'privileged', '--privileged' => 'privileged',
]); ]);
foreach ($matches as $match) { foreach ($matches as $match) {
$option = $match[1]; $option = $match[1];
$value = isset($match[2]) && $match[2] !== '' ? $match[2] : true; if (isset($match[2]) && $match[2] !== '') {
if ($list_options->contains($option)) { $value = $match[2];
$value = explode(',', $value); $options[$option][] = $value;
} $options[$option] = array_unique($options[$option]);
if (array_key_exists($option, $options)) {
if (is_array($options[$option])) {
$options[$option][] = $value;
} else {
$options[$option] = [$options[$option], $value];
}
} else { } else {
$value = true;
$options[$option] = $value; $options[$option] = $value;
} }
} }
@@ -370,7 +365,7 @@ function convert_docker_run_to_compose(?string $custom_docker_run_options = null
} }
if ($option === '--ulimit') { if ($option === '--ulimit') {
$ulimits = collect([]); $ulimits = collect([]);
collect($value)->map(function ($ulimit) use ($ulimits){ collect($value)->map(function ($ulimit) use ($ulimits) {
$ulimit = explode('=', $ulimit); $ulimit = explode('=', $ulimit);
$type = $ulimit[0]; $type = $ulimit[0];
$limits = explode(':', $ulimit[1]); $limits = explode(':', $ulimit[1]);
@@ -381,7 +376,6 @@ function convert_docker_run_to_compose(?string $custom_docker_run_options = null
'soft' => $soft_limit, 'soft' => $soft_limit,
'hard' => $hard_limit 'hard' => $hard_limit
]); ]);
} else { } else {
$soft_limit = $ulimit[1]; $soft_limit = $ulimit[1];
$ulimits->put($type, [ $ulimits->put($type, [

View File

@@ -7,7 +7,7 @@ return [
// The release version of your application // The release version of your application
// Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD')) // Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD'))
'release' => '4.0.0-beta.212', 'release' => '4.0.0-beta.214',
// 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.212'; return '4.0.0-beta.214';

View File

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

View File

@@ -40,109 +40,133 @@
</svg> </svg>
</a> </a>
</li> </li>
<div class="inline-block text-left " x-data="{ open: false }">
<details x-data="{ open: false }" class="dropdown dropdown-right" x-bind:open="open"> <div>
<summary class="bg-transparent border-none btn hover:bg-transparent no-animation" <button x-on:click.prevent="open = !open" x-on:click.away="open = false" type="button"
x-on:click.prevent="open = !open" x-on:click.away="open = false"> <svg class="icon" class="py-4 mx-4" id="menu-button" aria-expanded="true" aria-haspopup="true">
viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg"> <svg class="icon" viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg">
<path fill="currentColor" <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" /> 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> </svg>
<ul tabindex="0" class="w-64 p-2 border border-coolgray-200 dropdown-content menu bg-coolgray-100 "> </button>
<li title="Tags" class="border-transparent hover:bg-coolgray-200 "> </div>
<a class=" hover:bg-transparent hover:no-underline" href="{{ route('tags.index') }}"> <div x-show="open" x-cloak
<svg class="icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> 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"
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" role="menu" aria-orientation="vertical" aria-labelledby="menu-button" tabindex="-1">
stroke-width="2"> <div class="py-1" role="none">
<path <li title="Tags" class="border-transparent hover:bg-coolgray-200 ">
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" /> <a class=" hover:bg-transparent hover:no-underline" href="{{ route('tags.index') }}">
<path d="m18 19l1.592-1.592a4.82 4.82 0 0 0 0-6.816L15 6m-8 4h-.01" /> <svg class="{{ request()->is('tags*') ? 'text-warning icon' : 'icon' }}"viewBox="0 0 24 24"
</g> xmlns="http://www.w3.org/2000/svg">
</svg> <g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
Tags stroke-width="2">
</a> <path
</li> 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" />
<li title="Command Center" class="hover:bg-coolgray-200"> <path d="m18 19l1.592-1.592a4.82 4.82 0 0 0 0-6.816L15 6m-8 4h-.01" />
<a class="hover:bg-transparent hover:no-underline" href="{{ route('command-center') }}"> </g>
<svg xmlns="http://www.w3.org/2000/svg" </svg>
class="{{ request()->is('command-center') ? ' icon' : 'icon' }}" viewBox="0 0 24 24" Tags
stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" </a>
stroke-linejoin="round"> </li>
<path stroke="none" d="M0 0h24v24H0z" fill="none" /> <li title="Command Center" class="hover:bg-coolgray-200">
<path d="M5 7l5 5l-5 5" /> <a class="hover:bg-transparent hover:no-underline" href="{{ route('command-center') }}">
<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">
<svg xmlns="http://www.w3.org/2000/svg" <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" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none"
stroke-linecap="round" stroke-linejoin="round"> stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path <path d="M5 7l5 5l-5 5" />
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="M12 19l7 0" />
<path d="M9 12a3 3 0 1 0 6 0a3 3 0 0 0 -6 0" />
</svg> </svg>
Settings 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="{{ 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> </a>
</li> </li>
@endif
</ul> @if (isInstanceAdmin())
</details> <li title="Settings" class="hover:bg-coolgray-200">
@if (isCloud()) <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>
@if (isCloud() && isInstanceAdmin())
<li title="Admin"> <li title="Admin">
<a class="hover:bg-transparent" href="/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"> <svg class="text-pink-600 icon" viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg">
@@ -180,11 +204,12 @@
</div> </div>
</li> </li>
<form action="/logout" method="POST" class="hover:bg-transparent"> <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 @csrf
<button type="submit" class="rounded-none hover:text-white hover:bg-transparent"> <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"> <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> </svg>
</button> </button>
</li> </li>

View File

@@ -1,4 +1,4 @@
@props(['closeWithX' => 'false', 'fullScreen' => 'false']) @props(['closeWithX' => false, 'fullScreen' => false])
<div x-data="{ <div x-data="{
slideOverOpen: false slideOverOpen: false
}" class="relative w-auto h-auto"> }" class="relative w-auto h-auto">
@@ -19,7 +19,7 @@
x-transition:leave-start="translate-x-0" x-transition:leave-end="translate-x-full" x-transition:leave-start="translate-x-0" x-transition:leave-end="translate-x-full"
@class([ @class([
'max-w-md w-screen' => !$fullScreen, 'max-w-md w-screen' => !$fullScreen,
'max-w-7xl w-screen' => $fullScreen, 'max-w-4xl w-screen' => $fullScreen,
])> ])>
<div <div
class="flex flex-col h-full py-6 overflow-hidden border-l shadow-lg bg-base-100 border-neutral-800"> 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"> <div class="pl-2 pr-1 text-xs font-bold tracking-widerr text-warning">
{{ str($status)->before(':')->headline() }} {{ str($status)->before(':')->headline() }}
</div> </div>
@if (!str($status)->startsWith('Proxy')) @if (!str($status)->startsWith('Proxy') && !str($status)->contains('('))
<div class="text-xs text-warning">({{ str($status)->after(':') }})</div> <div class="text-xs text-warning">({{ str($status)->after(':') }})</div>
@endif @endif
</div> </div>

View File

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

View File

@@ -121,18 +121,24 @@
There are already servers available for your Team. Do you want to use one of them? There are already servers available for your Team. Do you want to use one of them?
</x-slot:question> </x-slot:question>
<x-slot:actions> <x-slot:actions>
<x-forms.button class="justify-center w-64 box" wire:click="createNewServer">No (create one for me) <div class="flex flex-col gap-4">
</x-forms.button> <div>
<div> <x-forms.button class="justify-center w-64 box" wire:click="createNewServer">No (create one
<form wire:submit='selectExistingServer' class="flex flex-col w-full gap-4 lg:w-96"> for
<x-forms.select label="Existing servers" class="w-96" id='selectedExistingServer'> me)
@foreach ($servers as $server) </x-forms.button>
<option wire:key="{{ $loop->index }}" value="{{ $server->id }}"> </div>
{{ $server->name }}</option> <div>
@endforeach <form wire:submit='selectExistingServer' class="flex flex-col w-full gap-4 lg:w-96">
</x-forms.select> <x-forms.select label="Existing servers" class="w-96" id='selectedExistingServer'>
<x-forms.button type="submit">Use this Server</x-forms.button> @foreach ($servers as $server)
</form> <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> </div>
@if (!$serverReachable) @if (!$serverReachable)
This server is not reachable with the following public key. 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>" 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" /> id="isCloudflareTunnel" label="Cloudflare Tunnel" />
</div> </div>
<x-forms.button type="submit">Check Connection</x-forms.button> <x-forms.button type="submit">Continue</x-forms.button>
</form> </form>
</x-slot:actions> </x-slot:actions>
<x-slot:explanation> <x-slot:explanation>
@@ -227,14 +233,15 @@
@endif @endif
</div> </div>
<div> <div>
@if ($currentState === 'install-docker') @if ($currentState === 'validate-server')
<x-boarding-step title="Install Docker"> <x-boarding-step title="Validate & Configure Server">
<x-slot:question> <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:question>
<x-slot:actions> <x-slot:actions>
<x-slide-over closeWithX fullScreen> <x-slide-over closeWithX fullScreen>
<x-slot:title>Configuring Server</x-slot:title> <x-slot:title>Validating & Configuring</x-slot:title>
<x-slot:content> <x-slot:content>
<livewire:server.validate-and-install :server="$this->createdServer" /> <livewire:server.validate-and-install :server="$this->createdServer" />
</x-slot:content> </x-slot:content>
@@ -254,7 +261,7 @@
</x-boarding-step> </x-boarding-step>
@endif @endif
</div> </div>
<div> {{-- <div>
@if ($currentState === 'select-proxy') @if ($currentState === 'select-proxy')
<x-boarding-step title="Select a Proxy"> <x-boarding-step title="Select a Proxy">
<x-slot:question> <x-slot:question>
@@ -281,7 +288,7 @@
</x-slot:explanation> </x-slot:explanation>
</x-boarding-step> </x-boarding-step>
@endif @endif
</div> </div> --}}
<div> <div>
@if ($currentState === 'create-project') @if ($currentState === 'create-project')
<x-boarding-step title="Project"> <x-boarding-step title="Project">

View File

@@ -1,5 +1,9 @@
<div> <div>
<livewire:project.application.preview.form :application="$application" /> <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> <div>
@if ($application->is_github_based()) @if ($application->is_github_based())
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
@@ -51,7 +55,7 @@
</div> </div>
@if ($application->previews->count() > 0) @if ($application->previews->count() > 0)
<div class="pb-4">Previews</div> <div class="pb-4">Previews</div>
<div class="flex gap-6 "> <div class="flex flex-wrap gap-6">
@foreach ($application->previews as $preview) @foreach ($application->previews as $preview)
<div class="flex flex-col p-4 bg-coolgray-200"> <div class="flex flex-col p-4 bg-coolgray-200">
<div class="flex gap-2">PR #{{ data_get($preview, 'pull_request_id') }} | <div class="flex gap-2">PR #{{ data_get($preview, 'pull_request_id') }} |

View File

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

View File

@@ -48,25 +48,27 @@
<x-forms.input placeholder="Search for name, fqdn..." class="w-full" x-model="search" /> <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"> <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"> <template x-for="item in filteredApplications" :key="item.id">
<span class="relative"> <span>
<a class="h-24 box group" :href="item.hrefLink"> <a class="h-24 box group" :href="item.hrefLink">
<div class="flex flex-col mx-6"> <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.description"></div>
<div class="description" x-text="item.fqdn"></div> <div class="description" x-text="item.fqdn"></div>
</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> </a>
<div class="flex gap-1 pt-1 group-hover:text-white group min-h-6"> <div class="flex gap-1 pt-1 group-hover:text-white group min-h-6">
<template x-for="tag in item.tags"> <template x-for="tag in item.tags">
@@ -79,21 +81,26 @@
</span> </span>
</template> </template>
<template x-for="item in filteredPostgresqls" :key="item.id"> <template x-for="item in filteredPostgresqls" :key="item.id">
<span class="relative"> <span>
<a class="h-24 box group" :href="item.hrefLink"> <a class="h-24 box group" :href="item.hrefLink">
<div class="flex flex-col mx-6"> <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 class="description" x-text="item.description"></div>
</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> </a>
<div class="flex gap-1 pt-1 group-hover:text-white group min-h-6"> <div class="flex gap-1 pt-1 group-hover:text-white group min-h-6">
<template x-for="tag in item.tags"> <template x-for="tag in item.tags">
@@ -106,22 +113,26 @@
</span> </span>
</template> </template>
<template x-for="item in filteredRedis" :key="item.id"> <template x-for="item in filteredRedis" :key="item.id">
<span class="relative"> <span>
<a class="h-24 box group" :href="item.hrefLink"> <a class="h-24 box group" :href="item.hrefLink">
<div class="flex flex-col mx-6"> <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 class="description" x-text="item.description"></div>
</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> </a>
<div class="flex gap-1 pt-1 group-hover:text-white group min-h-6"> <div class="flex gap-1 pt-1 group-hover:text-white group min-h-6">
<template x-for="tag in item.tags"> <template x-for="tag in item.tags">
@@ -134,21 +145,26 @@
</span> </span>
</template> </template>
<template x-for="item in filteredMongodbs" :key="item.id"> <template x-for="item in filteredMongodbs" :key="item.id">
<span class="relative"> <span>
<a class="h-24 box group" :href="item.hrefLink"> <a class="h-24 box group" :href="item.hrefLink">
<div class="flex flex-col mx-6"> <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 class="description" x-text="item.description"></div>
</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> </a>
<div class="flex gap-1 pt-1 group-hover:text-white group min-h-6"> <div class="flex gap-1 pt-1 group-hover:text-white group min-h-6">
<template x-for="tag in item.tags"> <template x-for="tag in item.tags">
@@ -161,21 +177,26 @@
</span> </span>
</template> </template>
<template x-for="item in filteredMysqls" :key="item.id"> <template x-for="item in filteredMysqls" :key="item.id">
<span class="relative"> <span>
<a class="h-24 box group" :href="item.hrefLink"> <a class="h-24 box group" :href="item.hrefLink">
<div class="flex flex-col mx-6"> <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 class="description" x-text="item.description"></div>
</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> </a>
<div class="flex gap-1 pt-1 group-hover:text-white group min-h-6"> <div class="flex gap-1 pt-1 group-hover:text-white group min-h-6">
<template x-for="tag in item.tags"> <template x-for="tag in item.tags">
@@ -188,21 +209,26 @@
</span> </span>
</template> </template>
<template x-for="item in filteredMariadbs" :key="item.id"> <template x-for="item in filteredMariadbs" :key="item.id">
<span class="relative"> <span>
<a class="h-24 box group" :href="item.hrefLink"> <a class="h-24 box group" :href="item.hrefLink">
<div class="flex flex-col mx-6"> <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 class="description" x-text="item.description"></div>
</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> </a>
<div class="flex gap-1 pt-1 group-hover:text-white group min-h-6"> <div class="flex gap-1 pt-1 group-hover:text-white group min-h-6">
<template x-for="tag in item.tags"> <template x-for="tag in item.tags">
@@ -215,21 +241,26 @@
</span> </span>
</template> </template>
<template x-for="item in filteredServices" :key="item.id"> <template x-for="item in filteredServices" :key="item.id">
<span class="relative"> <span>
<a class="h-24 box group" :href="item.hrefLink"> <a class="h-24 box group" :href="item.hrefLink">
<div class="flex flex-col mx-6"> <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 class="description" x-text="item.description"></div>
</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> </a>
<div class="flex gap-1 pt-1 group-hover:text-white group min-h-6"> <div class="flex gap-1 pt-1 group-hover:text-white group min-h-6">
<template x-for="tag in item.tags"> <template x-for="tag in item.tags">

View File

@@ -76,7 +76,7 @@
</div> </div>
<div class="flex items-center px-4"> <div class="flex items-center px-4">
<a class="flex flex-col flex-1 group-hover:text-white hover:no-underline" <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" <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" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none"
stroke-linecap="round" stroke-linejoin="round"> stroke-linecap="round" stroke-linejoin="round">
@@ -122,7 +122,7 @@
</div> </div>
<div class="flex items-center px-4"> <div class="flex items-center px-4">
<a class="flex flex-col flex-1 group-hover:text-white hover:no-underline" <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" <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" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none"
stroke-linecap="round" stroke-linejoin="round"> stroke-linecap="round" stroke-linejoin="round">

View File

@@ -3,7 +3,7 @@
<div class="flex h-full pt-6"> <div class="flex h-full pt-6">
<div class="flex flex-col gap-4 min-w-fit"> <div class="flex flex-col gap-4 min-w-fit">
<a class="{{ request()->routeIs('project.service.configuration') ? 'text-white' : '' }}" <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> <button><- Back</button>
</a> </a>
<a :class="activeTab === 'general' && 'text-white'" <a :class="activeTab === 'general' && 'text-white'"

View File

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

View File

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

View File

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

View File

@@ -51,7 +51,7 @@
</x-forms.button> </x-forms.button>
</div> </div>
@else @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"> 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" <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" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
@@ -64,9 +64,6 @@
@endif @endif
@endif @endif
<script> <script>
function checkProxy() {
window.Livewire.dispatch('checkProxy')
}
Livewire.on('proxyChecked', () => { Livewire.on('proxyChecked', () => {
startProxy.showModal(); startProxy.showModal();
window.Livewire.dispatch('startProxy'); window.Livewire.dispatch('startProxy');

View File

@@ -68,6 +68,51 @@
<div class="w-64"><x-loading text="Docker is installed:" /></div> <div class="w-64"><x-loading text="Docker is installed:" /></div>
@endif @endif
@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 @endif
@isset($docker_version) @isset($docker_version)
<div class="flex w-64 gap-2">Minimum Docker version installed: <svg class="w-5 h-5 text-success" <div class="flex w-64 gap-2">Minimum Docker version installed: <svg class="w-5 h-5 text-success"
@@ -81,6 +126,7 @@
</g> </g>
</svg></div> </svg></div>
@endisset @endisset
<livewire:new-activity-monitor header="Logs" /> <livewire:new-activity-monitor header="Logs" />
@isset($error) @isset($error)
<pre class="font-bold whitespace-pre-line text-error">{!! $error !!}</pre> <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()) { if (isDev()) {
Route::get('/dev/compose', Compose::class)->name('dev.compose'); 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::post('/forgot-password', [Controller::class, 'forgot_password'])->name('password.forgot');
Route::get('/api/v1/test/realtime', [Controller::class, 'realtime_test'])->middleware('auth'); 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::prefix('project/{project_uuid}/{environment_name}/service/{service_uuid}')->group(function () {
Route::get('/', ServiceConfiguration::class)->name('project.service.configuration'); 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('/command', ExecuteContainerCommand::class)->name('project.service.command');
Route::get('/tasks/{task_uuid}', ScheduledTaskShow::class)->name('project.service.scheduled-tasks'); 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(); $subscription = Subscription::where('stripe_customer_id', $customerId)->first();
if (!$subscription) { if (!$subscription) {
Sleep::for(5)->seconds(); 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'); $trialEndedAlready = data_get($subscription, 'stripe_trial_already_ended');
$cancelAtPeriodEnd = data_get($data, 'cancel_at_period_end'); $cancelAtPeriodEnd = data_get($data, 'cancel_at_period_end');

View File

@@ -0,0 +1,31 @@
<?php
it('ConvertCapAdd', 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();
});
it('ConvertPrivilegedAndInit', function () {
$input = '---privileged --init';
$output = convert_docker_run_to_compose($input);
expect($output)->toBe([
'privileged' => true,
'init' => true,
])->ray();
});
it('ConvertUlimit', function () {
$input = '--ulimit nofile=262144:262144';
$output = convert_docker_run_to_compose($input);
expect($output)->toBe([
'ulimits' => [
'nofile' => [
'soft' => '262144',
'hard' => '262144',
],
],
])->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( uses(
Tests\TestCase::class, // Tests\TestCase::class,
// Illuminate\Foundation\Testing\RefreshDatabase::class, // Illuminate\Foundation\Testing\RefreshDatabase::class,
)->in('Feature'); )->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" "version": "3.12.36"
}, },
"v4": { "v4": {
"version": "4.0.0-beta.212" "version": "4.0.0-beta.214"
} }
} }
} }