commit
74092ea95b
59
app/Console/Commands/CleanupDatabase.php
Normal file
59
app/Console/Commands/CleanupDatabase.php
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
|
class CleanupDatabase extends Command
|
||||||
|
{
|
||||||
|
protected $signature = 'cleanup:database {--yes}';
|
||||||
|
protected $description = 'Cleanup database';
|
||||||
|
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
echo "Running database cleanup...\n";
|
||||||
|
$keep_days = 60;
|
||||||
|
|
||||||
|
// Cleanup failed jobs table
|
||||||
|
$failed_jobs = DB::table('failed_jobs')->where('failed_at', '<', now()->subDays(7));
|
||||||
|
$count = $failed_jobs->count();
|
||||||
|
echo "Delete $count entries from failed_jobs.\n";
|
||||||
|
if ($this->option('yes')) {
|
||||||
|
$failed_jobs->delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup sessions table
|
||||||
|
$sessions = DB::table('sessions')->where('last_activity', '<', now()->subDays($keep_days)->timestamp);
|
||||||
|
$count = $sessions->count();
|
||||||
|
echo "Delete $count entries from sessions.\n";
|
||||||
|
if ($this->option('yes')) {
|
||||||
|
$sessions->delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup activity_log table
|
||||||
|
$activity_log = DB::table('activity_log')->where('created_at', '<', now()->subDays($keep_days));
|
||||||
|
$count = $activity_log->count();
|
||||||
|
echo "Delete $count entries from activity_log.\n";
|
||||||
|
if ($this->option('yes')) {
|
||||||
|
$activity_log->delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup application_deployment_queues table
|
||||||
|
$application_deployment_queues = DB::table('application_deployment_queues')->where('created_at', '<', now()->subDays($keep_days));
|
||||||
|
$count = $application_deployment_queues->count();
|
||||||
|
echo "Delete $count entries from application_deployment_queues.\n";
|
||||||
|
if ($this->option('yes')) {
|
||||||
|
$application_deployment_queues->delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup webhooks table
|
||||||
|
$webhooks = DB::table('webhooks')->where('created_at', '<', now()->subDays($keep_days));
|
||||||
|
$count = $webhooks->count();
|
||||||
|
echo "Delete $count entries from webhooks.\n";
|
||||||
|
if ($this->option('yes')) {
|
||||||
|
$webhooks->delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -8,15 +8,16 @@ use Illuminate\Console\Command;
|
|||||||
class CleanupUnreachableServers extends Command
|
class CleanupUnreachableServers extends Command
|
||||||
{
|
{
|
||||||
protected $signature = 'cleanup:unreachable-servers';
|
protected $signature = 'cleanup:unreachable-servers';
|
||||||
protected $description = 'Cleanup Unreachable Servers (3 days)';
|
protected $description = 'Cleanup Unreachable Servers (7 days)';
|
||||||
|
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
echo "Running unreachable server cleanup...\n";
|
echo "Running unreachable server cleanup...\n";
|
||||||
$servers = Server::where('unreachable_count', 3)->where('unreachable_notification_sent', true)->where('updated_at', '<', now()->subDays(3))->get();
|
$servers = Server::where('unreachable_count', 3)->where('unreachable_notification_sent', true)->where('updated_at', '<', now()->subDays(7))->get();
|
||||||
if ($servers->count() > 0) {
|
if ($servers->count() > 0) {
|
||||||
foreach ($servers as $server) {
|
foreach ($servers as $server) {
|
||||||
echo "Cleanup unreachable server ($server->id) with name $server->name";
|
echo "Cleanup unreachable server ($server->id) with name $server->name";
|
||||||
|
send_internal_notification("Server $server->name is unreachable for 7 days. Cleaning up...");
|
||||||
$server->update([
|
$server->update([
|
||||||
'ip' => '1.2.3.4'
|
'ip' => '1.2.3.4'
|
||||||
]);
|
]);
|
||||||
|
@ -69,12 +69,34 @@ class Kernel extends ConsoleKernel
|
|||||||
}
|
}
|
||||||
foreach ($containerServers as $server) {
|
foreach ($containerServers as $server) {
|
||||||
$schedule->job(new ContainerStatusJob($server))->everyMinute()->onOneServer();
|
$schedule->job(new ContainerStatusJob($server))->everyMinute()->onOneServer();
|
||||||
|
// $schedule
|
||||||
|
// ->call(function () use ($server) {
|
||||||
|
// $randomSeconds = rand(1, 40);
|
||||||
|
// $job = new ContainerStatusJob($server);
|
||||||
|
// $job->delay($randomSeconds);
|
||||||
|
// ray('dispatching container status job in ' . $randomSeconds . ' seconds');
|
||||||
|
// dispatch($job);
|
||||||
|
// })->name('container-status-' . $server->id)->everyMinute()->onOneServer();
|
||||||
if ($server->isLogDrainEnabled()) {
|
if ($server->isLogDrainEnabled()) {
|
||||||
$schedule->job(new CheckLogDrainContainerJob($server))->everyMinute()->onOneServer();
|
$schedule->job(new CheckLogDrainContainerJob($server))->everyMinute()->onOneServer();
|
||||||
|
// $schedule
|
||||||
|
// ->call(function () use ($server) {
|
||||||
|
// $randomSeconds = rand(1, 40);
|
||||||
|
// $job = new CheckLogDrainContainerJob($server);
|
||||||
|
// $job->delay($randomSeconds);
|
||||||
|
// dispatch($job);
|
||||||
|
// })->name('log-drain-container-check-' . $server->id)->everyMinute()->onOneServer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
foreach ($servers as $server) {
|
foreach ($servers as $server) {
|
||||||
$schedule->job(new ServerStatusJob($server))->everyMinute()->onOneServer();
|
$schedule->job(new ServerStatusJob($server))->everyMinute()->onOneServer();
|
||||||
|
// $schedule
|
||||||
|
// ->call(function () use ($server) {
|
||||||
|
// $randomSeconds = rand(1, 40);
|
||||||
|
// $job = new ServerStatusJob($server);
|
||||||
|
// $job->delay($randomSeconds);
|
||||||
|
// dispatch($job);
|
||||||
|
// })->name('server-status-job-' . $server->id)->everyMinute()->onOneServer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private function instance_auto_update($schedule)
|
private function instance_auto_update($schedule)
|
||||||
|
@ -14,7 +14,7 @@ use Illuminate\Http\Request;
|
|||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
use Visus\Cuid2\Cuid2;
|
use Visus\Cuid2\Cuid2;
|
||||||
|
|
||||||
class Deploy extends Controller
|
class APIDeploy extends Controller
|
||||||
{
|
{
|
||||||
public function deploy(Request $request)
|
public function deploy(Request $request)
|
||||||
{
|
{
|
||||||
|
@ -6,7 +6,7 @@ use App\Http\Controllers\Controller;
|
|||||||
use App\Models\Project as ModelsProject;
|
use App\Models\Project as ModelsProject;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
class Project extends Controller
|
class APIProject extends Controller
|
||||||
{
|
{
|
||||||
public function projects(Request $request)
|
public function projects(Request $request)
|
||||||
{
|
{
|
||||||
|
@ -6,7 +6,7 @@ use App\Http\Controllers\Controller;
|
|||||||
use App\Models\Server as ModelsServer;
|
use App\Models\Server as ModelsServer;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
class Server extends Controller
|
class APIServer extends Controller
|
||||||
{
|
{
|
||||||
public function servers(Request $request)
|
public function servers(Request $request)
|
||||||
{
|
{
|
||||||
|
@ -1223,6 +1223,19 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
if ((bool)$this->application->settings->is_consistent_container_name_enabled) {
|
if ((bool)$this->application->settings->is_consistent_container_name_enabled) {
|
||||||
$custom_compose = convert_docker_run_to_compose($this->application->custom_docker_run_options);
|
$custom_compose = convert_docker_run_to_compose($this->application->custom_docker_run_options);
|
||||||
if (count($custom_compose) > 0) {
|
if (count($custom_compose) > 0) {
|
||||||
|
$ipv4 = data_get($custom_compose, 'ip.0');
|
||||||
|
$ipv6 = data_get($custom_compose, 'ip6.0');
|
||||||
|
data_forget($custom_compose, 'ip');
|
||||||
|
data_forget($custom_compose, 'ip6');
|
||||||
|
if ($ipv4 || $ipv6) {
|
||||||
|
data_forget($docker_compose['services'][$this->application->uuid], 'networks');
|
||||||
|
}
|
||||||
|
if ($ipv4) {
|
||||||
|
$docker_compose['services'][$this->application->uuid]['networks'][$this->destination->network]['ipv4_address'] = $ipv4;
|
||||||
|
}
|
||||||
|
if ($ipv6) {
|
||||||
|
$docker_compose['services'][$this->application->uuid]['networks'][$this->destination->network]['ipv6_address'] = $ipv6;
|
||||||
|
}
|
||||||
$docker_compose['services'][$this->container_name] = array_merge_recursive($docker_compose['services'][$this->container_name], $custom_compose);
|
$docker_compose['services'][$this->container_name] = array_merge_recursive($docker_compose['services'][$this->container_name], $custom_compose);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -1230,6 +1243,19 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
data_forget($docker_compose, 'services.' . $this->container_name);
|
data_forget($docker_compose, 'services.' . $this->container_name);
|
||||||
$custom_compose = convert_docker_run_to_compose($this->application->custom_docker_run_options);
|
$custom_compose = convert_docker_run_to_compose($this->application->custom_docker_run_options);
|
||||||
if (count($custom_compose) > 0) {
|
if (count($custom_compose) > 0) {
|
||||||
|
$ipv4 = data_get($custom_compose, 'ip.0');
|
||||||
|
$ipv6 = data_get($custom_compose, 'ip6.0');
|
||||||
|
data_forget($custom_compose, 'ip');
|
||||||
|
data_forget($custom_compose, 'ip6');
|
||||||
|
if ($ipv4 || $ipv6) {
|
||||||
|
data_forget($docker_compose['services'][$this->application->uuid], 'networks');
|
||||||
|
}
|
||||||
|
if ($ipv4) {
|
||||||
|
$docker_compose['services'][$this->application->uuid]['networks'][$this->destination->network]['ipv4_address'] = $ipv4;
|
||||||
|
}
|
||||||
|
if ($ipv6) {
|
||||||
|
$docker_compose['services'][$this->application->uuid]['networks'][$this->destination->network]['ipv6_address'] = $ipv6;
|
||||||
|
}
|
||||||
$docker_compose['services'][$this->application->uuid] = array_merge_recursive($docker_compose['services'][$this->application->uuid], $custom_compose);
|
$docker_compose['services'][$this->application->uuid] = array_merge_recursive($docker_compose['services'][$this->application->uuid], $custom_compose);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1649,7 +1675,6 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->next(ApplicationDeploymentStatus::FAILED->value);
|
$this->next(ApplicationDeploymentStatus::FAILED->value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -43,6 +43,10 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
|
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
|
if (!$this->server->isFunctional()) {
|
||||||
|
return 'Server is not ready.';
|
||||||
|
};
|
||||||
|
|
||||||
$applications = $this->server->applications();
|
$applications = $this->server->applications();
|
||||||
$skip_these_applications = collect([]);
|
$skip_these_applications = collect([]);
|
||||||
foreach ($applications as $application) {
|
foreach ($applications as $application) {
|
||||||
@ -57,10 +61,6 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
$applications = $applications->filter(function ($value, $key) use ($skip_these_applications) {
|
$applications = $applications->filter(function ($value, $key) use ($skip_these_applications) {
|
||||||
return !$skip_these_applications->pluck('id')->contains($value->id);
|
return !$skip_these_applications->pluck('id')->contains($value->id);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!$this->server->isFunctional()) {
|
|
||||||
return 'Server is not ready.';
|
|
||||||
};
|
|
||||||
try {
|
try {
|
||||||
if ($this->server->isSwarm()) {
|
if ($this->server->isSwarm()) {
|
||||||
$containers = instant_remote_process(["docker service inspect $(docker service ls -q) --format '{{json .}}'"], $this->server, false);
|
$containers = instant_remote_process(["docker service inspect $(docker service ls -q) --format '{{json .}}'"], $this->server, false);
|
||||||
|
68
app/Jobs/ServerLimitCheckJob.php
Normal file
68
app/Jobs/ServerLimitCheckJob.php
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs;
|
||||||
|
|
||||||
|
use App\Models\Team;
|
||||||
|
use App\Notifications\Server\ForceDisabled;
|
||||||
|
use App\Notifications\Server\ForceEnabled;
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
|
class ServerLimitCheckJob implements ShouldQueue, ShouldBeEncrypted
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
|
public $tries = 4;
|
||||||
|
public function backoff(): int
|
||||||
|
{
|
||||||
|
return isDev() ? 1 : 3;
|
||||||
|
}
|
||||||
|
public function __construct(public Team $team)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
public function middleware(): array
|
||||||
|
{
|
||||||
|
return [(new WithoutOverlapping($this->team->uuid))];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function uniqueId(): int
|
||||||
|
{
|
||||||
|
return $this->team->uuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$servers = $this->team->servers;
|
||||||
|
$servers_count = $servers->count();
|
||||||
|
$limit = $this->team->limits['serverLimit'];
|
||||||
|
$number_of_servers_to_disable = $servers_count - $limit;
|
||||||
|
ray('ServerLimitCheckJob', $this->team->uuid, $servers_count, $limit, $number_of_servers_to_disable);
|
||||||
|
if ($number_of_servers_to_disable > 0) {
|
||||||
|
ray('Disabling servers');
|
||||||
|
$servers = $servers->sortbyDesc('created_at');
|
||||||
|
$servers_to_disable = $servers->take($number_of_servers_to_disable);
|
||||||
|
$servers_to_disable->each(function ($server) {
|
||||||
|
$server->forceDisableServer();
|
||||||
|
$this->team->notify(new ForceDisabled($server));
|
||||||
|
});
|
||||||
|
} else if ($number_of_servers_to_disable === 0) {
|
||||||
|
$servers->each(function ($server) {
|
||||||
|
if ($server->isForceDisabled()) {
|
||||||
|
$server->forceEnableServer();
|
||||||
|
$this->team->notify(new ForceEnabled($server));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
send_internal_notification('ServerLimitCheckJob failed with: ' . $e->getMessage());
|
||||||
|
ray($e->getMessage());
|
||||||
|
return handleError($e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -41,15 +41,6 @@ class ServerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
throw new \RuntimeException('Server is not ready.');
|
throw new \RuntimeException('Server is not ready.');
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
// $this->server->validateConnection();
|
|
||||||
// $this->server->validateOS();
|
|
||||||
// $docker_installed = $this->server->validateDockerEngine();
|
|
||||||
// if (!$docker_installed) {
|
|
||||||
// $this->server->installDocker();
|
|
||||||
// $this->server->validateDockerEngine();
|
|
||||||
// }
|
|
||||||
|
|
||||||
// $this->server->validateDockerEngineVersion();
|
|
||||||
if ($this->server->isFunctional()) {
|
if ($this->server->isFunctional()) {
|
||||||
$this->cleanup(notify: false);
|
$this->cleanup(notify: false);
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
namespace App\Livewire\Admin;
|
namespace App\Livewire\Admin;
|
||||||
|
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
use Illuminate\Support\Facades\Crypt;
|
use Illuminate\Support\Facades\Crypt;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
@ -27,6 +28,7 @@ class Index extends Component
|
|||||||
auth()->login($user);
|
auth()->login($user);
|
||||||
|
|
||||||
if ($user_id === 0) {
|
if ($user_id === 0) {
|
||||||
|
Cache::forget('team:0');
|
||||||
session()->forget('adminToken');
|
session()->forget('adminToken');
|
||||||
} else {
|
} else {
|
||||||
$token_payload = [
|
$token_payload = [
|
||||||
@ -35,6 +37,7 @@ class Index extends Component
|
|||||||
$token = Crypt::encrypt($token_payload);
|
$token = Crypt::encrypt($token_payload);
|
||||||
session(['adminToken' => $token]);
|
session(['adminToken' => $token]);
|
||||||
}
|
}
|
||||||
|
session()->regenerate();
|
||||||
return refreshSession();
|
return refreshSession();
|
||||||
}
|
}
|
||||||
public function render()
|
public function render()
|
||||||
|
@ -4,7 +4,7 @@ namespace App\Livewire;
|
|||||||
|
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
class Sponsorship extends Component
|
class LayoutPopups extends Component
|
||||||
{
|
{
|
||||||
public function getListeners()
|
public function getListeners()
|
||||||
{
|
{
|
||||||
@ -23,6 +23,6 @@ class Sponsorship extends Component
|
|||||||
}
|
}
|
||||||
public function render()
|
public function render()
|
||||||
{
|
{
|
||||||
return view('livewire.sponsorship');
|
return view('livewire.layout-popups');
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -3,6 +3,7 @@
|
|||||||
namespace App\Livewire\Server;
|
namespace App\Livewire\Server;
|
||||||
|
|
||||||
use App\Models\PrivateKey;
|
use App\Models\PrivateKey;
|
||||||
|
use App\Models\Team;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
class Create extends Component
|
class Create extends Component
|
||||||
@ -16,11 +17,7 @@ class Create extends Component
|
|||||||
$this->limit_reached = false;
|
$this->limit_reached = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$team = currentTeam();
|
$this->limit_reached = Team::serverLimitReached();
|
||||||
$servers = $team->servers->count();
|
|
||||||
['serverLimit' => $serverLimit] = $team->limits;
|
|
||||||
|
|
||||||
$this->limit_reached = $servers >= $serverLimit;
|
|
||||||
}
|
}
|
||||||
public function render()
|
public function render()
|
||||||
{
|
{
|
||||||
|
@ -5,6 +5,7 @@ namespace App\Livewire\Server\New;
|
|||||||
use App\Enums\ProxyStatus;
|
use App\Enums\ProxyStatus;
|
||||||
use App\Enums\ProxyTypes;
|
use App\Enums\ProxyTypes;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
|
use App\Models\Team;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
class ByIp extends Component
|
class ByIp extends Component
|
||||||
@ -76,6 +77,9 @@ class ByIp extends Component
|
|||||||
if (is_null($this->private_key_id)) {
|
if (is_null($this->private_key_id)) {
|
||||||
return $this->dispatch('error', 'You must select a private key');
|
return $this->dispatch('error', 'You must select a private key');
|
||||||
}
|
}
|
||||||
|
if (Team::serverLimitReached()) {
|
||||||
|
return $this->dispatch('error', 'You have reached the server limit for your subscription.');
|
||||||
|
}
|
||||||
$payload = [
|
$payload = [
|
||||||
'name' => $this->name,
|
'name' => $this->name,
|
||||||
'description' => $this->description,
|
'description' => $this->description,
|
||||||
|
@ -2,17 +2,17 @@
|
|||||||
|
|
||||||
namespace App\Livewire\Subscription;
|
namespace App\Livewire\Subscription;
|
||||||
|
|
||||||
|
use App\Models\Team;
|
||||||
use Illuminate\Support\Facades\Http;
|
use Illuminate\Support\Facades\Http;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
class Actions extends Component
|
class Actions extends Component
|
||||||
{
|
{
|
||||||
public $server_limits = 0;
|
public $server_limits = 0;
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
$limits = currentTeam()->limits;
|
$this->server_limits = Team::serverLimit();
|
||||||
$this->server_limits = data_get($limits, 'serverLimit', 0);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
public function cancel()
|
public function cancel()
|
||||||
{
|
{
|
||||||
|
@ -9,8 +9,9 @@ use Stripe\Checkout\Session;
|
|||||||
class PricingPlans extends Component
|
class PricingPlans extends Component
|
||||||
{
|
{
|
||||||
public bool $isTrial = false;
|
public bool $isTrial = false;
|
||||||
public function mount() {
|
public function mount()
|
||||||
$this->isTrial = !data_get(currentTeam(),'subscription.stripe_trial_already_ended');
|
{
|
||||||
|
$this->isTrial = !data_get(currentTeam(), 'subscription.stripe_trial_already_ended');
|
||||||
if (config('constants.limits.trial_period') == 0) {
|
if (config('constants.limits.trial_period') == 0) {
|
||||||
$this->isTrial = false;
|
$this->isTrial = false;
|
||||||
}
|
}
|
||||||
@ -26,15 +27,15 @@ class PricingPlans extends Component
|
|||||||
case 'basic-yearly':
|
case 'basic-yearly':
|
||||||
$priceId = config('subscription.stripe_price_id_basic_yearly');
|
$priceId = config('subscription.stripe_price_id_basic_yearly');
|
||||||
break;
|
break;
|
||||||
case 'ultimate-monthly':
|
|
||||||
$priceId = config('subscription.stripe_price_id_ultimate_monthly');
|
|
||||||
break;
|
|
||||||
case 'pro-monthly':
|
case 'pro-monthly':
|
||||||
$priceId = config('subscription.stripe_price_id_pro_monthly');
|
$priceId = config('subscription.stripe_price_id_pro_monthly');
|
||||||
break;
|
break;
|
||||||
case 'pro-yearly':
|
case 'pro-yearly':
|
||||||
$priceId = config('subscription.stripe_price_id_pro_yearly');
|
$priceId = config('subscription.stripe_price_id_pro_yearly');
|
||||||
break;
|
break;
|
||||||
|
case 'ultimate-monthly':
|
||||||
|
$priceId = config('subscription.stripe_price_id_ultimate_monthly');
|
||||||
|
break;
|
||||||
case 'ultimate-yearly':
|
case 'ultimate-yearly':
|
||||||
$priceId = config('subscription.stripe_price_id_ultimate_yearly');
|
$priceId = config('subscription.stripe_price_id_ultimate_yearly');
|
||||||
break;
|
break;
|
||||||
@ -64,18 +65,25 @@ class PricingPlans extends Component
|
|||||||
'success_url' => route('dashboard', ['success' => true]),
|
'success_url' => route('dashboard', ['success' => true]),
|
||||||
'cancel_url' => route('subscription.index', ['cancelled' => true]),
|
'cancel_url' => route('subscription.index', ['cancelled' => true]),
|
||||||
];
|
];
|
||||||
|
if (str($type)->contains('ultimate')) {
|
||||||
if (!data_get($team,'subscription.stripe_trial_already_ended')) {
|
$payload['line_items'][0]['adjustable_quantity'] = [
|
||||||
if (config('constants.limits.trial_period') > 0) {
|
'enabled' => true,
|
||||||
$payload['subscription_data'] = [
|
'minimum' => 10,
|
||||||
'trial_period_days' => config('constants.limits.trial_period'),
|
|
||||||
'trial_settings' => [
|
|
||||||
'end_behavior' => [
|
|
||||||
'missing_payment_method' => 'cancel',
|
|
||||||
]
|
|
||||||
],
|
|
||||||
];
|
];
|
||||||
|
$payload['line_items'][0]['quantity'] = 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!data_get($team, 'subscription.stripe_trial_already_ended')) {
|
||||||
|
if (config('constants.limits.trial_period') > 0) {
|
||||||
|
$payload['subscription_data'] = [
|
||||||
|
'trial_period_days' => config('constants.limits.trial_period'),
|
||||||
|
'trial_settings' => [
|
||||||
|
'end_behavior' => [
|
||||||
|
'missing_payment_method' => 'cancel',
|
||||||
|
]
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
$payload['payment_method_collection'] = 'if_required';
|
$payload['payment_method_collection'] = 'if_required';
|
||||||
}
|
}
|
||||||
$customer = currentTeam()->subscription?->stripe_customer_id ?? null;
|
$customer = currentTeam()->subscription?->stripe_customer_id ?? null;
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Livewire\Tags;
|
namespace App\Livewire\Tags;
|
||||||
|
|
||||||
use App\Http\Controllers\Api\Deploy;
|
use App\Http\Controllers\Api\APIDeploy as Deploy;
|
||||||
use App\Models\ApplicationDeploymentQueue;
|
use App\Models\ApplicationDeploymentQueue;
|
||||||
use App\Models\Tag;
|
use App\Models\Tag;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
@ -10,6 +10,7 @@ use App\Notifications\Server\Unreachable;
|
|||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
use Spatie\SchemalessAttributes\Casts\SchemalessAttributes;
|
use Spatie\SchemalessAttributes\Casts\SchemalessAttributes;
|
||||||
use Spatie\SchemalessAttributes\SchemalessAttributesTrait;
|
use Spatie\SchemalessAttributes\SchemalessAttributesTrait;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
@ -69,7 +70,7 @@ class Server extends BaseModel
|
|||||||
|
|
||||||
static public function isUsable()
|
static public function isUsable()
|
||||||
{
|
{
|
||||||
return Server::ownedByCurrentTeam()->whereRelation('settings', 'is_reachable', true)->whereRelation('settings', 'is_usable', true)->whereRelation('settings', 'is_swarm_worker', false)->whereRelation('settings', 'is_build_server', false);
|
return Server::ownedByCurrentTeam()->whereRelation('settings', 'is_reachable', true)->whereRelation('settings', 'is_usable', true)->whereRelation('settings', 'is_swarm_worker', false)->whereRelation('settings', 'is_build_server', false)->whereRelation('settings', 'force_disabled', false);
|
||||||
}
|
}
|
||||||
|
|
||||||
static public function destinationsByServer(string $server_id)
|
static public function destinationsByServer(string $server_id)
|
||||||
@ -149,8 +150,31 @@ class Server extends BaseModel
|
|||||||
ray('skipping 1.2.3.4');
|
ray('skipping 1.2.3.4');
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
if ($this->settings->force_disabled === true) {
|
||||||
|
ray('force_disabled');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
public function isForceDisabled()
|
||||||
|
{
|
||||||
|
return $this->settings->force_disabled;
|
||||||
|
}
|
||||||
|
public function forceEnableServer()
|
||||||
|
{
|
||||||
|
$this->settings->update([
|
||||||
|
'force_disabled' => false,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
public function forceDisableServer()
|
||||||
|
{
|
||||||
|
$this->settings->update([
|
||||||
|
'force_disabled' => true,
|
||||||
|
]);
|
||||||
|
$sshKeyFileLocation = "id.root@{$this->uuid}";
|
||||||
|
Storage::disk('ssh-keys')->delete($sshKeyFileLocation);
|
||||||
|
Storage::disk('ssh-mux')->delete($this->muxFilename());
|
||||||
|
}
|
||||||
public function isServerReady(int $tries = 3)
|
public function isServerReady(int $tries = 3)
|
||||||
{
|
{
|
||||||
if ($this->skipServer()) {
|
if ($this->skipServer()) {
|
||||||
@ -374,7 +398,7 @@ class Server extends BaseModel
|
|||||||
}
|
}
|
||||||
public function isFunctional()
|
public function isFunctional()
|
||||||
{
|
{
|
||||||
return $this->settings->is_reachable && $this->settings->is_usable;
|
return $this->settings->is_reachable && $this->settings->is_usable && !$this->settings->force_disabled;
|
||||||
}
|
}
|
||||||
public function isLogDrainEnabled()
|
public function isLogDrainEnabled()
|
||||||
{
|
{
|
||||||
|
@ -48,7 +48,22 @@ class Team extends Model implements SendsDiscord, SendsEmail
|
|||||||
}
|
}
|
||||||
return explode(',', $recipients);
|
return explode(',', $recipients);
|
||||||
}
|
}
|
||||||
|
static public function serverLimitReached() {
|
||||||
|
$serverLimit = Team::serverLimit();
|
||||||
|
$team = currentTeam();
|
||||||
|
$servers = $team->servers->count();
|
||||||
|
return $servers >= $serverLimit;
|
||||||
|
}
|
||||||
|
public function serverOverflow() {
|
||||||
|
if ($this->serverLimit() < $this->servers->count()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
static public function serverLimit()
|
||||||
|
{
|
||||||
|
return Team::find(currentTeam()->id)->limits['serverLimit'];
|
||||||
|
}
|
||||||
public function limits(): Attribute
|
public function limits(): Attribute
|
||||||
{
|
{
|
||||||
return Attribute::make(
|
return Attribute::make(
|
||||||
@ -63,14 +78,19 @@ class Team extends Model implements SendsDiscord, SendsEmail
|
|||||||
$subscription = $subscription->type();
|
$subscription = $subscription->type();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$serverLimit = config('constants.limits.server')[strtolower($subscription)];
|
if ($this->custom_server_limit) {
|
||||||
|
$serverLimit = $this->custom_server_limit;
|
||||||
|
} else {
|
||||||
|
$serverLimit = config('constants.limits.server')[strtolower($subscription)];
|
||||||
|
}
|
||||||
$sharedEmailEnabled = config('constants.limits.email')[strtolower($subscription)];
|
$sharedEmailEnabled = config('constants.limits.email')[strtolower($subscription)];
|
||||||
return ['serverLimit' => $serverLimit, 'sharedEmailEnabled' => $sharedEmailEnabled];
|
return ['serverLimit' => $serverLimit, 'sharedEmailEnabled' => $sharedEmailEnabled];
|
||||||
}
|
}
|
||||||
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
public function environment_variables() {
|
public function environment_variables()
|
||||||
|
{
|
||||||
return $this->hasMany(SharedEnvironmentVariable::class)->whereNull('project_id')->whereNull('environment_id');
|
return $this->hasMany(SharedEnvironmentVariable::class)->whereNull('project_id')->whereNull('environment_id');
|
||||||
}
|
}
|
||||||
public function members()
|
public function members()
|
||||||
@ -130,7 +150,8 @@ class Team extends Model implements SendsDiscord, SendsEmail
|
|||||||
{
|
{
|
||||||
return $this->hasMany(S3Storage::class)->where('is_usable', true);
|
return $this->hasMany(S3Storage::class)->where('is_usable', true);
|
||||||
}
|
}
|
||||||
public function trialEnded() {
|
public function trialEnded()
|
||||||
|
{
|
||||||
foreach ($this->servers as $server) {
|
foreach ($this->servers as $server) {
|
||||||
$server->settings()->update([
|
$server->settings()->update([
|
||||||
'is_usable' => false,
|
'is_usable' => false,
|
||||||
@ -138,7 +159,8 @@ class Team extends Model implements SendsDiscord, SendsEmail
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public function trialEndedButSubscribed() {
|
public function trialEndedButSubscribed()
|
||||||
|
{
|
||||||
foreach ($this->servers as $server) {
|
foreach ($this->servers as $server) {
|
||||||
$server->settings()->update([
|
$server->settings()->update([
|
||||||
'is_usable' => true,
|
'is_usable' => true,
|
||||||
|
63
app/Notifications/Server/ForceDisabled.php
Normal file
63
app/Notifications/Server/ForceDisabled.php
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Notifications\Server;
|
||||||
|
|
||||||
|
use App\Models\Server;
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use App\Notifications\Channels\DiscordChannel;
|
||||||
|
use App\Notifications\Channels\EmailChannel;
|
||||||
|
use App\Notifications\Channels\TelegramChannel;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Notifications\Messages\MailMessage;
|
||||||
|
use Illuminate\Notifications\Notification;
|
||||||
|
|
||||||
|
class ForceDisabled extends Notification implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Queueable;
|
||||||
|
|
||||||
|
public $tries = 1;
|
||||||
|
public function __construct(public Server $server)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function via(object $notifiable): array
|
||||||
|
{
|
||||||
|
$channels = [];
|
||||||
|
$isEmailEnabled = isEmailEnabled($notifiable);
|
||||||
|
$isDiscordEnabled = data_get($notifiable, 'discord_enabled');
|
||||||
|
$isTelegramEnabled = data_get($notifiable, 'telegram_enabled');
|
||||||
|
|
||||||
|
if ($isDiscordEnabled) {
|
||||||
|
$channels[] = DiscordChannel::class;
|
||||||
|
}
|
||||||
|
if ($isEmailEnabled) {
|
||||||
|
$channels[] = EmailChannel::class;
|
||||||
|
}
|
||||||
|
if ($isTelegramEnabled) {
|
||||||
|
$channels[] = TelegramChannel::class;
|
||||||
|
}
|
||||||
|
return $channels;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toMail(): MailMessage
|
||||||
|
{
|
||||||
|
$mail = new MailMessage();
|
||||||
|
$mail->subject("Coolify: Server ({$this->server->name}) disabled because it is not paid!");
|
||||||
|
$mail->view('emails.server-force-disabled', [
|
||||||
|
'name' => $this->server->name,
|
||||||
|
]);
|
||||||
|
return $mail;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toDiscord(): string
|
||||||
|
{
|
||||||
|
$message = "Coolify: Server ({$this->server->name}) disabled because it is not paid!\n All automations and integrations are stopped.\nPlease update your subscription to enable the server again [here](https://app.coolify.io/subsciprtions).";
|
||||||
|
return $message;
|
||||||
|
}
|
||||||
|
public function toTelegram(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
"message" => "Coolify: Server ({$this->server->name}) disabled because it is not paid!\n All automations and integrations are stopped.\nPlease update your subscription to enable the server again [here](https://app.coolify.io/subsciprtions)."
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
63
app/Notifications/Server/ForceEnabled.php
Normal file
63
app/Notifications/Server/ForceEnabled.php
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Notifications\Server;
|
||||||
|
|
||||||
|
use App\Models\Server;
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use App\Notifications\Channels\DiscordChannel;
|
||||||
|
use App\Notifications\Channels\EmailChannel;
|
||||||
|
use App\Notifications\Channels\TelegramChannel;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Notifications\Messages\MailMessage;
|
||||||
|
use Illuminate\Notifications\Notification;
|
||||||
|
|
||||||
|
class ForceEnabled extends Notification implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Queueable;
|
||||||
|
|
||||||
|
public $tries = 1;
|
||||||
|
public function __construct(public Server $server)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function via(object $notifiable): array
|
||||||
|
{
|
||||||
|
$channels = [];
|
||||||
|
$isEmailEnabled = isEmailEnabled($notifiable);
|
||||||
|
$isDiscordEnabled = data_get($notifiable, 'discord_enabled');
|
||||||
|
$isTelegramEnabled = data_get($notifiable, 'telegram_enabled');
|
||||||
|
|
||||||
|
if ($isDiscordEnabled) {
|
||||||
|
$channels[] = DiscordChannel::class;
|
||||||
|
}
|
||||||
|
if ($isEmailEnabled) {
|
||||||
|
$channels[] = EmailChannel::class;
|
||||||
|
}
|
||||||
|
if ($isTelegramEnabled) {
|
||||||
|
$channels[] = TelegramChannel::class;
|
||||||
|
}
|
||||||
|
return $channels;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toMail(): MailMessage
|
||||||
|
{
|
||||||
|
$mail = new MailMessage();
|
||||||
|
$mail->subject("Coolify: Server ({$this->server->name}) enabled again!");
|
||||||
|
$mail->view('emails.server-force-enabled', [
|
||||||
|
'name' => $this->server->name,
|
||||||
|
]);
|
||||||
|
return $mail;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toDiscord(): string
|
||||||
|
{
|
||||||
|
$message = "Coolify: Server ({$this->server->name}) enabled again!";
|
||||||
|
return $message;
|
||||||
|
}
|
||||||
|
public function toTelegram(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
"message" => "Coolify: Server ({$this->server->name}) enabled again!"
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
@ -24,6 +24,12 @@ trait ExecuteRemoteCommand
|
|||||||
if ($this->server instanceof Server === false) {
|
if ($this->server instanceof Server === false) {
|
||||||
throw new \RuntimeException('Server is not set or is not an instance of Server model');
|
throw new \RuntimeException('Server is not set or is not an instance of Server model');
|
||||||
}
|
}
|
||||||
|
if ($this->server->settings->force_disabled) {
|
||||||
|
$this->application_deployment_queue->update([
|
||||||
|
'status' => ApplicationDeploymentStatus::FAILED->value,
|
||||||
|
]);
|
||||||
|
throw new \RuntimeException('Server is disabled');
|
||||||
|
}
|
||||||
$commandsText->each(function ($single_command) {
|
$commandsText->each(function ($single_command) {
|
||||||
$command = data_get($single_command, 'command') ?? $single_command[0] ?? null;
|
$command = data_get($single_command, 'command') ?? $single_command[0] ?? null;
|
||||||
if ($command === null) {
|
if ($command === null) {
|
||||||
|
@ -423,7 +423,7 @@ function convert_docker_run_to_compose(?string $custom_docker_run_options = null
|
|||||||
'--security-opt',
|
'--security-opt',
|
||||||
'--sysctl',
|
'--sysctl',
|
||||||
'--ulimit',
|
'--ulimit',
|
||||||
'--device'
|
'--device',
|
||||||
]);
|
]);
|
||||||
$mapping = collect([
|
$mapping = collect([
|
||||||
'--cap-add' => 'cap_add',
|
'--cap-add' => 'cap_add',
|
||||||
@ -435,6 +435,7 @@ function convert_docker_run_to_compose(?string $custom_docker_run_options = null
|
|||||||
'--init' => 'init',
|
'--init' => 'init',
|
||||||
'--ulimit' => 'ulimits',
|
'--ulimit' => 'ulimits',
|
||||||
'--privileged' => 'privileged',
|
'--privileged' => 'privileged',
|
||||||
|
'--ip' => 'ip',
|
||||||
]);
|
]);
|
||||||
foreach ($matches as $match) {
|
foreach ($matches as $match) {
|
||||||
$option = $match[1];
|
$option = $match[1];
|
||||||
|
@ -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.224',
|
'release' => '4.0.0-beta.225',
|
||||||
// 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'),
|
||||||
|
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
return '4.0.0-beta.224';
|
return '4.0.0-beta.225';
|
||||||
|
@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('teams', function (Blueprint $table) {
|
||||||
|
$table->integer('custom_server_limit')->nullable();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('teams', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('custom_server_limit');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('server_settings', function (Blueprint $table) {
|
||||||
|
$table->boolean('force_disabled')->default(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('server_settings', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('force_disabled');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
22
resources/views/components/banner.blade.php
Normal file
22
resources/views/components/banner.blade.php
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
@props(['closable' => true])
|
||||||
|
<div x-data="{
|
||||||
|
bannerVisible: false,
|
||||||
|
bannerVisibleAfter: 100,
|
||||||
|
}" x-show="bannerVisible" x-transition:enter="transition ease-out duration-200"
|
||||||
|
x-transition:enter-start="-translate-y-10" x-transition:enter-end="translate-y-0"
|
||||||
|
x-transition:leave="transition ease-in duration-100" x-transition:leave-start="translate-y-0"
|
||||||
|
x-transition:leave-end="-translate-y-10" x-init="setTimeout(() => { bannerVisible = true }, bannerVisibleAfter);"
|
||||||
|
class="relative z-50 w-full py-2 mx-auto duration-100 ease-out shadow-sm bg-coolgray-100 sm:py-0 sm:h-14" x-cloak>
|
||||||
|
<div class="flex items-center justify-between h-full px-3">
|
||||||
|
{{ $slot }}
|
||||||
|
@if ($closable)
|
||||||
|
<button @click="bannerVisible=false"
|
||||||
|
class="flex items-center flex-shrink-0 translate-x-1 ease-out duration-150 justify-center w-6 h-6 p-1.5 text-neutral-200 rounded-full hover:bg-coolgray-500">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
|
||||||
|
stroke="currentColor" class="w-full h-full">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -1,6 +1,6 @@
|
|||||||
<div class="flex flex-col items-center justify-center h-screen">
|
<div class="flex flex-col items-center justify-center h-screen">
|
||||||
<span class="text-xl font-bold text-white">You have reached the limit of {{ $name }} you can create.</span>
|
<span class="text-xl font-bold text-white">You have reached the limit of {{ $name }} you can create.</span>
|
||||||
<span>Please <a class="text-white underline "href="{{ route('team.index') }}">upgrade your
|
<span>Please <a class="text-white underline "href="{{ route('subscription.show') }}">upgrade your
|
||||||
subscription</a> to create more
|
subscription</a> to create more
|
||||||
{{ $name }}.</span>
|
{{ $name }}.</span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -188,21 +188,21 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="pt-16 lg:px-8 lg:pt-0 xl:px-14">
|
<div class="pt-16 lg:px-8 lg:pt-0 xl:px-14">
|
||||||
<h3 id="tier-ultimate" class="text-base font-semibold leading-7 text-white">Ultimate</h3>
|
<h3 id="tier-ultimate" class="text-base font-semibold leading-7 text-white">Ultimate</h3>
|
||||||
<p class="flex items-baseline mt-6 gap-x-1">
|
<p class="flex items-baseline mt-6 gap-x-1">
|
||||||
<span x-show="selected === 'monthly'" x-cloak>
|
<span x-show="selected === 'monthly'" x-cloak>
|
||||||
<span class="text-4xl font-bold tracking-tight text-white">$?</span>
|
<span class="text-4xl font-bold tracking-tight text-white">Custom</span>
|
||||||
<span class="text-sm font-semibold leading-6 ">/month + VAT</span>
|
{{-- <span class="text-sm font-semibold leading-6 ">pay-as-you-go</span> --}}
|
||||||
</span>
|
</span>
|
||||||
<span x-show="selected === 'yearly'" x-cloak>
|
<span x-show="selected === 'yearly'" x-cloak>
|
||||||
<span class="text-4xl font-bold tracking-tight text-white">$?</span>
|
<span class="text-4xl font-bold tracking-tight text-white">Custom</span>
|
||||||
<span class="text-sm font-semibold leading-6 ">/month + VAT</span>
|
{{-- <span class="text-sm font-semibold leading-6 ">/month + VAT</span> --}}
|
||||||
</span>
|
</span>
|
||||||
</p>
|
</p>
|
||||||
<span x-show="selected === 'monthly'" x-cloak>
|
<span x-show="selected === 'monthly'" x-cloak>
|
||||||
<span>billed monthly</span>
|
<span>pay-as-you-go</span>
|
||||||
</span>
|
</span>
|
||||||
<span x-show="selected === 'yearly'" x-cloak>
|
<span x-show="selected === 'yearly'" x-cloak>
|
||||||
<span>billed annually</span>
|
<span>pay-as-you-go</span>
|
||||||
</span>
|
</span>
|
||||||
@if ($showSubscribeButtons)
|
@if ($showSubscribeButtons)
|
||||||
@isset($ultimate)
|
@isset($ultimate)
|
||||||
@ -219,7 +219,7 @@
|
|||||||
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z"
|
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z"
|
||||||
clip-rule="evenodd" />
|
clip-rule="evenodd" />
|
||||||
</svg>
|
</svg>
|
||||||
Connect <span class="px-1 font-bold text-white">unlimited</span> servers
|
Connect <span class="px-1 font-bold text-white">10+</span> servers
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li class="flex gap-x-3">
|
<li class="flex gap-x-3">
|
||||||
|
@ -1,24 +1,18 @@
|
|||||||
<div>
|
<div class="flex h-full pr-4">
|
||||||
@if ($server->isFunctional())
|
<div class="flex flex-col w-48 gap-4 min-w-fit">
|
||||||
<div class="flex h-full pr-4">
|
<a class="{{ request()->routeIs('server.proxy') ? 'text-white' : '' }}"
|
||||||
<div class="flex flex-col w-48 gap-4 min-w-fit">
|
href="{{ route('server.proxy', $parameters) }}">
|
||||||
<a class="{{ request()->routeIs('server.proxy') ? 'text-white' : '' }}"
|
<button>Configuration</button>
|
||||||
href="{{ route('server.proxy', $parameters) }}">
|
</a>
|
||||||
<button>Configuration</button>
|
@if (data_get($server, 'proxy.type') !== 'NONE')
|
||||||
</a>
|
<a class="{{ request()->routeIs('server.proxy.dynamic-confs') ? 'text-white' : '' }}"
|
||||||
@if (data_get($server, 'proxy.type') !== 'NONE')
|
href="{{ route('server.proxy.dynamic-confs', $parameters) }}">
|
||||||
<a class="{{ request()->routeIs('server.proxy.dynamic-confs') ? 'text-white' : '' }}"
|
<button>Dynamic Configurations</button>
|
||||||
href="{{ route('server.proxy.dynamic-confs', $parameters) }}">
|
</a>
|
||||||
<button>Dynamic Configurations</button>
|
<a class="{{ request()->routeIs('server.proxy.logs') ? 'text-white' : '' }}"
|
||||||
</a>
|
href="{{ route('server.proxy.logs', $parameters) }}">
|
||||||
<a class="{{ request()->routeIs('server.proxy.logs') ? 'text-white' : '' }}"
|
<button>Logs</button>
|
||||||
href="{{ route('server.proxy.logs', $parameters) }}">
|
</a>
|
||||||
<button>Logs</button>
|
@endif
|
||||||
</a>
|
</div>
|
||||||
@endif
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@else
|
|
||||||
<div>Server is not validated. Validate first.</div>
|
|
||||||
@endif
|
|
||||||
</div>
|
</div>
|
||||||
|
5
resources/views/emails/server-force-disabled.blade.php
Normal file
5
resources/views/emails/server-force-disabled.blade.php
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<x-emails.layout>
|
||||||
|
Your server ({{ $name }}) disabled because it is not paid! All automations and integrations are stopped.
|
||||||
|
|
||||||
|
Please update your subscription to enable the server again [here](https://app.coolify.io/subsciprtions).
|
||||||
|
</x-emails.layout>
|
3
resources/views/emails/server-force-enabled.blade.php
Normal file
3
resources/views/emails/server-force-enabled.blade.php
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<x-emails.layout>
|
||||||
|
Your server ({{ $name }}) is enabled again!
|
||||||
|
</x-emails.layout>
|
@ -7,7 +7,7 @@
|
|||||||
<magic-bar></magic-bar>
|
<magic-bar></magic-bar>
|
||||||
</div>
|
</div>
|
||||||
@endpersist
|
@endpersist
|
||||||
<livewire:sponsorship />
|
<livewire:layout-popups />
|
||||||
@auth
|
@auth
|
||||||
<livewire:realtime-connection />
|
<livewire:realtime-connection />
|
||||||
@endauth
|
@endauth
|
||||||
|
@ -11,4 +11,13 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
@if (currentTeam()->serverOverflow())
|
||||||
|
<x-banner :closable=false>
|
||||||
|
<div><span class="font-bold text-red-500">WARNING:</span> The number of active servers exceeds the limit
|
||||||
|
covered by your payment. If not resolved, some of your servers <span class="font-bold text-red-500">will
|
||||||
|
be deactivated</span>. Visit <a href="{{ route('subscription.show') }}"
|
||||||
|
class="text-white underline">/subscription</a> to update your subscription or remove some servers.
|
||||||
|
</div>
|
||||||
|
</x-banner>
|
||||||
|
@endif
|
||||||
</div>
|
</div>
|
@ -52,8 +52,8 @@
|
|||||||
@foreach (decode_remote_command_output($application_deployment_queue) as $line)
|
@foreach (decode_remote_command_output($application_deployment_queue) as $line)
|
||||||
<div @class([
|
<div @class([
|
||||||
'font-mono',
|
'font-mono',
|
||||||
'text-warning' => $line['hidden'],
|
'text-warning whitespace-pre-line' => $line['hidden'],
|
||||||
'text-red-500' => $line['type'] == 'stderr',
|
'text-red-500 whitespace-pre-line' => $line['type'] == 'stderr',
|
||||||
])>[{{ $line['timestamp'] }}] @if ($line['hidden'])
|
])>[{{ $line['timestamp'] }}] @if ($line['hidden'])
|
||||||
<br>COMMAND: <br>{{ $line['command'] }} <br><br>OUTPUT:
|
<br>COMMAND: <br>{{ $line['command'] }} <br><br>OUTPUT:
|
||||||
@endif @if (str($line['output'])->contains('http://') || str($line['output'])->contains('https://'))
|
@endif @if (str($line['output'])->contains('http://') || str($line['output'])->contains('https://'))
|
||||||
|
@ -7,10 +7,10 @@
|
|||||||
back!
|
back!
|
||||||
</div>
|
</div>
|
||||||
@if ($server->definedResources()->count() > 0)
|
@if ($server->definedResources()->count() > 0)
|
||||||
|
<div class="pb-2 text-red-500">You need to delete all resources before deleting this server.</div>
|
||||||
<x-new-modal disabled isErrorButton buttonTitle="Delete">
|
<x-new-modal disabled isErrorButton buttonTitle="Delete">
|
||||||
This server will be deleted. It is not reversible. <br>Please think again.
|
This server will be deleted. It is not reversible. <br>Please think again.
|
||||||
</x-new-modal>
|
</x-new-modal>
|
||||||
<div>You need to delete all resources before deleting this server.</div>
|
|
||||||
@else
|
@else
|
||||||
<x-new-modal isErrorButton buttonTitle="Delete">
|
<x-new-modal isErrorButton buttonTitle="Delete">
|
||||||
This server will be deleted. It is not reversible. <br>Please think again.
|
This server will be deleted. It is not reversible. <br>Please think again.
|
||||||
|
@ -47,6 +47,10 @@
|
|||||||
Validate Server
|
Validate Server
|
||||||
</x-forms.button>
|
</x-forms.button>
|
||||||
@endif
|
@endif
|
||||||
|
@if ($server->isForceDisabled() && isCloud())
|
||||||
|
<div class="pt-4 font-bold text-red-500">The system has disabled the server because you have exceeded the
|
||||||
|
number of servers for which you have paid.</div>
|
||||||
|
@endif
|
||||||
<div class="flex flex-col gap-2 pt-4">
|
<div class="flex flex-col gap-2 pt-4">
|
||||||
<div class="flex flex-col w-full gap-2 lg:flex-row">
|
<div class="flex flex-col w-full gap-2 lg:flex-row">
|
||||||
<x-forms.input id="server.name" label="Name" required />
|
<x-forms.input id="server.name" label="Name" required />
|
||||||
|
@ -1,18 +1,18 @@
|
|||||||
<div>
|
<div>
|
||||||
<div class="flex items-start gap-2">
|
<div class="flex items-start gap-2">
|
||||||
<h1>Servers</h1>
|
<h1>Servers</h1>
|
||||||
<a class="text-white hover:no-underline" href="{{ route('server.create') }}">
|
<a class="text-white hover:no-underline" href="{{ route('server.create') }}">
|
||||||
<x-forms.button class="btn">+ Add</x-forms.button>
|
<x-forms.button class="btn">+ Add</x-forms.button>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="subtitle ">All Servers</div>
|
<div class="subtitle ">All Servers</div>
|
||||||
<div class="grid gap-2 lg:grid-cols-2">
|
<div class="grid gap-2 lg:grid-cols-2">
|
||||||
@forelse ($servers as $server)
|
@forelse ($servers as $server)
|
||||||
<a href="{{ route('server.show', ['server_uuid' => data_get($server, 'uuid')]) }}"
|
<a href="{{ route('server.show', ['server_uuid' => data_get($server, 'uuid')]) }}"
|
||||||
@class([
|
@class([
|
||||||
'gap-2 border cursor-pointer box group',
|
'gap-2 border cursor-pointer box group',
|
||||||
'border-transparent' => $server->settings->is_reachable,
|
'border-transparent' => $server->settings->is_reachable && $server->settings->is_usable && !$server->settings->force_disabled,
|
||||||
'border-red-500' => !$server->settings->is_reachable,
|
'border-red-500' => !$server->settings->is_reachable || $server->settings->force_disabled,
|
||||||
])>
|
])>
|
||||||
<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">
|
||||||
@ -30,6 +30,9 @@
|
|||||||
@if (!$server->settings->is_usable)
|
@if (!$server->settings->is_usable)
|
||||||
<span>Not usable by Coolify</span>
|
<span>Not usable by Coolify</span>
|
||||||
@endif
|
@endif
|
||||||
|
@if ($server->settings->force_disabled)
|
||||||
|
<span>Disabled by the system</span>
|
||||||
|
@endif
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-1"></div>
|
<div class="flex-1"></div>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<x-server.navbar :server="$server" :parameters="$parameters" />
|
<x-server.navbar :server="$server" :parameters="$parameters" />
|
||||||
|
<x-server.sidebar :server="$server" :parameters="$parameters" />
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<x-server.sidebar :server="$server" :parameters="$parameters" />
|
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
@if ($server->isFunctional())
|
@if ($server->isFunctional())
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
@ -48,7 +48,6 @@
|
|||||||
<div wire:loading.remove> No dynamic configurations found.</div>
|
<div wire:loading.remove> No dynamic configurations found.</div>
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
<div>
|
<div>
|
||||||
<x-server.navbar :server="$server" :parameters="$parameters" />
|
<x-server.navbar :server="$server" :parameters="$parameters" />
|
||||||
<div class="flex gap-2">
|
@if ($server->isFunctional())
|
||||||
<x-server.sidebar :server="$server" :parameters="$parameters" />
|
<div class="flex gap-2">
|
||||||
<div class="w-full">
|
<x-server.sidebar :server="$server" :parameters="$parameters" />
|
||||||
@if ($server->isFunctional())
|
<div class="w-full">
|
||||||
<livewire:server.proxy :server="$server" />
|
<livewire:server.proxy :server="$server" />
|
||||||
@endif
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
@else
|
||||||
|
<div>Server is not validated. Validate first.</div>
|
||||||
|
@endif
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
<div>
|
<div>
|
||||||
@if (subscriptionProvider() === 'stripe')
|
@if (subscriptionProvider() === 'stripe')
|
||||||
<div class="pt-4">
|
<div class="pt-4">
|
||||||
<div class="pb-4">Your current Plan is: <strong
|
<h2>Your current plan</h2>
|
||||||
|
<div class="pb-4">Tier: <strong
|
||||||
class="text-warning">{{ data_get(currentTeam(), 'subscription')->type() }}</strong></div>
|
class="text-warning">{{ data_get(currentTeam(), 'subscription')->type() }}</strong></div>
|
||||||
|
|
||||||
@if (currentTeam()->subscription->stripe_cancel_at_period_end)
|
@if (currentTeam()->subscription->stripe_cancel_at_period_end)
|
||||||
@ -10,13 +11,19 @@
|
|||||||
<div>Subscription is active. Last invoice is
|
<div>Subscription is active. Last invoice is
|
||||||
{{ currentTeam()->subscription->stripe_invoice_paid ? 'paid' : 'not paid' }}.</div>
|
{{ currentTeam()->subscription->stripe_invoice_paid ? 'paid' : 'not paid' }}.</div>
|
||||||
@endif
|
@endif
|
||||||
<h3 class="pt-4">Limits</h3>
|
<div>Number of paid servers: {{ $server_limits }}</div>
|
||||||
<div>Server: {{ $server_limits }}</div>
|
<div>Currently active servers: {{ currentTeam()->servers->count() }}</div>
|
||||||
<h3 class="pt-4">Actions</h3>
|
@if (currentTeam()->serverOverflow())
|
||||||
|
<div class="py-4"><span class="font-bold text-red-500">WARNING:</span> You must delete
|
||||||
|
{{ currentTeam()->servers->count() - $server_limits }} servers,
|
||||||
|
or upgrade your subscription. {{ currentTeam()->servers->count() - $server_limits }} servers will be
|
||||||
|
deactivated.</div>
|
||||||
|
@endif
|
||||||
|
<h2 class="pt-4">Manage your subscription</h2>
|
||||||
<div class="pb-4">Cancel, upgrade or downgrade your subscription.</div>
|
<div class="pb-4">Cancel, upgrade or downgrade your subscription.</div>
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<x-forms.button wire:click='stripeCustomerPortal'>Manage your subscription on <svg
|
<x-forms.button wire:click='stripeCustomerPortal'>Go to <svg xmlns="http://www.w3.org/2000/svg"
|
||||||
xmlns="http://www.w3.org/2000/svg" class="w-12" viewBox="0 0 512 214">
|
class="w-12" viewBox="0 0 512 214">
|
||||||
<path fill="#635BFF"
|
<path fill="#635BFF"
|
||||||
d="M512 110.08c0-36.409-17.636-65.138-51.342-65.138c-33.85 0-54.33 28.73-54.33 64.854c0 42.808 24.179 64.426 58.88 64.426c16.925 0 29.725-3.84 39.396-9.244v-28.445c-9.67 4.836-20.764 7.823-34.844 7.823c-13.796 0-26.027-4.836-27.591-21.618h69.547c0-1.85.284-9.245.284-12.658Zm-70.258-13.511c0-16.071 9.814-22.756 18.774-22.756c8.675 0 17.92 6.685 17.92 22.756h-36.694Zm-90.31-51.627c-13.939 0-22.899 6.542-27.876 11.094l-1.85-8.818h-31.288v165.83l35.555-7.537l.143-40.249c5.12 3.698 12.657 8.96 25.173 8.96c25.458 0 48.64-20.48 48.64-65.564c-.142-41.245-23.609-63.716-48.498-63.716Zm-8.534 97.991c-8.391 0-13.37-2.986-16.782-6.684l-.143-52.765c3.698-4.124 8.818-6.968 16.925-6.968c12.942 0 21.902 14.506 21.902 33.137c0 19.058-8.818 33.28-21.902 33.28ZM241.493 36.551l35.698-7.68V0l-35.698 7.538V36.55Zm0 10.809h35.698v124.444h-35.698V47.36Zm-38.257 10.524L200.96 47.36h-30.72v124.444h35.556V87.467c8.39-10.951 22.613-8.96 27.022-7.396V47.36c-4.551-1.707-21.191-4.836-29.582 10.524Zm-71.112-41.386l-34.702 7.395l-.142 113.92c0 21.05 15.787 36.551 36.836 36.551c11.662 0 20.195-2.133 24.888-4.693V140.8c-4.55 1.849-27.022 8.391-27.022-12.658V77.653h27.022V47.36h-27.022l.142-30.862ZM35.982 83.484c0-5.546 4.551-7.68 12.09-7.68c10.808 0 24.461 3.272 35.27 9.103V51.484c-11.804-4.693-23.466-6.542-35.27-6.542C19.2 44.942 0 60.018 0 85.192c0 39.252 54.044 32.995 54.044 49.92c0 6.541-5.688 8.675-13.653 8.675c-11.804 0-26.88-4.836-38.827-11.378v33.849c13.227 5.689 26.596 8.106 38.827 8.106c29.582 0 49.92-14.648 49.92-40.106c-.142-42.382-54.329-34.845-54.329-50.774Z" />
|
d="M512 110.08c0-36.409-17.636-65.138-51.342-65.138c-33.85 0-54.33 28.73-54.33 64.854c0 42.808 24.179 64.426 58.88 64.426c16.925 0 29.725-3.84 39.396-9.244v-28.445c-9.67 4.836-20.764 7.823-34.844 7.823c-13.796 0-26.027-4.836-27.591-21.618h69.547c0-1.85.284-9.245.284-12.658Zm-70.258-13.511c0-16.071 9.814-22.756 18.774-22.756c8.675 0 17.92 6.685 17.92 22.756h-36.694Zm-90.31-51.627c-13.939 0-22.899 6.542-27.876 11.094l-1.85-8.818h-31.288v165.83l35.555-7.537l.143-40.249c5.12 3.698 12.657 8.96 25.173 8.96c25.458 0 48.64-20.48 48.64-65.564c-.142-41.245-23.609-63.716-48.498-63.716Zm-8.534 97.991c-8.391 0-13.37-2.986-16.782-6.684l-.143-52.765c3.698-4.124 8.818-6.968 16.925-6.968c12.942 0 21.902 14.506 21.902 33.137c0 19.058-8.818 33.28-21.902 33.28ZM241.493 36.551l35.698-7.68V0l-35.698 7.538V36.55Zm0 10.809h35.698v124.444h-35.698V47.36Zm-38.257 10.524L200.96 47.36h-30.72v124.444h35.556V87.467c8.39-10.951 22.613-8.96 27.022-7.396V47.36c-4.551-1.707-21.191-4.836-29.582 10.524Zm-71.112-41.386l-34.702 7.395l-.142 113.92c0 21.05 15.787 36.551 36.836 36.551c11.662 0 20.195-2.133 24.888-4.693V140.8c-4.55 1.849-27.022 8.391-27.022-12.658V77.653h27.022V47.36h-27.022l.142-30.862ZM35.982 83.484c0-5.546 4.551-7.68 12.09-7.68c10.808 0 24.461 3.272 35.27 9.103V51.484c-11.804-4.693-23.466-6.542-35.27-6.542C19.2 44.942 0 60.018 0 85.192c0 39.252 54.044 32.995 54.044 49.92c0 6.541-5.688 8.675-13.653 8.675c-11.804 0-26.88-4.836-38.827-11.378v33.849c13.227 5.689 26.596 8.106 38.827 8.106c29.582 0 49.92-14.648 49.92-40.106c-.142-42.382-54.329-34.845-54.329-50.774Z" />
|
||||||
</svg></x-forms.button>
|
</svg></x-forms.button>
|
||||||
|
@ -22,16 +22,13 @@
|
|||||||
</x-forms.button>
|
</x-forms.button>
|
||||||
</x-slot:pro>
|
</x-slot:pro>
|
||||||
<x-slot:ultimate>
|
<x-slot:ultimate>
|
||||||
<x-forms.button x-show="selected === 'monthly'" x-cloak aria-describedby="tier-ultimate"
|
<x-forms.button x-show="selected === 'monthly'" x-cloak aria-describedby="tier-ultimate" class="w-full h-10 buyme"
|
||||||
class="w-full h-10 buyme"><a class="text-white hover:no-underline" href="{{ config('coolify.contact') }}"
|
wire:click="subscribeStripe('ultimate-monthly')">
|
||||||
target="_blank">
|
{{ $isTrial ? 'Start Trial' : 'Subscribe' }}
|
||||||
Contact Us</a>
|
|
||||||
</x-forms.button>
|
</x-forms.button>
|
||||||
|
|
||||||
<x-forms.button x-show="selected === 'yearly'" x-cloak aria-describedby="tier-ultimate"
|
<x-forms.button x-show="selected === 'yearly'" x-cloak aria-describedby="tier-ultimate" class="w-full h-10 buyme"
|
||||||
class="w-full h-10 buyme"><a class="text-white hover:no-underline" href="{{ config('coolify.contact') }}"
|
wire:click="subscribeStripe('ultimate-yearly')"> {{ $isTrial ? 'Start Trial' : 'Subscribe' }}
|
||||||
target="_blank">
|
|
||||||
Contact Us</a>
|
|
||||||
</x-forms.button>
|
</x-forms.button>
|
||||||
</x-slot:ultimate>
|
</x-slot:ultimate>
|
||||||
@endif
|
@endif
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
use App\Http\Controllers\Api\Deploy;
|
use App\Http\Controllers\Api\APIDeploy as Deploy;
|
||||||
use App\Http\Controllers\Api\Project;
|
use App\Http\Controllers\Api\APIProject as Project;
|
||||||
use App\Http\Controllers\Api\Server;
|
use App\Http\Controllers\Api\APIServer as Server;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Http;
|
use Illuminate\Support\Facades\Http;
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
use App\Http\Controllers\Api\Server as ApiServer;
|
|
||||||
use App\Models\GitlabApp;
|
use App\Models\GitlabApp;
|
||||||
use App\Models\PrivateKey;
|
use App\Models\PrivateKey;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
use App\Enums\ProcessStatus;
|
use App\Enums\ProcessStatus;
|
||||||
use App\Jobs\ApplicationPullRequestUpdateJob;
|
use App\Jobs\ApplicationPullRequestUpdateJob;
|
||||||
use App\Jobs\GithubAppPermissionJob;
|
use App\Jobs\GithubAppPermissionJob;
|
||||||
|
use App\Jobs\ServerLimitCheckJob;
|
||||||
use App\Jobs\SubscriptionInvoiceFailedJob;
|
use App\Jobs\SubscriptionInvoiceFailedJob;
|
||||||
use App\Jobs\SubscriptionTrialEndedJob;
|
use App\Jobs\SubscriptionTrialEndedJob;
|
||||||
use App\Jobs\SubscriptionTrialEndsSoonJob;
|
use App\Jobs\SubscriptionTrialEndsSoonJob;
|
||||||
@ -875,6 +876,15 @@ Route::post('/payments/stripe/events', function () {
|
|||||||
$alreadyCancelAtPeriodEnd = data_get($subscription, 'stripe_cancel_at_period_end');
|
$alreadyCancelAtPeriodEnd = data_get($subscription, 'stripe_cancel_at_period_end');
|
||||||
$feedback = data_get($data, 'cancellation_details.feedback');
|
$feedback = data_get($data, 'cancellation_details.feedback');
|
||||||
$comment = data_get($data, 'cancellation_details.comment');
|
$comment = data_get($data, 'cancellation_details.comment');
|
||||||
|
$lookup_key = data_get($data, 'items.data.0.price.lookup_key');
|
||||||
|
if (str($lookup_key)->contains('ultimate')) {
|
||||||
|
$quantity = data_get($data, 'items.data.0.quantity', 10);
|
||||||
|
$team = data_get($subscription, 'team');
|
||||||
|
$team->update([
|
||||||
|
'custom_server_limit' => $quantity,
|
||||||
|
]);
|
||||||
|
ServerLimitCheckJob::dispatch($team);
|
||||||
|
}
|
||||||
$subscription->update([
|
$subscription->update([
|
||||||
'stripe_feedback' => $feedback,
|
'stripe_feedback' => $feedback,
|
||||||
'stripe_comment' => $comment,
|
'stripe_comment' => $comment,
|
||||||
|
@ -16,6 +16,7 @@ services:
|
|||||||
- DB_USERNAME=$SERVICE_USER_MYSQL
|
- DB_USERNAME=$SERVICE_USER_MYSQL
|
||||||
- DB_PASSWORD=$SERVICE_PASSWORD_MYSQL
|
- DB_PASSWORD=$SERVICE_PASSWORD_MYSQL
|
||||||
- STATIC_CRON_TOKEN=$SERVICE_BASE64_CRONTOKEN
|
- STATIC_CRON_TOKEN=$SERVICE_BASE64_CRONTOKEN
|
||||||
|
- TRUSTED_PROXIES=*
|
||||||
volumes:
|
volumes:
|
||||||
- firefly-upload:/var/www/html/storage/upload
|
- firefly-upload:/var/www/html/storage/upload
|
||||||
healthcheck:
|
healthcheck:
|
||||||
@ -49,8 +50,20 @@ services:
|
|||||||
retries: 10
|
retries: 10
|
||||||
volumes:
|
volumes:
|
||||||
- firefly-mysql-data:/var/lib/mysql
|
- firefly-mysql-data:/var/lib/mysql
|
||||||
# cron:
|
cron:
|
||||||
# image: alpine
|
image: alpine
|
||||||
# command: sh -c "echo \"0 3 * * * wget -qO- http://app:8080/api/v1/cron/$STATIC_CRON_TOKEN\" | crontab - && crond -f -L /dev/stdout"
|
entrypoint: ["/entrypoint.sh"]
|
||||||
# environment:
|
volumes:
|
||||||
# - STATIC_CRON_TOKEN=$SERVICE_PASSWORD_32_CRONTOKEN
|
- type: bind
|
||||||
|
source: ./entrypoint.sh
|
||||||
|
target: /entrypoint.sh
|
||||||
|
content: |
|
||||||
|
#!/bin/sh
|
||||||
|
# Substitute the environment variable into the cron command
|
||||||
|
CRON_COMMAND="0 3 * * * wget -qO- http://firefly:8080/api/v1/cron/${STATIC_CRON_TOKEN}"
|
||||||
|
# Add the cron command to the crontab
|
||||||
|
echo "$CRON_COMMAND" | crontab -
|
||||||
|
# Start the cron daemon in the foreground with logging to stdout
|
||||||
|
crond -f -L /dev/stdout
|
||||||
|
environment:
|
||||||
|
- STATIC_CRON_TOKEN=$SERVICE_BASE64_CRONTOKEN
|
||||||
|
@ -195,7 +195,7 @@
|
|||||||
"firefly": {
|
"firefly": {
|
||||||
"documentation": "https:\/\/firefly-iii.org",
|
"documentation": "https:\/\/firefly-iii.org",
|
||||||
"slogan": "A personal finances manager that can help you save money.",
|
"slogan": "A personal finances manager that can help you save money.",
|
||||||
"compose": "c2VydmljZXM6CiAgZmlyZWZseToKICAgIGltYWdlOiAnZmlyZWZseWlpaS9jb3JlOmxhdGVzdCcKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9GSVJFRkxZCiAgICAgIC0gQVBQX0tFWT0kU0VSVklDRV9CQVNFNjRfQVBQS0VZCiAgICAgIC0gREJfSE9TVD1teXNxbAogICAgICAtIERCX1BPUlQ9MzMwNgogICAgICAtIERCX0NPTk5FQ1RJT049bXlzcWwKICAgICAgLSAnREJfREFUQUJBU0U9JHtNWVNRTF9EQVRBQkFTRTotZmlyZWZseX0nCiAgICAgIC0gREJfVVNFUk5BTUU9JFNFUlZJQ0VfVVNFUl9NWVNRTAogICAgICAtIERCX1BBU1NXT1JEPSRTRVJWSUNFX1BBU1NXT1JEX01ZU1FMCiAgICAgIC0gU1RBVElDX0NST05fVE9LRU49JFNFUlZJQ0VfQkFTRTY0X0NST05UT0tFTgogICAgdm9sdW1lczoKICAgICAgLSAnZmlyZWZseS11cGxvYWQ6L3Zhci93d3cvaHRtbC9zdG9yYWdlL3VwbG9hZCcKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSBjdXJsCiAgICAgICAgLSAnLWYnCiAgICAgICAgLSAnaHR0cDovL2xvY2FsaG9zdDo4MDgwJwogICAgICBpbnRlcnZhbDogNXMKICAgICAgdGltZW91dDogMjBzCiAgICAgIHJldHJpZXM6IDEwCiAgICBkZXBlbmRzX29uOgogICAgICBteXNxbDoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogIG15c3FsOgogICAgaW1hZ2U6ICdtYXJpYWRiOmx0cycKICAgIGVudmlyb25tZW50OgogICAgICAtICdNWVNRTF9VU0VSPSR7U0VSVklDRV9VU0VSX01ZU1FMfScKICAgICAgLSAnTVlTUUxfUEFTU1dPUkQ9JHtTRVJWSUNFX1BBU1NXT1JEX01ZU1FMfScKICAgICAgLSAnTVlTUUxfREFUQUJBU0U9JHtNWVNRTF9EQVRBQkFTRTotZmlyZWZseX0nCiAgICAgIC0gJ01ZU1FMX1JPT1RfUEFTU1dPUkQ9JHtTRVJWSUNFX1BBU1NXT1JEX01ZU1FMUk9PVH0nCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gbXlzcWxhZG1pbgogICAgICAgIC0gcGluZwogICAgICAgIC0gJy1oJwogICAgICAgIC0gbG9jYWxob3N0CiAgICAgICAgLSAnLXVyb290JwogICAgICAgIC0gJy1wJHtTRVJWSUNFX1BBU1NXT1JEX01ZU1FMUk9PVH0nCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiAyMHMKICAgICAgcmV0cmllczogMTAKICAgIHZvbHVtZXM6CiAgICAgIC0gJ2ZpcmVmbHktbXlzcWwtZGF0YTovdmFyL2xpYi9teXNxbCcK",
|
"compose": "c2VydmljZXM6CiAgZmlyZWZseToKICAgIGltYWdlOiAnZmlyZWZseWlpaS9jb3JlOmxhdGVzdCcKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9GSVJFRkxZCiAgICAgIC0gQVBQX0tFWT0kU0VSVklDRV9CQVNFNjRfQVBQS0VZCiAgICAgIC0gREJfSE9TVD1teXNxbAogICAgICAtIERCX1BPUlQ9MzMwNgogICAgICAtIERCX0NPTk5FQ1RJT049bXlzcWwKICAgICAgLSAnREJfREFUQUJBU0U9JHtNWVNRTF9EQVRBQkFTRTotZmlyZWZseX0nCiAgICAgIC0gREJfVVNFUk5BTUU9JFNFUlZJQ0VfVVNFUl9NWVNRTAogICAgICAtIERCX1BBU1NXT1JEPSRTRVJWSUNFX1BBU1NXT1JEX01ZU1FMCiAgICAgIC0gU1RBVElDX0NST05fVE9LRU49JFNFUlZJQ0VfQkFTRTY0X0NST05UT0tFTgogICAgICAtICdUUlVTVEVEX1BST1hJRVM9KicKICAgIHZvbHVtZXM6CiAgICAgIC0gJ2ZpcmVmbHktdXBsb2FkOi92YXIvd3d3L2h0bWwvc3RvcmFnZS91cGxvYWQnCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gY3VybAogICAgICAgIC0gJy1mJwogICAgICAgIC0gJ2h0dHA6Ly9sb2NhbGhvc3Q6ODA4MCcKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHRpbWVvdXQ6IDIwcwogICAgICByZXRyaWVzOiAxMAogICAgZGVwZW5kc19vbjoKICAgICAgbXlzcWw6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICBteXNxbDoKICAgIGltYWdlOiAnbWFyaWFkYjpsdHMnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSAnTVlTUUxfVVNFUj0ke1NFUlZJQ0VfVVNFUl9NWVNRTH0nCiAgICAgIC0gJ01ZU1FMX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9NWVNRTH0nCiAgICAgIC0gJ01ZU1FMX0RBVEFCQVNFPSR7TVlTUUxfREFUQUJBU0U6LWZpcmVmbHl9JwogICAgICAtICdNWVNRTF9ST09UX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9NWVNRTFJPT1R9JwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIG15c3FsYWRtaW4KICAgICAgICAtIHBpbmcKICAgICAgICAtICctaCcKICAgICAgICAtIGxvY2FsaG9zdAogICAgICAgIC0gJy11cm9vdCcKICAgICAgICAtICctcCR7U0VSVklDRV9QQVNTV09SRF9NWVNRTFJPT1R9JwogICAgICBpbnRlcnZhbDogNXMKICAgICAgdGltZW91dDogMjBzCiAgICAgIHJldHJpZXM6IDEwCiAgICB2b2x1bWVzOgogICAgICAtICdmaXJlZmx5LW15c3FsLWRhdGE6L3Zhci9saWIvbXlzcWwnCiAgY3JvbjoKICAgIGltYWdlOiBhbHBpbmUKICAgIGVudHJ5cG9pbnQ6CiAgICAgIC0gL2VudHJ5cG9pbnQuc2gKICAgIHZvbHVtZXM6CiAgICAgIC0KICAgICAgICB0eXBlOiBiaW5kCiAgICAgICAgc291cmNlOiAuL2VudHJ5cG9pbnQuc2gKICAgICAgICB0YXJnZXQ6IC9lbnRyeXBvaW50LnNoCiAgICAgICAgY29udGVudDogIiMhL2Jpbi9zaFxuIyBTdWJzdGl0dXRlIHRoZSBlbnZpcm9ubWVudCB2YXJpYWJsZSBpbnRvIHRoZSBjcm9uIGNvbW1hbmRcbkNST05fQ09NTUFORD1cIjAgMyAqICogKiB3Z2V0IC1xTy0gaHR0cDovL2ZpcmVmbHk6ODA4MC9hcGkvdjEvY3Jvbi8ke1NUQVRJQ19DUk9OX1RPS0VOfVwiXG4jIEFkZCB0aGUgY3JvbiBjb21tYW5kIHRvIHRoZSBjcm9udGFiXG5lY2hvIFwiJENST05fQ09NTUFORFwiIHwgY3JvbnRhYiAtXG4jIFN0YXJ0IHRoZSBjcm9uIGRhZW1vbiBpbiB0aGUgZm9yZWdyb3VuZCB3aXRoIGxvZ2dpbmcgdG8gc3Rkb3V0XG5jcm9uZCAtZiAtTCAvZGV2L3N0ZG91dCIKICAgIGVudmlyb25tZW50OgogICAgICAtIFNUQVRJQ19DUk9OX1RPS0VOPSRTRVJWSUNFX0JBU0U2NF9DUk9OVE9LRU4K",
|
||||||
"tags": [
|
"tags": [
|
||||||
"finance",
|
"finance",
|
||||||
"money",
|
"money",
|
||||||
|
@ -8,6 +8,15 @@ test('ConvertCapAdd', function () {
|
|||||||
])->ray();
|
])->ray();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('ConvertIp', function () {
|
||||||
|
$input = '--cap-add=NET_ADMIN --cap-add=NET_RAW --cap-add SYS_ADMIN --ip 127.0.0.1 --ip 127.0.0.2';
|
||||||
|
$output = convert_docker_run_to_compose($input);
|
||||||
|
expect($output)->toBe([
|
||||||
|
'cap_add' => ['NET_ADMIN', 'NET_RAW', 'SYS_ADMIN'],
|
||||||
|
'ip' => ['127.0.0.1', '127.0.0.2']
|
||||||
|
])->ray();
|
||||||
|
});
|
||||||
|
|
||||||
test('ConvertPrivilegedAndInit', function () {
|
test('ConvertPrivilegedAndInit', function () {
|
||||||
$input = '---privileged --init';
|
$input = '---privileged --init';
|
||||||
$output = convert_docker_run_to_compose($input);
|
$output = convert_docker_run_to_compose($input);
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
"version": "3.12.36"
|
"version": "3.12.36"
|
||||||
},
|
},
|
||||||
"v4": {
|
"v4": {
|
||||||
"version": "4.0.0-beta.224"
|
"version": "4.0.0-beta.225"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user