commit
c735ff545e
2
.github/workflows/development-build.yml
vendored
2
.github/workflows/development-build.yml
vendored
@ -52,7 +52,7 @@ jobs:
|
|||||||
push: true
|
push: true
|
||||||
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:next-aarch64
|
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:next-aarch64
|
||||||
merge-manifest:
|
merge-manifest:
|
||||||
runs-on: [self-hosted, x64]
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
packages: write
|
packages: write
|
||||||
|
2
.github/workflows/production-build.yml
vendored
2
.github/workflows/production-build.yml
vendored
@ -10,7 +10,7 @@ env:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
amd64:
|
amd64:
|
||||||
runs-on: ubuntu-latest
|
runs-on: [self-hosted, x64]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- name: Login to ghcr.io
|
- name: Login to ghcr.io
|
||||||
|
@ -73,7 +73,7 @@ class RunRemoteProcess
|
|||||||
$this->time_start = hrtime(true);
|
$this->time_start = hrtime(true);
|
||||||
|
|
||||||
$status = ProcessStatus::IN_PROGRESS;
|
$status = ProcessStatus::IN_PROGRESS;
|
||||||
$processResult = processWithEnv()->forever()->run($this->getCommand(), $this->handleOutput(...));
|
$processResult = Process::forever()->run($this->getCommand(), $this->handleOutput(...));
|
||||||
|
|
||||||
if ($this->activity->properties->get('status') === ProcessStatus::ERROR->value) {
|
if ($this->activity->properties->get('status') === ProcessStatus::ERROR->value) {
|
||||||
$status = ProcessStatus::ERROR;
|
$status = ProcessStatus::ERROR;
|
||||||
|
@ -16,10 +16,7 @@ class CheckConfigurationSync
|
|||||||
|
|
||||||
if ($reset || is_null($proxy_configuration)) {
|
if ($reset || is_null($proxy_configuration)) {
|
||||||
$proxy_configuration = Str::of(generate_default_proxy_configuration($server))->trim()->value;
|
$proxy_configuration = Str::of(generate_default_proxy_configuration($server))->trim()->value;
|
||||||
resolve(SaveConfigurationSync::class)($server, $proxy_configuration);
|
|
||||||
return $proxy_configuration;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $proxy_configuration;
|
return $proxy_configuration;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,11 +7,12 @@ use Illuminate\Support\Str;
|
|||||||
|
|
||||||
class SaveConfigurationSync
|
class SaveConfigurationSync
|
||||||
{
|
{
|
||||||
public function __invoke(Server $server, string $configuration)
|
public function __invoke(Server $server)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
|
$proxy_settings = resolve(CheckConfigurationSync::class)($server, true);
|
||||||
$proxy_path = get_proxy_path();
|
$proxy_path = get_proxy_path();
|
||||||
$docker_compose_yml_base64 = base64_encode($configuration);
|
$docker_compose_yml_base64 = base64_encode($proxy_settings);
|
||||||
|
|
||||||
$server->proxy->last_saved_settings = Str::of($docker_compose_yml_base64)->pipe('md5')->value;
|
$server->proxy->last_saved_settings = Str::of($docker_compose_yml_base64)->pipe('md5')->value;
|
||||||
$server->save();
|
$server->save();
|
||||||
|
@ -24,7 +24,7 @@ use Illuminate\Console\Command;
|
|||||||
use Illuminate\Mail\Message;
|
use Illuminate\Mail\Message;
|
||||||
use Illuminate\Notifications\Messages\MailMessage;
|
use Illuminate\Notifications\Messages\MailMessage;
|
||||||
use Mail;
|
use Mail;
|
||||||
use Str;
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
use function Laravel\Prompts\confirm;
|
use function Laravel\Prompts\confirm;
|
||||||
use function Laravel\Prompts\select;
|
use function Laravel\Prompts\select;
|
||||||
@ -62,7 +62,7 @@ class Emails extends Command
|
|||||||
'application-status-changed' => 'Application - Status Changed',
|
'application-status-changed' => 'Application - Status Changed',
|
||||||
'backup-success' => 'Database - Backup Success',
|
'backup-success' => 'Database - Backup Success',
|
||||||
'backup-failed' => 'Database - Backup Failed',
|
'backup-failed' => 'Database - Backup Failed',
|
||||||
'invitation-link' => 'Invitation Link',
|
// 'invitation-link' => 'Invitation Link',
|
||||||
'waitlist-invitation-link' => 'Waitlist Invitation Link',
|
'waitlist-invitation-link' => 'Waitlist Invitation Link',
|
||||||
'waitlist-confirmation' => 'Waitlist Confirmation',
|
'waitlist-confirmation' => 'Waitlist Confirmation',
|
||||||
'realusers-before-trial' => 'REAL - Registered Users Before Trial without Subscription',
|
'realusers-before-trial' => 'REAL - Registered Users Before Trial without Subscription',
|
||||||
@ -141,20 +141,20 @@ class Emails extends Command
|
|||||||
$this->mail = (new BackupSuccess($backup, $db))->toMail();
|
$this->mail = (new BackupSuccess($backup, $db))->toMail();
|
||||||
$this->sendEmail();
|
$this->sendEmail();
|
||||||
break;
|
break;
|
||||||
case 'invitation-link':
|
// case 'invitation-link':
|
||||||
$user = User::all()->first();
|
// $user = User::all()->first();
|
||||||
$invitation = TeamInvitation::whereEmail($user->email)->first();
|
// $invitation = TeamInvitation::whereEmail($user->email)->first();
|
||||||
if (!$invitation) {
|
// if (!$invitation) {
|
||||||
$invitation = TeamInvitation::create([
|
// $invitation = TeamInvitation::create([
|
||||||
'uuid' => Str::uuid(),
|
// 'uuid' => Str::uuid(),
|
||||||
'email' => $user->email,
|
// 'email' => $user->email,
|
||||||
'team_id' => 1,
|
// 'team_id' => 1,
|
||||||
'link' => 'http://example.com',
|
// 'link' => 'http://example.com',
|
||||||
]);
|
// ]);
|
||||||
}
|
// }
|
||||||
$this->mail = (new InvitationLink($user))->toMail();
|
// $this->mail = (new InvitationLink($user))->toMail();
|
||||||
$this->sendEmail();
|
// $this->sendEmail();
|
||||||
break;
|
// break;
|
||||||
case 'waitlist-invitation-link':
|
case 'waitlist-invitation-link':
|
||||||
$this->mail = new MailMessage();
|
$this->mail = new MailMessage();
|
||||||
$this->mail->view('emails.waitlist-invitation', [
|
$this->mail->view('emails.waitlist-invitation', [
|
||||||
|
@ -21,7 +21,7 @@ class Kernel extends ConsoleKernel
|
|||||||
if (isDev()) {
|
if (isDev()) {
|
||||||
// $schedule->job(new ContainerStatusJob(Server::find(0)))->everyTenMinutes()->onOneServer();
|
// $schedule->job(new ContainerStatusJob(Server::find(0)))->everyTenMinutes()->onOneServer();
|
||||||
// $schedule->command('horizon:snapshot')->everyMinute();
|
// $schedule->command('horizon:snapshot')->everyMinute();
|
||||||
// $schedule->job(new CleanupInstanceStuffsJob)->everyMinute();
|
$schedule->job(new CleanupInstanceStuffsJob)->everyMinute()->onOneServer();
|
||||||
// $schedule->job(new CheckResaleLicenseJob)->hourly();
|
// $schedule->job(new CheckResaleLicenseJob)->hourly();
|
||||||
// $schedule->job(new DockerCleanupJob)->everyOddHour();
|
// $schedule->job(new DockerCleanupJob)->everyOddHour();
|
||||||
// $this->instance_auto_update($schedule);
|
// $this->instance_auto_update($schedule);
|
||||||
@ -29,7 +29,7 @@ class Kernel extends ConsoleKernel
|
|||||||
$this->check_resources($schedule);
|
$this->check_resources($schedule);
|
||||||
} else {
|
} else {
|
||||||
$schedule->command('horizon:snapshot')->everyFiveMinutes();
|
$schedule->command('horizon:snapshot')->everyFiveMinutes();
|
||||||
$schedule->job(new CleanupInstanceStuffsJob)->everyTenMinutes()->onOneServer();
|
$schedule->job(new CleanupInstanceStuffsJob)->everyTwoMinutes()->onOneServer();
|
||||||
$schedule->job(new CheckResaleLicenseJob)->hourly()->onOneServer();
|
$schedule->job(new CheckResaleLicenseJob)->hourly()->onOneServer();
|
||||||
$schedule->job(new DockerCleanupJob)->everyTenMinutes()->onOneServer();
|
$schedule->job(new DockerCleanupJob)->everyTenMinutes()->onOneServer();
|
||||||
$this->instance_auto_update($schedule);
|
$this->instance_auto_update($schedule);
|
||||||
|
@ -3,21 +3,18 @@
|
|||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
use App\Models\InstanceSettings;
|
use App\Models\InstanceSettings;
|
||||||
use App\Models\Project;
|
|
||||||
use App\Models\S3Storage;
|
use App\Models\S3Storage;
|
||||||
use App\Models\StandalonePostgresql;
|
use App\Models\StandalonePostgresql;
|
||||||
use App\Models\TeamInvitation;
|
use App\Models\TeamInvitation;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use Auth;
|
|
||||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||||
use Illuminate\Foundation\Validation\ValidatesRequests;
|
use Illuminate\Foundation\Validation\ValidatesRequests;
|
||||||
use Illuminate\Routing\Controller as BaseController;
|
use Illuminate\Routing\Controller as BaseController;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
use Illuminate\Support\Facades\Crypt;
|
use Illuminate\Support\Facades\Crypt;
|
||||||
use Illuminate\Support\Facades\Hash;
|
use Illuminate\Support\Facades\Hash;
|
||||||
use Illuminate\Support\Facades\Http;
|
use Illuminate\Support\Str;
|
||||||
use Throwable;
|
use Throwable;
|
||||||
use Str;
|
|
||||||
|
|
||||||
|
|
||||||
class Controller extends BaseController
|
class Controller extends BaseController
|
||||||
{
|
{
|
||||||
@ -35,8 +32,15 @@ class Controller extends BaseController
|
|||||||
return redirect()->route('login');
|
return redirect()->route('login');
|
||||||
}
|
}
|
||||||
if (Hash::check($password, $user->password)) {
|
if (Hash::check($password, $user->password)) {
|
||||||
|
$invitation = TeamInvitation::whereEmail($email);
|
||||||
|
if ($invitation->exists()) {
|
||||||
|
$team = $invitation->first()->team;
|
||||||
|
$user->teams()->attach($team->id, ['role' => $invitation->first()->role]);
|
||||||
|
$invitation->delete();
|
||||||
|
} else {
|
||||||
|
$team = $user->teams()->first();
|
||||||
|
}
|
||||||
Auth::login($user);
|
Auth::login($user);
|
||||||
$team = $user->teams()->first();
|
|
||||||
session(['currentTeam' => $team]);
|
session(['currentTeam' => $team]);
|
||||||
return redirect()->route('dashboard');
|
return redirect()->route('dashboard');
|
||||||
}
|
}
|
||||||
@ -137,24 +141,20 @@ class Controller extends BaseController
|
|||||||
try {
|
try {
|
||||||
$invitation = TeamInvitation::whereUuid(request()->route('uuid'))->firstOrFail();
|
$invitation = TeamInvitation::whereUuid(request()->route('uuid'))->firstOrFail();
|
||||||
$user = User::whereEmail($invitation->email)->firstOrFail();
|
$user = User::whereEmail($invitation->email)->firstOrFail();
|
||||||
if (is_null(auth()->user())) {
|
|
||||||
return redirect()->route('login');
|
|
||||||
}
|
|
||||||
if (auth()->user()->id !== $user->id) {
|
if (auth()->user()->id !== $user->id) {
|
||||||
abort(401);
|
abort(401);
|
||||||
}
|
}
|
||||||
|
$invitationValid = $invitation->isValid();
|
||||||
$createdAt = $invitation->created_at;
|
if ($invitationValid) {
|
||||||
$diff = $createdAt->diffInMinutes(now());
|
|
||||||
if ($diff <= config('constants.invitation.link.expiration')) {
|
|
||||||
$user->teams()->attach($invitation->team->id, ['role' => $invitation->role]);
|
$user->teams()->attach($invitation->team->id, ['role' => $invitation->role]);
|
||||||
|
refreshSession($invitation->team);
|
||||||
$invitation->delete();
|
$invitation->delete();
|
||||||
return redirect()->route('team.index');
|
return redirect()->route('team.index');
|
||||||
} else {
|
} else {
|
||||||
$invitation->delete();
|
|
||||||
abort(401);
|
abort(401);
|
||||||
}
|
}
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
|
ray($e->getMessage());
|
||||||
throw $e;
|
throw $e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@ class Help extends Component
|
|||||||
];
|
];
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
$this->path = Route::current()->uri();
|
$this->path = Route::current()?->uri() ?? null;
|
||||||
if (isDev()) {
|
if (isDev()) {
|
||||||
$this->description = "I'm having trouble with {$this->path}";
|
$this->description = "I'm having trouble with {$this->path}";
|
||||||
$this->subject = "Help with {$this->path}";
|
$this->subject = "Help with {$this->path}";
|
||||||
@ -41,7 +41,7 @@ class Help extends Component
|
|||||||
]
|
]
|
||||||
);
|
);
|
||||||
$mail->subject("[HELP - {$subscriptionType}]: {$this->subject}");
|
$mail->subject("[HELP - {$subscriptionType}]: {$this->subject}");
|
||||||
send_user_an_email($mail, 'hi@coollabs.io', auth()->user()?->email);
|
send_user_an_email($mail, auth()->user()?->email, 'hi@coollabs.io');
|
||||||
$this->emit('success', 'Your message has been sent successfully. We will get in touch with you as soon as possible.');
|
$this->emit('success', 'Your message has been sent successfully. We will get in touch with you as soon as possible.');
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return general_error_handler($e, $this);
|
return general_error_handler($e, $this);
|
||||||
|
@ -46,9 +46,6 @@ class DiscordSettings extends Component
|
|||||||
public function saveModel()
|
public function saveModel()
|
||||||
{
|
{
|
||||||
$this->team->save();
|
$this->team->save();
|
||||||
if (is_a($this->team, Team::class)) {
|
|
||||||
refreshSession();
|
|
||||||
}
|
|
||||||
$this->emit('success', 'Settings saved.');
|
$this->emit('success', 'Settings saved.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,9 +110,6 @@ class EmailSettings extends Component
|
|||||||
public function saveModel()
|
public function saveModel()
|
||||||
{
|
{
|
||||||
$this->team->save();
|
$this->team->save();
|
||||||
if (is_a($this->team, Team::class)) {
|
|
||||||
refreshSession();
|
|
||||||
}
|
|
||||||
$this->emit('success', 'Settings saved.');
|
$this->emit('success', 'Settings saved.');
|
||||||
}
|
}
|
||||||
public function submit()
|
public function submit()
|
||||||
@ -141,10 +138,11 @@ class EmailSettings extends Component
|
|||||||
try {
|
try {
|
||||||
$this->resetErrorBag();
|
$this->resetErrorBag();
|
||||||
$this->validate([
|
$this->validate([
|
||||||
|
'team.smtp_from_address' => 'required|email',
|
||||||
|
'team.smtp_from_name' => 'required',
|
||||||
'team.resend_api_key' => 'required'
|
'team.resend_api_key' => 'required'
|
||||||
]);
|
]);
|
||||||
$this->team->save();
|
$this->team->save();
|
||||||
refreshSession();
|
|
||||||
$this->emit('success', 'Settings saved successfully.');
|
$this->emit('success', 'Settings saved successfully.');
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
$this->team->resend_enabled = false;
|
$this->team->resend_enabled = false;
|
||||||
|
@ -52,9 +52,6 @@ class TelegramSettings extends Component
|
|||||||
public function saveModel()
|
public function saveModel()
|
||||||
{
|
{
|
||||||
$this->team->save();
|
$this->team->save();
|
||||||
if (is_a($this->team, Team::class)) {
|
|
||||||
refreshSession();
|
|
||||||
}
|
|
||||||
$this->emit('success', 'Settings saved.');
|
$this->emit('success', 'Settings saved.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,13 +4,15 @@ namespace App\Http\Livewire\PrivateKey;
|
|||||||
|
|
||||||
use App\Models\PrivateKey;
|
use App\Models\PrivateKey;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
use phpseclib3\Crypt\PublicKeyLoader;
|
||||||
|
|
||||||
class Create extends Component
|
class Create extends Component
|
||||||
{
|
{
|
||||||
public string|null $from = null;
|
public ?string $from = null;
|
||||||
public string $name;
|
public string $name;
|
||||||
public string|null $description = null;
|
public ?string $description = null;
|
||||||
public string $value;
|
public string $value;
|
||||||
|
public ?string $publicKey = null;
|
||||||
protected $rules = [
|
protected $rules = [
|
||||||
'name' => 'required|string',
|
'name' => 'required|string',
|
||||||
'value' => 'required|string',
|
'value' => 'required|string',
|
||||||
@ -20,6 +22,23 @@ class Create extends Component
|
|||||||
'value' => 'private Key',
|
'value' => 'private Key',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
public function generateNewKey()
|
||||||
|
{
|
||||||
|
$this->name = generate_random_name();
|
||||||
|
$this->description = 'Created by Coolify';
|
||||||
|
['private' => $this->value, 'public' => $this->publicKey] = generateSSHKey();
|
||||||
|
}
|
||||||
|
public function updated($updateProperty)
|
||||||
|
{
|
||||||
|
if ($updateProperty === 'value') {
|
||||||
|
try {
|
||||||
|
$this->publicKey = PublicKeyLoader::load($this->$updateProperty)->getPublicKey()->toString('OpenSSH',['comment' => '']);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$this->publicKey = "Invalid private key";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->validateOnly($updateProperty);
|
||||||
|
}
|
||||||
public function createPrivateKey()
|
public function createPrivateKey()
|
||||||
{
|
{
|
||||||
$this->validate();
|
$this->validate();
|
||||||
|
@ -2,8 +2,6 @@
|
|||||||
|
|
||||||
namespace App\Http\Livewire\Project\Application;
|
namespace App\Http\Livewire\Project\Application;
|
||||||
|
|
||||||
use App\Jobs\ApplicationContainerStatusJob;
|
|
||||||
use App\Jobs\ContainerStatusJob;
|
|
||||||
use App\Models\Application;
|
use App\Models\Application;
|
||||||
use App\Models\ApplicationPreview;
|
use App\Models\ApplicationPreview;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
|
@ -5,7 +5,7 @@ namespace App\Http\Livewire\Project\Shared\EnvironmentVariable;
|
|||||||
use App\Models\EnvironmentVariable;
|
use App\Models\EnvironmentVariable;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
use Visus\Cuid2\Cuid2;
|
use Visus\Cuid2\Cuid2;
|
||||||
use Str;
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
class All extends Component
|
class All extends Component
|
||||||
{
|
{
|
||||||
|
@ -88,7 +88,9 @@ class Form extends Component
|
|||||||
public function submit()
|
public function submit()
|
||||||
{
|
{
|
||||||
$this->validate();
|
$this->validate();
|
||||||
$uniqueIPs = Server::all()->pluck('ip')->toArray();
|
$uniqueIPs = Server::all()->reject(function (Server $server) {
|
||||||
|
return $server->id === $this->server->id;
|
||||||
|
})->pluck('ip')->toArray();
|
||||||
if (in_array($this->server->ip, $uniqueIPs)) {
|
if (in_array($this->server->ip, $uniqueIPs)) {
|
||||||
$this->emit('error', 'IP address is already in use by another team.');
|
$this->emit('error', 'IP address is already in use by another team.');
|
||||||
return;
|
return;
|
||||||
|
@ -48,7 +48,7 @@ class Proxy extends Component
|
|||||||
public function submit()
|
public function submit()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
resolve(SaveConfigurationSync::class)($this->server, $this->proxy_settings);
|
resolve(SaveConfigurationSync::class)($this->server);
|
||||||
|
|
||||||
$this->server->proxy->redirect_url = $this->redirect_url;
|
$this->server->proxy->redirect_url = $this->redirect_url;
|
||||||
$this->server->save();
|
$this->server->save();
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Http\Livewire\Server\Proxy;
|
namespace App\Http\Livewire\Server\Proxy;
|
||||||
|
|
||||||
|
use App\Actions\Proxy\SaveConfigurationSync;
|
||||||
use App\Actions\Proxy\StartProxy;
|
use App\Actions\Proxy\StartProxy;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
@ -21,7 +22,7 @@ class Deploy extends Component
|
|||||||
$this->server->proxy->last_applied_settings &&
|
$this->server->proxy->last_applied_settings &&
|
||||||
$this->server->proxy->last_saved_settings !== $this->server->proxy->last_applied_settings
|
$this->server->proxy->last_saved_settings !== $this->server->proxy->last_applied_settings
|
||||||
) {
|
) {
|
||||||
resolve(SaveConfigurationSync::class)($this->server, $this->proxy_settings);
|
resolve(SaveConfigurationSync::class)($this->server);
|
||||||
}
|
}
|
||||||
|
|
||||||
$activity = resolve(StartProxy::class)($this->server);
|
$activity = resolve(StartProxy::class)($this->server);
|
||||||
|
16
app/Http/Livewire/Server/Proxy/Modal.php
Normal file
16
app/Http/Livewire/Server/Proxy/Modal.php
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Livewire\Server\Proxy;
|
||||||
|
|
||||||
|
use App\Models\Server;
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class Modal extends Component
|
||||||
|
{
|
||||||
|
public Server $server;
|
||||||
|
|
||||||
|
public function proxyStatusUpdated()
|
||||||
|
{
|
||||||
|
$this->emit('proxyStatusUpdated');
|
||||||
|
}
|
||||||
|
}
|
@ -13,7 +13,10 @@ class Show extends Component
|
|||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$this->server = Server::ownedByCurrentTeam(['name', 'description', 'ip', 'port', 'user', 'proxy'])->whereUuid(request()->server_uuid)->firstOrFail();
|
$this->server = Server::ownedByCurrentTeam(['name', 'description', 'ip', 'port', 'user', 'proxy'])->whereUuid(request()->server_uuid)->first();
|
||||||
|
if (is_null($this->server)) {
|
||||||
|
return redirect()->route('server.all');
|
||||||
|
}
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return general_error_handler(err: $e, that: $this);
|
return general_error_handler(err: $e, that: $this);
|
||||||
}
|
}
|
||||||
|
@ -44,6 +44,7 @@ class PricingPlans extends Component
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$payload = [
|
$payload = [
|
||||||
|
'billing_address_collection' => 'required',
|
||||||
'client_reference_id' => auth()->user()->id . ':' . currentTeam()->id,
|
'client_reference_id' => auth()->user()->id . ':' . currentTeam()->id,
|
||||||
'line_items' => [[
|
'line_items' => [[
|
||||||
'price' => $priceId,
|
'price' => $priceId,
|
||||||
|
@ -12,7 +12,6 @@ class Delete extends Component
|
|||||||
$currentTeam = currentTeam();
|
$currentTeam = currentTeam();
|
||||||
$currentTeam->delete();
|
$currentTeam->delete();
|
||||||
|
|
||||||
$team = auth()->user()->teams()->first();
|
|
||||||
$currentTeam->members->each(function ($user) use ($currentTeam) {
|
$currentTeam->members->each(function ($user) use ($currentTeam) {
|
||||||
if ($user->id === auth()->user()->id) {
|
if ($user->id === auth()->user()->id) {
|
||||||
return;
|
return;
|
||||||
|
@ -27,7 +27,6 @@ class Form extends Component
|
|||||||
$this->validate();
|
$this->validate();
|
||||||
try {
|
try {
|
||||||
$this->team->save();
|
$this->team->save();
|
||||||
refreshSession();
|
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return general_error_handler($e, $this);
|
return general_error_handler($e, $this);
|
||||||
}
|
}
|
||||||
|
@ -4,9 +4,13 @@ namespace App\Http\Livewire\Team;
|
|||||||
|
|
||||||
use App\Models\TeamInvitation;
|
use App\Models\TeamInvitation;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use App\Notifications\TransactionalEmails\InvitationLink;
|
use Illuminate\Notifications\Messages\MailMessage;
|
||||||
|
use Illuminate\Support\Facades\Artisan;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
use Visus\Cuid2\Cuid2;
|
use Visus\Cuid2\Cuid2;
|
||||||
|
use Illuminate\Support\Facades\Crypt;
|
||||||
|
use Illuminate\Support\Facades\Hash;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
class InviteLink extends Component
|
class InviteLink extends Component
|
||||||
{
|
{
|
||||||
@ -20,53 +24,68 @@ class InviteLink extends Component
|
|||||||
|
|
||||||
public function viaEmail()
|
public function viaEmail()
|
||||||
{
|
{
|
||||||
$this->generate_invite_link(isEmail: true);
|
$this->generate_invite_link(sendEmail: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function generate_invite_link(bool $isEmail = false)
|
public function viaLink()
|
||||||
|
{
|
||||||
|
$this->generate_invite_link(sendEmail: false);
|
||||||
|
}
|
||||||
|
private function generate_invite_link(bool $sendEmail = false)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$uuid = new Cuid2(32);
|
|
||||||
$link = url('/') . config('constants.invitation.link.base_url') . $uuid;
|
|
||||||
|
|
||||||
$user = User::whereEmail($this->email);
|
|
||||||
|
|
||||||
if (!$user->exists()) {
|
|
||||||
return general_error_handler(that: $this, customErrorMessage: "$this->email must be registered first (or activate transactional emails to invite via email).");
|
|
||||||
}
|
|
||||||
|
|
||||||
$member_emails = currentTeam()->members()->get()->pluck('email');
|
$member_emails = currentTeam()->members()->get()->pluck('email');
|
||||||
if ($member_emails->contains($this->email)) {
|
if ($member_emails->contains($this->email)) {
|
||||||
return general_error_handler(that: $this, customErrorMessage: "$this->email is already a member of " . currentTeam()->name . ".");
|
return general_error_handler(that: $this, customErrorMessage: "$this->email is already a member of " . currentTeam()->name . ".");
|
||||||
}
|
}
|
||||||
|
$uuid = new Cuid2(32);
|
||||||
|
$link = url('/') . config('constants.invitation.link.base_url') . $uuid;
|
||||||
|
$user = User::whereEmail($this->email)->first();
|
||||||
|
|
||||||
$invitation = TeamInvitation::whereEmail($this->email);
|
if (is_null($user)) {
|
||||||
|
$password = Str::password();
|
||||||
if ($invitation->exists()) {
|
$user = User::create([
|
||||||
$created_at = $invitation->first()->created_at;
|
'name' => Str::of($this->email)->before('@'),
|
||||||
$diff = $created_at->diffInMinutes(now());
|
'email' => $this->email,
|
||||||
if ($diff <= config('constants.invitation.link.expiration')) {
|
'password' => Hash::make($password),
|
||||||
return general_error_handler(that: $this, customErrorMessage: "Invitation already sent to $this->email and waiting for action.");
|
'force_password_reset' => true,
|
||||||
|
]);
|
||||||
|
$token = Crypt::encryptString("{$user->email}@@@$password");
|
||||||
|
$link = route('auth.link', ['token' => $token]);
|
||||||
|
}
|
||||||
|
$invitation = TeamInvitation::whereEmail($this->email)->first();
|
||||||
|
if (!is_null($invitation)) {
|
||||||
|
$invitationValid = $invitation->isValid();
|
||||||
|
if ($invitationValid) {
|
||||||
|
return general_error_handler(that: $this, customErrorMessage: "Pending invitation already exists for $this->email.");
|
||||||
} else {
|
} else {
|
||||||
$invitation->delete();
|
$invitation->delete();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TeamInvitation::firstOrCreate([
|
$invitation = TeamInvitation::firstOrCreate([
|
||||||
'team_id' => currentTeam()->id,
|
'team_id' => currentTeam()->id,
|
||||||
'uuid' => $uuid,
|
'uuid' => $uuid,
|
||||||
'email' => $this->email,
|
'email' => $this->email,
|
||||||
'role' => $this->role,
|
'role' => $this->role,
|
||||||
'link' => $link,
|
'link' => $link,
|
||||||
'via' => $isEmail ? 'email' : 'link',
|
'via' => $sendEmail ? 'email' : 'link',
|
||||||
]);
|
]);
|
||||||
if ($isEmail) {
|
if ($sendEmail) {
|
||||||
$user->first()->notify(new InvitationLink);
|
$mail = new MailMessage();
|
||||||
|
$mail->view('emails.invitation-link', [
|
||||||
|
'team' => currentTeam()->name,
|
||||||
|
'invitation_link' => $link,
|
||||||
|
]);
|
||||||
|
$mail->subject('You have been invited to ' . currentTeam()->name . ' on ' . config('app.name') . '.');
|
||||||
|
send_user_an_email($mail, $this->email);
|
||||||
$this->emit('success', 'Invitation sent via email successfully.');
|
$this->emit('success', 'Invitation sent via email successfully.');
|
||||||
|
$this->emit('refreshInvitations');
|
||||||
|
return;
|
||||||
} else {
|
} else {
|
||||||
$this->emit('success', 'Invitation link generated.');
|
$this->emit('success', 'Invitation link generated.');
|
||||||
|
$this->emit('refreshInvitations');
|
||||||
}
|
}
|
||||||
$this->emit('refreshInvitations');
|
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
$error_message = $e->getMessage();
|
$error_message = $e->getMessage();
|
||||||
if ($e->getCode() === '23505') {
|
if ($e->getCode() === '23505') {
|
||||||
@ -75,9 +94,4 @@ class InviteLink extends Component
|
|||||||
return general_error_handler(err: $e, that: $this, customErrorMessage: $error_message);
|
return general_error_handler(err: $e, that: $this, customErrorMessage: $error_message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function viaLink()
|
|
||||||
{
|
|
||||||
$this->generate_invite_link();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
namespace App\Http\Livewire\Team;
|
namespace App\Http\Livewire\Team;
|
||||||
|
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
class Member extends Component
|
class Member extends Component
|
||||||
@ -24,6 +25,10 @@ class Member extends Component
|
|||||||
public function remove()
|
public function remove()
|
||||||
{
|
{
|
||||||
$this->member->teams()->detach(currentTeam());
|
$this->member->teams()->detach(currentTeam());
|
||||||
|
Cache::forget("team:{$this->member->id}");
|
||||||
|
Cache::remember('team:' . $this->member->id, 3600, function() {
|
||||||
|
return $this->member->teams()->first();
|
||||||
|
});
|
||||||
$this->emit('reloadWindow');
|
$this->emit('reloadWindow');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@ use App\Jobs\SendConfirmationForWaitlistJob;
|
|||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use App\Models\Waitlist;
|
use App\Models\Waitlist;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
use Str;
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
class Index extends Component
|
class Index extends Component
|
||||||
{
|
{
|
||||||
|
@ -5,6 +5,7 @@ namespace App\Http\Middleware;
|
|||||||
use Closure;
|
use Closure;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
class IsBoardingFlow
|
class IsBoardingFlow
|
||||||
{
|
{
|
||||||
@ -17,6 +18,9 @@ class IsBoardingFlow
|
|||||||
{
|
{
|
||||||
// ray()->showQueries()->color('orange');
|
// ray()->showQueries()->color('orange');
|
||||||
if (showBoarding() && !in_array($request->path(), allowedPathsForBoardingAccounts())) {
|
if (showBoarding() && !in_array($request->path(), allowedPathsForBoardingAccounts())) {
|
||||||
|
if (Str::startsWith($request->path(), 'invitations')) {
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
return redirect('boarding');
|
return redirect('boarding');
|
||||||
}
|
}
|
||||||
return $next($request);
|
return $next($request);
|
||||||
|
@ -5,6 +5,7 @@ namespace App\Http\Middleware;
|
|||||||
use Closure;
|
use Closure;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
class IsSubscriptionValid
|
class IsSubscriptionValid
|
||||||
{
|
{
|
||||||
@ -31,6 +32,9 @@ class IsSubscriptionValid
|
|||||||
if (!isSubscriptionActive() && !isSubscriptionOnGracePeriod()) {
|
if (!isSubscriptionActive() && !isSubscriptionOnGracePeriod()) {
|
||||||
// ray('SubscriptionValid Middleware');
|
// ray('SubscriptionValid Middleware');
|
||||||
if (!in_array($request->path(), allowedPathsForUnsubscribedAccounts())) {
|
if (!in_array($request->path(), allowedPathsForUnsubscribedAccounts())) {
|
||||||
|
if (Str::startsWith($request->path(), 'invitations')) {
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
return redirect('subscription');
|
return redirect('subscription');
|
||||||
} else {
|
} else {
|
||||||
return $next($request);
|
return $next($request);
|
||||||
|
@ -1,54 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Jobs;
|
|
||||||
|
|
||||||
use App\Models\Application;
|
|
||||||
use App\Models\ApplicationPreview;
|
|
||||||
use Illuminate\Bus\Queueable;
|
|
||||||
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
|
||||||
use Illuminate\Contracts\Queue\ShouldBeUnique;
|
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
|
||||||
use Illuminate\Foundation\Bus\Dispatchable;
|
|
||||||
use Illuminate\Queue\InteractsWithQueue;
|
|
||||||
use Illuminate\Queue\SerializesModels;
|
|
||||||
|
|
||||||
class ApplicationContainerStatusJob implements ShouldQueue, ShouldBeUnique, ShouldBeEncrypted
|
|
||||||
{
|
|
||||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
|
||||||
|
|
||||||
public string $containerName;
|
|
||||||
|
|
||||||
public function __construct(
|
|
||||||
public Application $application,
|
|
||||||
public int $pullRequestId = 0)
|
|
||||||
{
|
|
||||||
$this->containerName = generateApplicationContainerName($application->uuid, $pullRequestId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function uniqueId(): string
|
|
||||||
{
|
|
||||||
return $this->containerName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function handle(): void
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$status = getApplicationContainerStatus(application: $this->application);
|
|
||||||
if ($this->application->status === 'running' && $status !== 'running') {
|
|
||||||
// $this->application->environment->project->team->notify(new StatusChanged($this->application));
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->pullRequestId !== 0) {
|
|
||||||
$preview = ApplicationPreview::findPreviewByApplicationAndPullId($this->application->id, $this->pullRequestId);
|
|
||||||
$preview->status = $status;
|
|
||||||
$preview->save();
|
|
||||||
} else {
|
|
||||||
$this->application->status = $status;
|
|
||||||
$this->application->save();
|
|
||||||
}
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
ray($e->getMessage());
|
|
||||||
throw $e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -652,7 +652,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
|
|
||||||
private function generate_healthcheck_commands()
|
private function generate_healthcheck_commands()
|
||||||
{
|
{
|
||||||
if ($this->application->dockerfile) {
|
if ($this->application->dockerfile || $this->application->build_pack === 'dockerfile') {
|
||||||
// TODO: disabled HC because there are several ways to hc a simple docker image, hard to figure out a good way. Like some docker images (pocketbase) does not have curl.
|
// TODO: disabled HC because there are several ways to hc a simple docker image, hard to figure out a good way. Like some docker images (pocketbase) does not have curl.
|
||||||
return 'exit 0';
|
return 'exit 0';
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Jobs;
|
namespace App\Jobs;
|
||||||
|
|
||||||
|
use App\Models\TeamInvitation;
|
||||||
use App\Models\Waitlist;
|
use App\Models\Waitlist;
|
||||||
use Illuminate\Bus\Queueable;
|
use Illuminate\Bus\Queueable;
|
||||||
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||||
@ -32,7 +33,12 @@ class CleanupInstanceStuffsJob implements ShouldQueue, ShouldBeUnique, ShouldBeE
|
|||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
send_internal_notification('CleanupInstanceStuffsJob failed with error: ' . $e->getMessage());
|
send_internal_notification('CleanupInstanceStuffsJob failed with error: ' . $e->getMessage());
|
||||||
ray($e->getMessage());
|
ray($e->getMessage());
|
||||||
throw $e;
|
}
|
||||||
|
try {
|
||||||
|
$this->cleanup_invitation_link();
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
send_internal_notification('CleanupInstanceStuffsJob failed with error: ' . $e->getMessage());
|
||||||
|
ray($e->getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -43,4 +49,11 @@ class CleanupInstanceStuffsJob implements ShouldQueue, ShouldBeUnique, ShouldBeE
|
|||||||
$item->delete();
|
$item->delete();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
private function cleanup_invitation_link()
|
||||||
|
{
|
||||||
|
$invitation = TeamInvitation::all();
|
||||||
|
foreach ($invitation as $item) {
|
||||||
|
$item->isValid();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,9 +17,9 @@ use Illuminate\Foundation\Bus\Dispatchable;
|
|||||||
use Illuminate\Queue\InteractsWithQueue;
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||||
use Illuminate\Queue\SerializesModels;
|
use Illuminate\Queue\SerializesModels;
|
||||||
use Str;
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
class ContainerStatusJob implements ShouldQueue, ShouldBeUnique, ShouldBeEncrypted
|
class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
||||||
{
|
{
|
||||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
@ -89,7 +89,6 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeUnique, ShouldBeEncrypt
|
|||||||
$labels = data_get($container, 'Config.Labels');
|
$labels = data_get($container, 'Config.Labels');
|
||||||
$labels = Arr::undot(format_docker_labels_to_json($labels));
|
$labels = Arr::undot(format_docker_labels_to_json($labels));
|
||||||
$labelId = data_get($labels, 'coolify.applicationId');
|
$labelId = data_get($labels, 'coolify.applicationId');
|
||||||
ray($labelId);
|
|
||||||
if ($labelId) {
|
if ($labelId) {
|
||||||
if (str_contains($labelId,'-pr-')) {
|
if (str_contains($labelId,'-pr-')) {
|
||||||
$previewId = (int) Str::after($labelId, '-pr-');
|
$previewId = (int) Str::after($labelId, '-pr-');
|
||||||
|
@ -1,57 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Jobs;
|
|
||||||
|
|
||||||
use App\Models\ApplicationPreview;
|
|
||||||
use App\Models\StandalonePostgresql;
|
|
||||||
use App\Notifications\Application\StatusChanged;
|
|
||||||
use Illuminate\Bus\Queueable;
|
|
||||||
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
|
||||||
use Illuminate\Contracts\Queue\ShouldBeUnique;
|
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
|
||||||
use Illuminate\Foundation\Bus\Dispatchable;
|
|
||||||
use Illuminate\Queue\InteractsWithQueue;
|
|
||||||
use Illuminate\Queue\SerializesModels;
|
|
||||||
|
|
||||||
class DatabaseContainerStatusJob implements ShouldQueue, ShouldBeUnique, ShouldBeEncrypted
|
|
||||||
{
|
|
||||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
|
||||||
|
|
||||||
public string $containerName;
|
|
||||||
|
|
||||||
public function __construct(
|
|
||||||
public StandalonePostgresql $database,
|
|
||||||
) {
|
|
||||||
$this->containerName = $database->uuid;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function uniqueId(): string
|
|
||||||
{
|
|
||||||
return $this->containerName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function handle(): void
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$status = getContainerStatus(
|
|
||||||
server: $this->database->destination->server,
|
|
||||||
container_id: $this->containerName,
|
|
||||||
throwError: false
|
|
||||||
);
|
|
||||||
|
|
||||||
if ($this->database->status === 'running' && $status !== 'running') {
|
|
||||||
if (data_get($this->database, 'environment.project.team')) {
|
|
||||||
// $this->database->environment->project->team->notify(new StatusChanged($this->database));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ($this->database->status !== $status) {
|
|
||||||
$this->database->status = $status;
|
|
||||||
$this->database->save();
|
|
||||||
}
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
send_internal_notification('DatabaseContainerStatusJob failed with: ' . $e->getMessage());
|
|
||||||
ray($e->getMessage());
|
|
||||||
throw $e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,87 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Jobs;
|
|
||||||
|
|
||||||
use App\Actions\Proxy\StartProxy;
|
|
||||||
use App\Enums\ProxyStatus;
|
|
||||||
use App\Enums\ProxyTypes;
|
|
||||||
use App\Models\Server;
|
|
||||||
use Illuminate\Bus\Queueable;
|
|
||||||
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
|
||||||
use Illuminate\Contracts\Queue\ShouldBeUnique;
|
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
|
||||||
use Illuminate\Foundation\Bus\Dispatchable;
|
|
||||||
use Illuminate\Queue\InteractsWithQueue;
|
|
||||||
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
|
||||||
use Illuminate\Queue\SerializesModels;
|
|
||||||
|
|
||||||
class ProxyContainerStatusJob implements ShouldQueue, ShouldBeUnique, ShouldBeEncrypted
|
|
||||||
{
|
|
||||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
|
||||||
|
|
||||||
public Server $server;
|
|
||||||
public $tries = 1;
|
|
||||||
public $timeout = 120;
|
|
||||||
|
|
||||||
public function __construct(Server $server)
|
|
||||||
{
|
|
||||||
$this->server = $server;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function middleware(): array
|
|
||||||
{
|
|
||||||
return [new WithoutOverlapping($this->server->uuid)];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function uniqueId(): string
|
|
||||||
{
|
|
||||||
ray($this->server->uuid);
|
|
||||||
return $this->server->uuid;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function handle(): void
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$proxyType = data_get($this->server, 'proxy.type');
|
|
||||||
if ($proxyType === ProxyTypes::NONE->value) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (is_null($proxyType)) {
|
|
||||||
if ($this->server->isProxyShouldRun()) {
|
|
||||||
$this->server->proxy->type = ProxyTypes::TRAEFIK_V2->value;
|
|
||||||
$this->server->proxy->status = ProxyStatus::EXITED->value;
|
|
||||||
$this->server->save();
|
|
||||||
resolve(StartProxy::class)($this->server);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$container = getContainerStatus(server: $this->server, all_data: true, container_id: 'coolify-proxy', throwError: false);
|
|
||||||
$containerStatus = data_get($container, 'State.Status');
|
|
||||||
$databaseContainerStatus = data_get($this->server, 'proxy.status', 'exited');
|
|
||||||
|
|
||||||
|
|
||||||
if ($proxyType !== ProxyTypes::NONE->value) {
|
|
||||||
if ($containerStatus === 'running') {
|
|
||||||
$this->server->proxy->status = $containerStatus;
|
|
||||||
$this->server->save();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if ((is_null($containerStatus) ||$containerStatus !== 'running' || $databaseContainerStatus !== 'running' || ($containerStatus && $databaseContainerStatus !== $containerStatus)) && $this->server->isProxyShouldRun()) {
|
|
||||||
$this->server->proxy->status = $containerStatus;
|
|
||||||
$this->server->save();
|
|
||||||
resolve(StartProxy::class)($this->server);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
if ($e->getCode() === 1) {
|
|
||||||
$this->server->proxy->status = 'exited';
|
|
||||||
$this->server->save();
|
|
||||||
}
|
|
||||||
send_internal_notification('ProxyContainerStatusJob failed with: ' . $e->getMessage());
|
|
||||||
ray($e->getMessage());
|
|
||||||
throw $e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -9,7 +9,7 @@ use Illuminate\Foundation\Bus\Dispatchable;
|
|||||||
use Illuminate\Queue\InteractsWithQueue;
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
use Illuminate\Queue\SerializesModels;
|
use Illuminate\Queue\SerializesModels;
|
||||||
use Illuminate\Support\Facades\Http;
|
use Illuminate\Support\Facades\Http;
|
||||||
use Str;
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
class SendMessageToTelegramJob implements ShouldQueue, ShouldBeEncrypted
|
class SendMessageToTelegramJob implements ShouldQueue, ShouldBeEncrypted
|
||||||
{
|
{
|
||||||
|
@ -19,6 +19,13 @@ class Team extends Model implements SendsDiscord, SendsEmail
|
|||||||
'resend_api_key' => 'encrypted',
|
'resend_api_key' => 'encrypted',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
protected static function booted()
|
||||||
|
{
|
||||||
|
static::saved(function () {
|
||||||
|
refreshSession();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public function routeNotificationForDiscord()
|
public function routeNotificationForDiscord()
|
||||||
{
|
{
|
||||||
return data_get($this, 'discord_webhook_url', null);
|
return data_get($this, 'discord_webhook_url', null);
|
||||||
|
@ -19,4 +19,13 @@ class TeamInvitation extends Model
|
|||||||
{
|
{
|
||||||
return $this->belongsTo(Team::class);
|
return $this->belongsTo(Team::class);
|
||||||
}
|
}
|
||||||
|
public function isValid() {
|
||||||
|
$createdAt = $this->created_at;
|
||||||
|
$diff = $createdAt->diffInMinutes(now());
|
||||||
|
if ($diff <= config('constants.invitation.link.expiration')) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
$this->delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,10 +4,10 @@ namespace App\Models;
|
|||||||
|
|
||||||
use App\Notifications\Channels\SendsEmail;
|
use App\Notifications\Channels\SendsEmail;
|
||||||
use App\Notifications\TransactionalEmails\ResetPassword as TransactionalEmailsResetPassword;
|
use App\Notifications\TransactionalEmails\ResetPassword as TransactionalEmailsResetPassword;
|
||||||
use Cache;
|
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||||
use Illuminate\Notifications\Notifiable;
|
use Illuminate\Notifications\Notifiable;
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
use Laravel\Fortify\TwoFactorAuthenticatable;
|
use Laravel\Fortify\TwoFactorAuthenticatable;
|
||||||
use Laravel\Sanctum\HasApiTokens;
|
use Laravel\Sanctum\HasApiTokens;
|
||||||
|
|
||||||
@ -61,7 +61,7 @@ class User extends Authenticatable implements SendsEmail
|
|||||||
|
|
||||||
public function isAdmin()
|
public function isAdmin()
|
||||||
{
|
{
|
||||||
return $this->pivot->role === 'admin' || $this->pivot->role === 'owner';
|
return data_get($this->pivot,'role') === 'admin' || data_get($this->pivot,'role') === 'owner';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function isAdminFromSession()
|
public function isAdminFromSession()
|
||||||
@ -78,7 +78,8 @@ class User extends Authenticatable implements SendsEmail
|
|||||||
if ($is_part_of_root_team && $is_admin_of_root_team) {
|
if ($is_part_of_root_team && $is_admin_of_root_team) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
$role = $teams->where('id', auth()->user()->id)->first()->pivot->role;
|
$team = $teams->where('id', session('currentTeam')->id)->first();
|
||||||
|
$role = data_get($team,'pivot.role');
|
||||||
return $role === 'admin' || $role === 'owner';
|
return $role === 'admin' || $role === 'owner';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,7 +39,7 @@ trait ExecuteRemoteCommand
|
|||||||
$this->save = data_get($single_command, 'save');
|
$this->save = data_get($single_command, 'save');
|
||||||
|
|
||||||
$remote_command = generateSshCommand( $ip, $user, $port, $command);
|
$remote_command = generateSshCommand( $ip, $user, $port, $command);
|
||||||
$process = processWithEnv()->timeout(3600)->idleTimeout(3600)->start($remote_command, function (string $type, string $output) use ($command, $hidden) {
|
$process = Process::timeout(3600)->idleTimeout(3600)->start($remote_command, function (string $type, string $output) use ($command, $hidden) {
|
||||||
$output = Str::of($output)->trim();
|
$output = Str::of($output)->trim();
|
||||||
$new_log_entry = [
|
$new_log_entry = [
|
||||||
'command' => $command,
|
'command' => $command,
|
||||||
|
@ -24,6 +24,7 @@ class Textarea extends Component
|
|||||||
public bool $disabled = false,
|
public bool $disabled = false,
|
||||||
public bool $readonly = false,
|
public bool $readonly = false,
|
||||||
public string|null $helper = null,
|
public string|null $helper = null,
|
||||||
|
public bool $realtimeValidation = false,
|
||||||
public string $defaultClass = "textarea bg-coolgray-200 rounded text-white scrollbar disabled:bg-coolgray-200/50 disabled:border-none placeholder:text-coolgray-500 read-only:text-neutral-500 read-only:bg-coolgray-200/50"
|
public string $defaultClass = "textarea bg-coolgray-200 rounded text-white scrollbar disabled:bg-coolgray-200/50 disabled:border-none placeholder:text-coolgray-500 read-only:text-neutral-500 read-only:bg-coolgray-200/50"
|
||||||
) {
|
) {
|
||||||
//
|
//
|
||||||
|
@ -75,7 +75,6 @@ function getApplicationContainerStatus(Application $application) {
|
|||||||
}
|
}
|
||||||
function getContainerStatus(Server $server, string $container_id, bool $all_data = false, bool $throwError = false)
|
function getContainerStatus(Server $server, string $container_id, bool $all_data = false, bool $throwError = false)
|
||||||
{
|
{
|
||||||
// check_server_connection($server);
|
|
||||||
$container = instant_remote_process(["docker inspect --format '{{json .}}' {$container_id}"], $server, $throwError);
|
$container = instant_remote_process(["docker inspect --format '{{json .}}' {$container_id}"], $server, $throwError);
|
||||||
if (!$container) {
|
if (!$container) {
|
||||||
return 'exited';
|
return 'exited';
|
||||||
|
@ -15,6 +15,7 @@ use Illuminate\Support\Facades\Process;
|
|||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
use Illuminate\Support\Sleep;
|
use Illuminate\Support\Sleep;
|
||||||
use Spatie\Activitylog\Models\Activity;
|
use Spatie\Activitylog\Models\Activity;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
function remote_process(
|
function remote_process(
|
||||||
array $command,
|
array $command,
|
||||||
@ -49,20 +50,23 @@ function remote_process(
|
|||||||
])();
|
])();
|
||||||
}
|
}
|
||||||
|
|
||||||
function removePrivateKeyFromSshAgent(Server $server)
|
// function removePrivateKeyFromSshAgent(Server $server)
|
||||||
{
|
// {
|
||||||
if (data_get($server, 'privateKey.private_key') === null) {
|
// if (data_get($server, 'privateKey.private_key') === null) {
|
||||||
throw new \Exception("Server {$server->name} does not have a private key");
|
// throw new \Exception("Server {$server->name} does not have a private key");
|
||||||
}
|
// }
|
||||||
processWithEnv()->run("echo '{$server->privateKey->private_key}' | ssh-add -d -");
|
// // processWithEnv()->run("echo '{$server->privateKey->private_key}' | ssh-add -d -");
|
||||||
}
|
// }
|
||||||
function addPrivateKeyToSshAgent(Server $server)
|
function addPrivateKeyToSshAgent(Server $server)
|
||||||
{
|
{
|
||||||
if (data_get($server, 'privateKey.private_key') === null) {
|
if (data_get($server, 'privateKey.private_key') === null) {
|
||||||
throw new \Exception("Server {$server->name} does not have a private key");
|
throw new \Exception("Server {$server->name} does not have a private key");
|
||||||
}
|
}
|
||||||
// ray('adding key', $server->privateKey->private_key);
|
$sshKeyFileLocation = "id.root@{$server->uuid}";
|
||||||
processWithEnv()->run("echo '{$server->privateKey->private_key}' | ssh-add -q -");
|
Storage::disk('ssh-keys')->makeDirectory('.');
|
||||||
|
Storage::disk('ssh-mux')->makeDirectory('.');
|
||||||
|
Storage::disk('ssh-keys')->put($sshKeyFileLocation, $server->privateKey->private_key);
|
||||||
|
return '/var/www/html/storage/app/ssh/keys/' . $sshKeyFileLocation;
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateSshCommand(string $server_ip, string $user, string $port, string $command, bool $isMux = true)
|
function generateSshCommand(string $server_ip, string $user, string $port, string $command, bool $isMux = true)
|
||||||
@ -71,7 +75,7 @@ function generateSshCommand(string $server_ip, string $user, string $port, strin
|
|||||||
if (!$server) {
|
if (!$server) {
|
||||||
throw new \Exception("Server with ip {$server_ip} not found");
|
throw new \Exception("Server with ip {$server_ip} not found");
|
||||||
}
|
}
|
||||||
addPrivateKeyToSshAgent($server);
|
$privateKeyLocation = addPrivateKeyToSshAgent($server);
|
||||||
$timeout = config('constants.ssh.command_timeout');
|
$timeout = config('constants.ssh.command_timeout');
|
||||||
$connectionTimeout = config('constants.ssh.connection_timeout');
|
$connectionTimeout = config('constants.ssh.connection_timeout');
|
||||||
$serverInterval = config('constants.ssh.server_interval');
|
$serverInterval = config('constants.ssh.server_interval');
|
||||||
@ -83,7 +87,8 @@ function generateSshCommand(string $server_ip, string $user, string $port, strin
|
|||||||
$ssh_command .= '-o ControlMaster=auto -o ControlPersist=1m -o ControlPath=/var/www/html/storage/app/ssh/mux/%h_%p_%r ';
|
$ssh_command .= '-o ControlMaster=auto -o ControlPersist=1m -o ControlPath=/var/www/html/storage/app/ssh/mux/%h_%p_%r ';
|
||||||
}
|
}
|
||||||
$command = "PATH=\$PATH:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/host/usr/local/sbin:/host/usr/local/bin:/host/usr/sbin:/host/usr/bin:/host/sbin:/host/bin && $command";
|
$command = "PATH=\$PATH:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/host/usr/local/sbin:/host/usr/local/bin:/host/usr/sbin:/host/usr/bin:/host/sbin:/host/bin && $command";
|
||||||
$ssh_command .= '-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null '
|
$ssh_command .= "-i {$privateKeyLocation} "
|
||||||
|
. '-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null '
|
||||||
. '-o PasswordAuthentication=no '
|
. '-o PasswordAuthentication=no '
|
||||||
. "-o ConnectTimeout=$connectionTimeout "
|
. "-o ConnectTimeout=$connectionTimeout "
|
||||||
. "-o ServerAliveInterval=$serverInterval "
|
. "-o ServerAliveInterval=$serverInterval "
|
||||||
@ -97,28 +102,11 @@ function generateSshCommand(string $server_ip, string $user, string $port, strin
|
|||||||
// ray($ssh_command);
|
// ray($ssh_command);
|
||||||
return $ssh_command;
|
return $ssh_command;
|
||||||
}
|
}
|
||||||
function processWithEnv()
|
|
||||||
{
|
|
||||||
return Process::env(['SSH_AUTH_SOCK' => config('coolify.ssh_auth_sock')]);
|
|
||||||
}
|
|
||||||
function instantCommand(string $command, $throwError = true)
|
|
||||||
{
|
|
||||||
$process = processWithEnv()->run($command);
|
|
||||||
$output = trim($process->output());
|
|
||||||
$exitCode = $process->exitCode();
|
|
||||||
if ($exitCode !== 0) {
|
|
||||||
if (!$throwError) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
throw new \RuntimeException($process->errorOutput(), $exitCode);
|
|
||||||
}
|
|
||||||
return $output;
|
|
||||||
}
|
|
||||||
function instant_remote_process(array $command, Server $server, $throwError = true, $repeat = 1)
|
function instant_remote_process(array $command, Server $server, $throwError = true, $repeat = 1)
|
||||||
{
|
{
|
||||||
$command_string = implode("\n", $command);
|
$command_string = implode("\n", $command);
|
||||||
$ssh_command = generateSshCommand($server->ip, $server->user, $server->port, $command_string);
|
$ssh_command = generateSshCommand($server->ip, $server->user, $server->port, $command_string);
|
||||||
$process = processWithEnv()->run($ssh_command);
|
$process = Process::run($ssh_command);
|
||||||
$output = trim($process->output());
|
$output = trim($process->output());
|
||||||
$exitCode = $process->exitCode();
|
$exitCode = $process->exitCode();
|
||||||
if ($exitCode !== 0) {
|
if ($exitCode !== 0) {
|
||||||
@ -169,14 +157,8 @@ function decode_remote_command_output(?ApplicationDeploymentQueue $application_d
|
|||||||
function refresh_server_connection(PrivateKey $private_key)
|
function refresh_server_connection(PrivateKey $private_key)
|
||||||
{
|
{
|
||||||
foreach ($private_key->servers as $server) {
|
foreach ($private_key->servers as $server) {
|
||||||
// Delete the old ssh mux file to force a new one to be created
|
|
||||||
Storage::disk('ssh-mux')->delete($server->muxFilename());
|
Storage::disk('ssh-mux')->delete($server->muxFilename());
|
||||||
// check if user is authenticated
|
|
||||||
// if (currentTeam()->id) {
|
|
||||||
// currentTeam()->privateKeys = PrivateKey::where('team_id', currentTeam()->id)->get();
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
removePrivateKeyFromSshAgent($server);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function validateServer(Server $server)
|
function validateServer(Server $server)
|
||||||
@ -221,29 +203,6 @@ function validateServer(Server $server)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function check_server_connection(Server $server)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
refresh_server_connection($server->privateKey);
|
|
||||||
instant_remote_process(['uptime'], $server);
|
|
||||||
$server->unreachable_count = 0;
|
|
||||||
$server->settings->is_reachable = true;
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
if ($server->unreachable_count == 2) {
|
|
||||||
$server->team->notify(new NotReachable($server));
|
|
||||||
$server->settings->is_reachable = false;
|
|
||||||
$server->settings->save();
|
|
||||||
} else {
|
|
||||||
$server->unreachable_count += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw $e;
|
|
||||||
} finally {
|
|
||||||
$server->settings->save();
|
|
||||||
$server->save();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function checkRequiredCommands(Server $server)
|
function checkRequiredCommands(Server $server)
|
||||||
{
|
{
|
||||||
$commands = collect(["jq", "jc"]);
|
$commands = collect(["jq", "jc"]);
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
use App\Models\InstanceSettings;
|
use App\Models\InstanceSettings;
|
||||||
use App\Models\Team;
|
use App\Models\Team;
|
||||||
|
use App\Models\User;
|
||||||
use App\Notifications\Channels\DiscordChannel;
|
use App\Notifications\Channels\DiscordChannel;
|
||||||
use App\Notifications\Channels\EmailChannel;
|
use App\Notifications\Channels\EmailChannel;
|
||||||
use App\Notifications\Channels\TelegramChannel;
|
use App\Notifications\Channels\TelegramChannel;
|
||||||
@ -10,6 +11,7 @@ use DanHarrin\LivewireRateLimiting\Exceptions\TooManyRequestsException;
|
|||||||
use Illuminate\Database\QueryException;
|
use Illuminate\Database\QueryException;
|
||||||
use Illuminate\Mail\Message;
|
use Illuminate\Mail\Message;
|
||||||
use Illuminate\Notifications\Messages\MailMessage;
|
use Illuminate\Notifications\Messages\MailMessage;
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
use Illuminate\Support\Facades\Http;
|
use Illuminate\Support\Facades\Http;
|
||||||
use Illuminate\Support\Facades\Mail;
|
use Illuminate\Support\Facades\Mail;
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
@ -60,7 +62,11 @@ function showBoarding(): bool
|
|||||||
function refreshSession(?Team $team = null): void
|
function refreshSession(?Team $team = null): void
|
||||||
{
|
{
|
||||||
if (!$team) {
|
if (!$team) {
|
||||||
$team = Team::find(currentTeam()->id);
|
if (auth()->user()->currentTeam()) {
|
||||||
|
$team = Team::find(auth()->user()->currentTeam()->id);
|
||||||
|
} else {
|
||||||
|
$team = User::find(auth()->user()->id)->teams->first();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Cache::forget('team:' . auth()->user()->id);
|
Cache::forget('team:' . auth()->user()->id);
|
||||||
Cache::remember('team:' . auth()->user()->id, 3600, function() use ($team) {
|
Cache::remember('team:' . auth()->user()->id, 3600, function() use ($team) {
|
||||||
@ -275,6 +281,7 @@ function send_user_an_email(MailMessage $mail, string $email, ?string $cc = null
|
|||||||
[],
|
[],
|
||||||
fn (Message $message) => $message
|
fn (Message $message) => $message
|
||||||
->to($email)
|
->to($email)
|
||||||
|
->replyTo($email)
|
||||||
->cc($cc)
|
->cc($cc)
|
||||||
->subject($mail->subject)
|
->subject($mail->subject)
|
||||||
->html((string) $mail->render())
|
->html((string) $mail->render())
|
||||||
|
@ -56,7 +56,7 @@ function isSubscriptionActive()
|
|||||||
}
|
}
|
||||||
$subscription = $team?->subscription;
|
$subscription = $team?->subscription;
|
||||||
|
|
||||||
if (!$subscription) {
|
if (is_null($subscription)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (isLemon()) {
|
if (isLemon()) {
|
||||||
|
@ -8,5 +8,4 @@ return [
|
|||||||
'dev_webhook' => env('SERVEO_URL'),
|
'dev_webhook' => env('SERVEO_URL'),
|
||||||
'base_config_path' => env('BASE_CONFIG_PATH', '/data/coolify'),
|
'base_config_path' => env('BASE_CONFIG_PATH', '/data/coolify'),
|
||||||
'helper_image' => env('HELPER_IMAGE', 'ghcr.io/coollabsio/coolify-helper:latest'),
|
'helper_image' => env('HELPER_IMAGE', 'ghcr.io/coollabsio/coolify-helper:latest'),
|
||||||
'ssh_auth_sock' => env('SSH_AUTH_SOCK', '/tmp/coolify-ssh-agent.sock'),
|
|
||||||
];
|
];
|
||||||
|
@ -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.36',
|
'release' => '4.0.0-beta.37',
|
||||||
'server_name' => env('APP_ID', 'coolify'),
|
'server_name' => env('APP_ID', 'coolify'),
|
||||||
// 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.36';
|
return '4.0.0-beta.37';
|
||||||
|
@ -0,0 +1,29 @@
|
|||||||
|
<?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('team_invitations', function (Blueprint $table) {
|
||||||
|
$table->text('link')->change();
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('team_invitations', function (Blueprint $table) {
|
||||||
|
$table->string('link')->change();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
@ -21,7 +21,6 @@ services:
|
|||||||
SSL_MODE: "off"
|
SSL_MODE: "off"
|
||||||
AUTORUN_LARAVEL_STORAGE_LINK: "false"
|
AUTORUN_LARAVEL_STORAGE_LINK: "false"
|
||||||
AUTORUN_LARAVEL_MIGRATION: "false"
|
AUTORUN_LARAVEL_MIGRATION: "false"
|
||||||
SSH_AUTH_SOCK: "/tmp/coolify-ssh-agent.sock"
|
|
||||||
volumes:
|
volumes:
|
||||||
- .:/var/www/html/:cached
|
- .:/var/www/html/:cached
|
||||||
postgres:
|
postgres:
|
||||||
|
@ -64,7 +64,6 @@ services:
|
|||||||
- LEMON_SQUEEZY_BASIC_PLAN_IDS
|
- LEMON_SQUEEZY_BASIC_PLAN_IDS
|
||||||
- LEMON_SQUEEZY_PRO_PLAN_IDS
|
- LEMON_SQUEEZY_PRO_PLAN_IDS
|
||||||
- LEMON_SQUEEZY_ULTIMATE_PLAN_IDS
|
- LEMON_SQUEEZY_ULTIMATE_PLAN_IDS
|
||||||
- SSH_AUTH_SOCK="/tmp/coolify-ssh-agent.sock"
|
|
||||||
ports:
|
ports:
|
||||||
- "${APP_PORT:-8000}:80"
|
- "${APP_PORT:-8000}:80"
|
||||||
expose:
|
expose:
|
||||||
|
@ -1 +0,0 @@
|
|||||||
oneshot
|
|
@ -1,5 +0,0 @@
|
|||||||
#!/usr/bin/execlineb -P
|
|
||||||
foreground {
|
|
||||||
s6-sleep 5
|
|
||||||
su - webuser -c "ssh-agent -a /tmp/coolify-ssh-agent.sock"
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
oneshot
|
|
@ -1,5 +0,0 @@
|
|||||||
#!/usr/bin/execlineb -P
|
|
||||||
foreground {
|
|
||||||
s6-sleep 5
|
|
||||||
su - webuser -c "ssh-agent -a /tmp/coolify-ssh-agent.sock"
|
|
||||||
}
|
|
@ -30,9 +30,12 @@
|
|||||||
</label>
|
</label>
|
||||||
@endif
|
@endif
|
||||||
<textarea placeholder="{{ $placeholder }}" {{ $attributes->merge(['class' => $defaultClass]) }}
|
<textarea placeholder="{{ $placeholder }}" {{ $attributes->merge(['class' => $defaultClass]) }}
|
||||||
wire:model.defer={{ $id }} @disabled($disabled) @readonly($readonly) @required($required)
|
@if ($realtimeValidation) wire:model.debounce.500ms="{{ $id }}"
|
||||||
id="{{ $id }}" name="{{ $name }}" name={{ $id }} wire:model.defer={{ $value ?? $id }}
|
@else
|
||||||
wire:dirty.class="input-warning"></textarea>
|
wire:model.defer={{ $value ?? $id }}
|
||||||
|
wire:dirty.class="input-warning"@endif
|
||||||
|
@disabled($disabled) @readonly($readonly) @required($required) id="{{ $id }}" name="{{ $name }}"
|
||||||
|
name={{ $id }} ></textarea>
|
||||||
@error($id)
|
@error($id)
|
||||||
<label class="label">
|
<label class="label">
|
||||||
<span class="text-red-500 label-text-alt">{{ $message }}</span>
|
<span class="text-red-500 label-text-alt">{{ $message }}</span>
|
||||||
|
@ -21,7 +21,8 @@
|
|||||||
</label>
|
</label>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</div>
|
</div>
|
||||||
<div class="py-2 text-center"><span class="font-bold text-warning">{{config('constants.limits.trial_period')}} days trial</span> included on all plans, without credit card details.</div>
|
<div class="py-2 text-center"><span class="font-bold text-warning">{{ config('constants.limits.trial_period') }}
|
||||||
|
days trial</span> included on all plans, without credit card details.</div>
|
||||||
<div x-show="selected === 'monthly'" class="flex justify-center h-10 mt-3 text-sm leading-6 ">
|
<div x-show="selected === 'monthly'" class="flex justify-center h-10 mt-3 text-sm leading-6 ">
|
||||||
<div>Save <span class="font-bold text-warning">1 month</span> annually with the yearly plans.
|
<div>Save <span class="font-bold text-warning">1 month</span> annually with the yearly plans.
|
||||||
</div>
|
</div>
|
||||||
@ -289,6 +290,170 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="pt-8 pb-12 text-4xl font-bold text-center text-white">Included in all plans</div>
|
||||||
|
<div class="grid grid-cols-1 gap-10 md:grid-cols-2 gap-y-28">
|
||||||
|
<div>
|
||||||
|
<div class="flex items-center gap-4 mb-4">
|
||||||
|
<div class="flex items-center justify-center w-10 h-10 text-white rounded-lg bg-coolgray-500">
|
||||||
|
<svg width="512" height="512" 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="M3 7a3 3 0 0 1 3-3h12a3 3 0 0 1 3 3v2a3 3 0 0 1-3 3H6a3 3 0 0 1-3-3zm12 13H6a3 3 0 0 1-3-3v-2a3 3 0 0 1 3-3h12M7 8v.01M7 16v.01M20 15l-2 3h3l-2 3" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="text-2xl font-semibold text-white">Bring Your Own Servers</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-1 text-base leading-7 text-gray-300">
|
||||||
|
Bring your own server from any cloud providers, or even your own server at home! All you need is SSH
|
||||||
|
access. You will have full control over your server, and you can even use it for other purposes.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="flex items-center gap-4 mb-4">
|
||||||
|
<div class="flex items-center justify-center w-10 h-10 text-white rounded-lg bg-coolgray-500">
|
||||||
|
<svg width="512" height="512" class="icon" viewBox="0 0 24 24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g fill="none" stroke="white" stroke-linecap="round" stroke-linejoin="round"
|
||||||
|
stroke-width="2">
|
||||||
|
<path
|
||||||
|
d="M7 7h10a2 2 0 0 1 2 2v1l1 1v3l-1 1v3a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2v-3l-1-1v-3l1-1V9a2 2 0 0 1 2-2zm3 9h4" />
|
||||||
|
<circle cx="8.5" cy="11.5" r=".5" fill="#000000" />
|
||||||
|
<circle cx="15.5" cy="11.5" r=".5" fill="#000000" />
|
||||||
|
<path d="M9 7L8 3m7 4l1-4" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="text-2xl font-semibold text-white">Server Automations</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-1 text-base leading-7 text-gray-300">
|
||||||
|
Once you connected your server, Coolify will start managing it and do a
|
||||||
|
lot of adminstrative tasks for you. You can also write your own scripts to
|
||||||
|
automate your server<span class="text-warning">*</span>.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="flex items-center gap-4 mb-4">
|
||||||
|
<div class="flex items-center justify-center w-10 h-10 text-white rounded-lg bg-coolgray-500">
|
||||||
|
<svg width="512" height="512" viewBox="0 0 24 24" class="icon"
|
||||||
|
xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
|
||||||
|
stroke-width="2">
|
||||||
|
<path d="M15 11h2a2 2 0 0 1 2 2v2m0 4a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2v-6a2 2 0 0 1 2-2h4" />
|
||||||
|
<path d="M11 16a1 1 0 1 0 2 0a1 1 0 1 0-2 0m-3-5V8m.347-3.631A4 4 0 0 1 16 6M3 3l18 18" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="text-2xl font-semibold text-white">No Vendor Lock-in</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-1 text-base leading-7 text-gray-300">
|
||||||
|
You own your own data. All configurations saved on your own servers, so if
|
||||||
|
you decide to stop using Coolify, you can still continue to manage your
|
||||||
|
deployed resources.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class="flex items-center gap-4 mb-4">
|
||||||
|
<div class="flex items-center justify-center w-10 h-10 text-white rounded-lg bg-coolgray-500">
|
||||||
|
<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" />
|
||||||
|
<rect x="3" y="4" width="18" height="12" rx="1" />
|
||||||
|
<path d="M7 20h10" />
|
||||||
|
<path d="M9 16v4" />
|
||||||
|
<path d="M15 16v4" />
|
||||||
|
<path d="M7 10h2l2 3l2 -6l1 3h3" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="text-2xl font-semibold text-white">Monitoring</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-1 text-base leading-7 text-gray-300">
|
||||||
|
Coolify will automatically monitor your configured servers and deployed
|
||||||
|
resources. Notifies you if something goes wrong on your favourite
|
||||||
|
channels, like Discord, Telegram, via Email and more...
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="flex items-center gap-4 mb-4">
|
||||||
|
<div class="flex items-center justify-center w-10 h-10 text-white rounded-lg bg-coolgray-500">
|
||||||
|
<svg width="512" height="512" class="icon" viewBox="0 0 24 24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
|
||||||
|
stroke-width="2">
|
||||||
|
<path d="M6 4h10l4 4v10a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2" />
|
||||||
|
<path d="M10 14a2 2 0 1 0 4 0a2 2 0 1 0-4 0m4-10v4H8V4" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="text-2xl font-semibold text-white">Automatic Backups</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-1 text-base leading-7 text-gray-300">
|
||||||
|
We automatically backup your databases to any S3 compatible solution. If
|
||||||
|
something goes wrong, you can easily restore your data with a few clicks.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="flex items-center gap-4 mb-4">
|
||||||
|
<div class="flex items-center justify-center w-10 h-10 text-white rounded-lg bg-coolgray-500">
|
||||||
|
<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" />
|
||||||
|
<polyline points="5 7 10 12 5 17" />
|
||||||
|
<line x1="13" y1="17" x2="19" y2="17" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="text-2xl font-semibold text-white">Powerful API</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-1 text-base leading-7 text-gray-300">
|
||||||
|
Programatically deploy, query, and manage your servers & resources.
|
||||||
|
Integrate to your CI/CD pipelines, or build your own custom integrations. <span
|
||||||
|
class="text-warning">*</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="flex items-center gap-4 mb-4">
|
||||||
|
<div class="flex items-center justify-center w-10 h-10 text-white rounded-lg bg-coolgray-500">
|
||||||
|
<svg width="512" height="512" class="icon" viewBox="0 0 24 24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
|
||||||
|
stroke-width="2">
|
||||||
|
<path
|
||||||
|
d="M4 18a2 2 0 1 0 4 0a2 2 0 1 0-4 0M4 6a2 2 0 1 0 4 0a2 2 0 1 0-4 0m12 12a2 2 0 1 0 4 0a2 2 0 1 0-4 0M6 8v8" />
|
||||||
|
<path d="M11 6h5a2 2 0 0 1 2 2v8" />
|
||||||
|
<path d="m14 9l-3-3l3-3" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="text-2xl font-semibold text-white">Push to Deploy</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-1 text-base leading-7 text-gray-300">
|
||||||
|
Git integration is default today. We support hosted (github.com,
|
||||||
|
gitlab.com<span class="inline-block text-warning">*</span>) or self-hosted<span class="text-warning">*</span>
|
||||||
|
(Github Enterprise, Gitlab) Git repositories.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="flex items-center gap-4 mb-4">
|
||||||
|
<div class="flex items-center justify-center w-10 h-10 text-white rounded-lg bg-coolgray-500">
|
||||||
|
<svg width="512" height="512" 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="M10 13a2 2 0 1 0 4 0a2 2 0 0 0-4 0m-2 8v-1a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v1M15 5a2 2 0 1 0 4 0a2 2 0 0 0-4 0m2 5h2a2 2 0 0 1 2 2v1M5 5a2 2 0 1 0 4 0a2 2 0 0 0-4 0m-2 8v-1a2 2 0 0 1 2-2h2" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="text-2xl font-semibold text-white">Pull Request Deployments</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-1 text-base leading-7 text-gray-300">
|
||||||
|
Automagically deploy new commits and pull requests separately to quickly
|
||||||
|
review contributions and speed up your teamwork!
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="pt-20 text-xs">
|
||||||
|
<span class="text-warning">*</span> Some features are work in progress and will be available soon.
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@isset($other)
|
@isset($other)
|
||||||
{{ $other }}
|
{{ $other }}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
<div class="pb-6">
|
<div class="pb-6">
|
||||||
|
<livewire:server.proxy.modal :server="$server" />
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<h1>Server</h1>
|
<h1>Server</h1>
|
||||||
@if ($server->settings->is_reachable)
|
@if ($server->settings->is_reachable)
|
||||||
|
@ -6,6 +6,5 @@ Please [click here]({{ $invitation_link }}) to accept the invitation.
|
|||||||
|
|
||||||
If you have any questions, please contact the team owner.<br><br>
|
If you have any questions, please contact the team owner.<br><br>
|
||||||
|
|
||||||
If it was not you who requested this invitation, please ignore this email, or instantly revoke the invitation by clicking [here]({{ $invitation_link }}/revoke).
|
If it was not you who requested this invitation, please ignore this email.
|
||||||
|
|
||||||
</x-emails.layout>
|
</x-emails.layout>
|
||||||
|
@ -21,7 +21,8 @@
|
|||||||
Copy from Instance Settings
|
Copy from Instance Settings
|
||||||
</x-forms.button>
|
</x-forms.button>
|
||||||
@endif
|
@endif
|
||||||
@if (isEmailEnabled($team) && auth()->user()->isAdminFromSession())
|
@if (isEmailEnabled($team) &&
|
||||||
|
auth()->user()->isAdminFromSession())
|
||||||
<x-forms.button onclick="sendTestEmail.showModal()"
|
<x-forms.button onclick="sendTestEmail.showModal()"
|
||||||
class="text-white normal-case btn btn-xs no-animation btn-primary">
|
class="text-white normal-case btn btn-xs no-animation btn-primary">
|
||||||
Send Test Email
|
Send Test Email
|
||||||
@ -51,61 +52,52 @@
|
|||||||
</x-forms.button>
|
</x-forms.button>
|
||||||
</form>
|
</form>
|
||||||
<div class="flex flex-col gap-4">
|
<div class="flex flex-col gap-4">
|
||||||
<details class="border rounded collapse border-coolgray-500 collapse-arrow ">
|
<div class="p-4 border border-coolgray-500">
|
||||||
<summary class="text-xl collapse-title">
|
<h3>SMTP Server</h3>
|
||||||
<div>SMTP Server</div>
|
<div class="w-32">
|
||||||
<div class="w-32">
|
<x-forms.checkbox instantSave id="team.smtp_enabled" label="Enabled" />
|
||||||
<x-forms.checkbox instantSave id="team.smtp_enabled" label="Enabled" />
|
|
||||||
</div>
|
|
||||||
</summary>
|
|
||||||
<div class="collapse-content">
|
|
||||||
<form wire:submit.prevent='submit' class="flex flex-col">
|
|
||||||
<div class="flex flex-col gap-4">
|
|
||||||
<div class="flex flex-col w-full gap-2 xl:flex-row">
|
|
||||||
<x-forms.input required id="team.smtp_host" placeholder="smtp.mailgun.org"
|
|
||||||
label="Host" />
|
|
||||||
<x-forms.input required id="team.smtp_port" placeholder="587" label="Port" />
|
|
||||||
<x-forms.input id="team.smtp_encryption" helper="If SMTP uses SSL, set it to 'tls'."
|
|
||||||
placeholder="tls" label="Encryption" />
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col w-full gap-2 xl:flex-row">
|
|
||||||
<x-forms.input id="team.smtp_username" label="SMTP Username" />
|
|
||||||
<x-forms.input id="team.smtp_password" type="password" label="SMTP Password" />
|
|
||||||
<x-forms.input id="team.smtp_timeout" helper="Timeout value for sending emails."
|
|
||||||
label="Timeout" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex justify-end gap-4 pt-6">
|
|
||||||
<x-forms.button type="submit">
|
|
||||||
Save
|
|
||||||
</x-forms.button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
</details>
|
<form wire:submit.prevent='submit' class="flex flex-col">
|
||||||
<details class="border rounded collapse border-coolgray-500 collapse-arrow">
|
<div class="flex flex-col gap-4">
|
||||||
<summary class="text-xl collapse-title">
|
<div class="flex flex-col w-full gap-2 xl:flex-row">
|
||||||
<div>Resend</div>
|
<x-forms.input required id="team.smtp_host" placeholder="smtp.mailgun.org" label="Host" />
|
||||||
<div class="w-32">
|
<x-forms.input required id="team.smtp_port" placeholder="587" label="Port" />
|
||||||
<x-forms.checkbox instantSave='instantSaveResend' id="team.resend_enabled" label="Enabled" />
|
<x-forms.input id="team.smtp_encryption" helper="If SMTP uses SSL, set it to 'tls'."
|
||||||
|
placeholder="tls" label="Encryption" />
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col w-full gap-2 xl:flex-row">
|
||||||
|
<x-forms.input id="team.smtp_username" label="SMTP Username" />
|
||||||
|
<x-forms.input id="team.smtp_password" type="password" label="SMTP Password" />
|
||||||
|
<x-forms.input id="team.smtp_timeout" helper="Timeout value for sending emails."
|
||||||
|
label="Timeout" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</summary>
|
<div class="flex justify-end gap-4 pt-6">
|
||||||
<div class="collapse-content">
|
<x-forms.button type="submit">
|
||||||
<form wire:submit.prevent='submitResend' class="flex flex-col">
|
Save
|
||||||
<div class="flex flex-col gap-4">
|
</x-forms.button>
|
||||||
<div class="flex flex-col w-full gap-2 xl:flex-row">
|
</div>
|
||||||
<x-forms.input required type="password" id="team.resend_api_key" placeholder="API key"
|
</form>
|
||||||
label="API Key" />
|
</div>
|
||||||
</div>
|
<div class="p-4 border border-coolgray-500">
|
||||||
</div>
|
<h3>Resend</h3>
|
||||||
<div class="flex justify-end gap-4 pt-6">
|
<div class="w-32">
|
||||||
<x-forms.button type="submit">
|
<x-forms.checkbox instantSave='instantSaveResend' id="team.resend_enabled" label="Enabled" />
|
||||||
Save
|
|
||||||
</x-forms.button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
</details>
|
<form wire:submit.prevent='submitResend' class="flex flex-col">
|
||||||
|
<div class="flex flex-col gap-4">
|
||||||
|
<div class="flex flex-col w-full gap-2 xl:flex-row">
|
||||||
|
<x-forms.input required type="password" id="team.resend_api_key" placeholder="API key"
|
||||||
|
label="API Key" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-end gap-4 pt-6">
|
||||||
|
<x-forms.button type="submit">
|
||||||
|
Save
|
||||||
|
</x-forms.button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
@if (isEmailEnabled($team) || data_get($team, 'use_instance_email_settings'))
|
@if (isEmailEnabled($team) || data_get($team, 'use_instance_email_settings'))
|
||||||
|
@ -4,8 +4,13 @@
|
|||||||
<x-forms.input id="name" label="Name" required />
|
<x-forms.input id="name" label="Name" required />
|
||||||
<x-forms.input id="description" label="Description" />
|
<x-forms.input id="description" label="Description" />
|
||||||
</div>
|
</div>
|
||||||
<x-forms.textarea id="value" rows="10" placeholder="-----BEGIN OPENSSH PRIVATE KEY-----"
|
<x-forms.textarea realtimeValidation id="value" rows="10"
|
||||||
label="Private Key" required />
|
placeholder="-----BEGIN OPENSSH PRIVATE KEY-----" label="Private Key" required />
|
||||||
|
<x-forms.button wire:click="generateNewKey">Generate new SSH key for me</x-forms.button>
|
||||||
|
<x-forms.textarea id="publicKey" rows="6" readonly label="Public Key" />
|
||||||
|
<span class="font-bold text-warning">ACTION REQUIRED: Copy the 'Public Key' to your server's
|
||||||
|
~/.ssh/authorized_keys
|
||||||
|
file.</span>
|
||||||
<x-forms.button type="submit">
|
<x-forms.button type="submit">
|
||||||
Save Private Key
|
Save Private Key
|
||||||
</x-forms.button>
|
</x-forms.button>
|
||||||
|
@ -1,16 +1,6 @@
|
|||||||
<div>
|
<div>
|
||||||
@if ($server->isFunctional())
|
@if ($server->isFunctional())
|
||||||
@if (data_get($server,'proxy.type'))
|
@if (data_get($server,'proxy.type'))
|
||||||
<x-modal submitWireAction="proxyStatusUpdated" modalId="startProxy">
|
|
||||||
<x-slot:modalBody>
|
|
||||||
<livewire:activity-monitor header="Proxy Startup Logs" />
|
|
||||||
</x-slot:modalBody>
|
|
||||||
<x-slot:modalSubmit>
|
|
||||||
<x-forms.button onclick="startProxy.close()" type="submit">
|
|
||||||
Close
|
|
||||||
</x-forms.button>
|
|
||||||
</x-slot:modalSubmit>
|
|
||||||
</x-modal>
|
|
||||||
<div x-init="$wire.loadProxyConfiguration">
|
<div x-init="$wire.loadProxyConfiguration">
|
||||||
@if ($selectedProxy === 'TRAEFIK_V2')
|
@if ($selectedProxy === 'TRAEFIK_V2')
|
||||||
<form wire:submit.prevent='submit'>
|
<form wire:submit.prevent='submit'>
|
||||||
|
12
resources/views/livewire/server/proxy/modal.blade.php
Normal file
12
resources/views/livewire/server/proxy/modal.blade.php
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<div>
|
||||||
|
<x-modal submitWireAction="proxyStatusUpdated" modalId="startProxy">
|
||||||
|
<x-slot:modalBody>
|
||||||
|
<livewire:activity-monitor header="Proxy Startup Logs" />
|
||||||
|
</x-slot:modalBody>
|
||||||
|
<x-slot:modalSubmit>
|
||||||
|
<x-forms.button onclick="startProxy.close()" type="submit">
|
||||||
|
Close
|
||||||
|
</x-forms.button>
|
||||||
|
</x-slot:modalSubmit>
|
||||||
|
</x-modal>
|
||||||
|
</div>
|
@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
<div class="flex gap-2" x-init="$wire.getProxyStatus">
|
<div class="flex gap-2" x-init="$wire.getProxyStatus">
|
||||||
@if ($server->proxy->status === 'running')
|
@if ($server->proxy->status === 'running')
|
||||||
<x-status.running text="Proxy Running" />
|
<x-status.running text="Proxy Running" />
|
||||||
|
@ -1,7 +1,34 @@
|
|||||||
<x-layout-subscription>
|
<x-layout-subscription>
|
||||||
@if ($settings->is_resale_license_active)
|
@if ($settings->is_resale_license_active)
|
||||||
<div class="flex justify-center mx-10">
|
@if (auth()->user()->isAdminFromSession())
|
||||||
<div x-data>
|
<div class="flex justify-center mx-10">
|
||||||
|
<div x-data>
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<h1>Subscription</h1>
|
||||||
|
<livewire:switch-team />
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center pb-8">
|
||||||
|
<span>Currently active team: <span
|
||||||
|
class="text-warning">{{ session('currentTeam.name') }}</span></span>
|
||||||
|
</div>
|
||||||
|
@if (request()->query->get('cancelled'))
|
||||||
|
<div class="mb-6 rounded alert alert-error">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6 stroke-current shrink-0" fill="none"
|
||||||
|
viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||||
|
</svg>
|
||||||
|
<span>Something went wrong with your subscription. Please try again or contact
|
||||||
|
support.</span>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
@if (config('subscription.provider') !== null)
|
||||||
|
<livewire:subscription.pricing-plans />
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@else
|
||||||
|
<div class="flex flex-col justify-center mx-10">
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<h1>Subscription</h1>
|
<h1>Subscription</h1>
|
||||||
<livewire:switch-team />
|
<livewire:switch-team />
|
||||||
@ -10,22 +37,10 @@
|
|||||||
<span>Currently active team: <span
|
<span>Currently active team: <span
|
||||||
class="text-warning">{{ session('currentTeam.name') }}</span></span>
|
class="text-warning">{{ session('currentTeam.name') }}</span></span>
|
||||||
</div>
|
</div>
|
||||||
@if (request()->query->get('cancelled'))
|
<div>You are not an admin or have been removed from this team. If this does not make sense, please <span class="text-white underline cursor-pointer" wire:click="help" onclick="help.showModal()">contact us</span>.</div>
|
||||||
<div class="mb-6 rounded alert alert-error">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6 stroke-current shrink-0" fill="none"
|
|
||||||
viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
|
||||||
d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
|
||||||
</svg>
|
|
||||||
<span>Something went wrong with your subscription. Please try again or contact support.</span>
|
|
||||||
</div>
|
|
||||||
@endif
|
|
||||||
@if (config('subscription.provider') !== null)
|
|
||||||
<livewire:subscription.pricing-plans />
|
|
||||||
@endif
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
@endif
|
||||||
@else
|
@else
|
||||||
<div class="px-10">Resale license is not active. Please contact your instance admin.</div>
|
<div class="px-10" >Resale license is not active. Please contact your instance admin.</div>
|
||||||
@endif
|
@endif
|
||||||
</x-layout-subscription>
|
</x-layout-subscription>
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
"version": "3.12.36"
|
"version": "3.12.36"
|
||||||
},
|
},
|
||||||
"v4": {
|
"v4": {
|
||||||
"version": "4.0.0-beta.36"
|
"version": "4.0.0-beta.37"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user