commit
c735ff545e
2
.github/workflows/development-build.yml
vendored
2
.github/workflows/development-build.yml
vendored
@ -52,7 +52,7 @@ jobs:
|
||||
push: true
|
||||
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:next-aarch64
|
||||
merge-manifest:
|
||||
runs-on: [self-hosted, x64]
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
2
.github/workflows/production-build.yml
vendored
2
.github/workflows/production-build.yml
vendored
@ -10,7 +10,7 @@ env:
|
||||
|
||||
jobs:
|
||||
amd64:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: [self-hosted, x64]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Login to ghcr.io
|
||||
|
@ -73,7 +73,7 @@ class RunRemoteProcess
|
||||
$this->time_start = hrtime(true);
|
||||
|
||||
$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) {
|
||||
$status = ProcessStatus::ERROR;
|
||||
|
@ -16,10 +16,7 @@ class CheckConfigurationSync
|
||||
|
||||
if ($reset || is_null($proxy_configuration)) {
|
||||
$proxy_configuration = Str::of(generate_default_proxy_configuration($server))->trim()->value;
|
||||
resolve(SaveConfigurationSync::class)($server, $proxy_configuration);
|
||||
return $proxy_configuration;
|
||||
}
|
||||
|
||||
return $proxy_configuration;
|
||||
}
|
||||
}
|
||||
|
@ -7,11 +7,12 @@ use Illuminate\Support\Str;
|
||||
|
||||
class SaveConfigurationSync
|
||||
{
|
||||
public function __invoke(Server $server, string $configuration)
|
||||
public function __invoke(Server $server)
|
||||
{
|
||||
try {
|
||||
$proxy_settings = resolve(CheckConfigurationSync::class)($server, true);
|
||||
$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->save();
|
||||
|
@ -24,7 +24,7 @@ use Illuminate\Console\Command;
|
||||
use Illuminate\Mail\Message;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Mail;
|
||||
use Str;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
use function Laravel\Prompts\confirm;
|
||||
use function Laravel\Prompts\select;
|
||||
@ -62,7 +62,7 @@ class Emails extends Command
|
||||
'application-status-changed' => 'Application - Status Changed',
|
||||
'backup-success' => 'Database - Backup Success',
|
||||
'backup-failed' => 'Database - Backup Failed',
|
||||
'invitation-link' => 'Invitation Link',
|
||||
// 'invitation-link' => 'Invitation Link',
|
||||
'waitlist-invitation-link' => 'Waitlist Invitation Link',
|
||||
'waitlist-confirmation' => 'Waitlist Confirmation',
|
||||
'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->sendEmail();
|
||||
break;
|
||||
case 'invitation-link':
|
||||
$user = User::all()->first();
|
||||
$invitation = TeamInvitation::whereEmail($user->email)->first();
|
||||
if (!$invitation) {
|
||||
$invitation = TeamInvitation::create([
|
||||
'uuid' => Str::uuid(),
|
||||
'email' => $user->email,
|
||||
'team_id' => 1,
|
||||
'link' => 'http://example.com',
|
||||
]);
|
||||
}
|
||||
$this->mail = (new InvitationLink($user))->toMail();
|
||||
$this->sendEmail();
|
||||
break;
|
||||
// case 'invitation-link':
|
||||
// $user = User::all()->first();
|
||||
// $invitation = TeamInvitation::whereEmail($user->email)->first();
|
||||
// if (!$invitation) {
|
||||
// $invitation = TeamInvitation::create([
|
||||
// 'uuid' => Str::uuid(),
|
||||
// 'email' => $user->email,
|
||||
// 'team_id' => 1,
|
||||
// 'link' => 'http://example.com',
|
||||
// ]);
|
||||
// }
|
||||
// $this->mail = (new InvitationLink($user))->toMail();
|
||||
// $this->sendEmail();
|
||||
// break;
|
||||
case 'waitlist-invitation-link':
|
||||
$this->mail = new MailMessage();
|
||||
$this->mail->view('emails.waitlist-invitation', [
|
||||
|
@ -21,7 +21,7 @@ class Kernel extends ConsoleKernel
|
||||
if (isDev()) {
|
||||
// $schedule->job(new ContainerStatusJob(Server::find(0)))->everyTenMinutes()->onOneServer();
|
||||
// $schedule->command('horizon:snapshot')->everyMinute();
|
||||
// $schedule->job(new CleanupInstanceStuffsJob)->everyMinute();
|
||||
$schedule->job(new CleanupInstanceStuffsJob)->everyMinute()->onOneServer();
|
||||
// $schedule->job(new CheckResaleLicenseJob)->hourly();
|
||||
// $schedule->job(new DockerCleanupJob)->everyOddHour();
|
||||
// $this->instance_auto_update($schedule);
|
||||
@ -29,7 +29,7 @@ class Kernel extends ConsoleKernel
|
||||
$this->check_resources($schedule);
|
||||
} else {
|
||||
$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 DockerCleanupJob)->everyTenMinutes()->onOneServer();
|
||||
$this->instance_auto_update($schedule);
|
||||
|
@ -3,21 +3,18 @@
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\Project;
|
||||
use App\Models\S3Storage;
|
||||
use App\Models\StandalonePostgresql;
|
||||
use App\Models\TeamInvitation;
|
||||
use App\Models\User;
|
||||
use Auth;
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
use Illuminate\Foundation\Validation\ValidatesRequests;
|
||||
use Illuminate\Routing\Controller as BaseController;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Crypt;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Str;
|
||||
use Throwable;
|
||||
use Str;
|
||||
|
||||
|
||||
class Controller extends BaseController
|
||||
{
|
||||
@ -35,8 +32,15 @@ class Controller extends BaseController
|
||||
return redirect()->route('login');
|
||||
}
|
||||
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);
|
||||
$team = $user->teams()->first();
|
||||
session(['currentTeam' => $team]);
|
||||
return redirect()->route('dashboard');
|
||||
}
|
||||
@ -137,24 +141,20 @@ class Controller extends BaseController
|
||||
try {
|
||||
$invitation = TeamInvitation::whereUuid(request()->route('uuid'))->firstOrFail();
|
||||
$user = User::whereEmail($invitation->email)->firstOrFail();
|
||||
if (is_null(auth()->user())) {
|
||||
return redirect()->route('login');
|
||||
}
|
||||
if (auth()->user()->id !== $user->id) {
|
||||
abort(401);
|
||||
}
|
||||
|
||||
$createdAt = $invitation->created_at;
|
||||
$diff = $createdAt->diffInMinutes(now());
|
||||
if ($diff <= config('constants.invitation.link.expiration')) {
|
||||
$invitationValid = $invitation->isValid();
|
||||
if ($invitationValid) {
|
||||
$user->teams()->attach($invitation->team->id, ['role' => $invitation->role]);
|
||||
refreshSession($invitation->team);
|
||||
$invitation->delete();
|
||||
return redirect()->route('team.index');
|
||||
} else {
|
||||
$invitation->delete();
|
||||
abort(401);
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
ray($e->getMessage());
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ class Help extends Component
|
||||
];
|
||||
public function mount()
|
||||
{
|
||||
$this->path = Route::current()->uri();
|
||||
$this->path = Route::current()?->uri() ?? null;
|
||||
if (isDev()) {
|
||||
$this->description = "I'm having trouble with {$this->path}";
|
||||
$this->subject = "Help with {$this->path}";
|
||||
@ -41,7 +41,7 @@ class Help extends Component
|
||||
]
|
||||
);
|
||||
$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.');
|
||||
} catch (\Throwable $e) {
|
||||
return general_error_handler($e, $this);
|
||||
|
@ -46,9 +46,6 @@ class DiscordSettings extends Component
|
||||
public function saveModel()
|
||||
{
|
||||
$this->team->save();
|
||||
if (is_a($this->team, Team::class)) {
|
||||
refreshSession();
|
||||
}
|
||||
$this->emit('success', 'Settings saved.');
|
||||
}
|
||||
|
||||
|
@ -110,9 +110,6 @@ class EmailSettings extends Component
|
||||
public function saveModel()
|
||||
{
|
||||
$this->team->save();
|
||||
if (is_a($this->team, Team::class)) {
|
||||
refreshSession();
|
||||
}
|
||||
$this->emit('success', 'Settings saved.');
|
||||
}
|
||||
public function submit()
|
||||
@ -141,10 +138,11 @@ class EmailSettings extends Component
|
||||
try {
|
||||
$this->resetErrorBag();
|
||||
$this->validate([
|
||||
'team.smtp_from_address' => 'required|email',
|
||||
'team.smtp_from_name' => 'required',
|
||||
'team.resend_api_key' => 'required'
|
||||
]);
|
||||
$this->team->save();
|
||||
refreshSession();
|
||||
$this->emit('success', 'Settings saved successfully.');
|
||||
} catch (\Throwable $e) {
|
||||
$this->team->resend_enabled = false;
|
||||
|
@ -52,9 +52,6 @@ class TelegramSettings extends Component
|
||||
public function saveModel()
|
||||
{
|
||||
$this->team->save();
|
||||
if (is_a($this->team, Team::class)) {
|
||||
refreshSession();
|
||||
}
|
||||
$this->emit('success', 'Settings saved.');
|
||||
}
|
||||
|
||||
|
@ -4,13 +4,15 @@ namespace App\Http\Livewire\PrivateKey;
|
||||
|
||||
use App\Models\PrivateKey;
|
||||
use Livewire\Component;
|
||||
use phpseclib3\Crypt\PublicKeyLoader;
|
||||
|
||||
class Create extends Component
|
||||
{
|
||||
public string|null $from = null;
|
||||
public ?string $from = null;
|
||||
public string $name;
|
||||
public string|null $description = null;
|
||||
public ?string $description = null;
|
||||
public string $value;
|
||||
public ?string $publicKey = null;
|
||||
protected $rules = [
|
||||
'name' => 'required|string',
|
||||
'value' => 'required|string',
|
||||
@ -20,6 +22,23 @@ class Create extends Component
|
||||
'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()
|
||||
{
|
||||
$this->validate();
|
||||
|
@ -2,8 +2,6 @@
|
||||
|
||||
namespace App\Http\Livewire\Project\Application;
|
||||
|
||||
use App\Jobs\ApplicationContainerStatusJob;
|
||||
use App\Jobs\ContainerStatusJob;
|
||||
use App\Models\Application;
|
||||
use App\Models\ApplicationPreview;
|
||||
use Illuminate\Support\Collection;
|
||||
|
@ -5,7 +5,7 @@ namespace App\Http\Livewire\Project\Shared\EnvironmentVariable;
|
||||
use App\Models\EnvironmentVariable;
|
||||
use Livewire\Component;
|
||||
use Visus\Cuid2\Cuid2;
|
||||
use Str;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class All extends Component
|
||||
{
|
||||
|
@ -88,7 +88,9 @@ class Form extends Component
|
||||
public function submit()
|
||||
{
|
||||
$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)) {
|
||||
$this->emit('error', 'IP address is already in use by another team.');
|
||||
return;
|
||||
|
@ -48,7 +48,7 @@ class Proxy extends Component
|
||||
public function submit()
|
||||
{
|
||||
try {
|
||||
resolve(SaveConfigurationSync::class)($this->server, $this->proxy_settings);
|
||||
resolve(SaveConfigurationSync::class)($this->server);
|
||||
|
||||
$this->server->proxy->redirect_url = $this->redirect_url;
|
||||
$this->server->save();
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Http\Livewire\Server\Proxy;
|
||||
|
||||
use App\Actions\Proxy\SaveConfigurationSync;
|
||||
use App\Actions\Proxy\StartProxy;
|
||||
use App\Models\Server;
|
||||
use Livewire\Component;
|
||||
@ -21,7 +22,7 @@ class Deploy extends Component
|
||||
$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);
|
||||
|
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()
|
||||
{
|
||||
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) {
|
||||
return general_error_handler(err: $e, that: $this);
|
||||
}
|
||||
|
@ -44,6 +44,7 @@ class PricingPlans extends Component
|
||||
return;
|
||||
}
|
||||
$payload = [
|
||||
'billing_address_collection' => 'required',
|
||||
'client_reference_id' => auth()->user()->id . ':' . currentTeam()->id,
|
||||
'line_items' => [[
|
||||
'price' => $priceId,
|
||||
|
@ -12,7 +12,6 @@ class Delete extends Component
|
||||
$currentTeam = currentTeam();
|
||||
$currentTeam->delete();
|
||||
|
||||
$team = auth()->user()->teams()->first();
|
||||
$currentTeam->members->each(function ($user) use ($currentTeam) {
|
||||
if ($user->id === auth()->user()->id) {
|
||||
return;
|
||||
|
@ -27,7 +27,6 @@ class Form extends Component
|
||||
$this->validate();
|
||||
try {
|
||||
$this->team->save();
|
||||
refreshSession();
|
||||
} catch (\Throwable $e) {
|
||||
return general_error_handler($e, $this);
|
||||
}
|
||||
|
@ -4,9 +4,13 @@ namespace App\Http\Livewire\Team;
|
||||
|
||||
use App\Models\TeamInvitation;
|
||||
use App\Models\User;
|
||||
use App\Notifications\TransactionalEmails\InvitationLink;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Livewire\Component;
|
||||
use Visus\Cuid2\Cuid2;
|
||||
use Illuminate\Support\Facades\Crypt;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class InviteLink extends Component
|
||||
{
|
||||
@ -20,53 +24,68 @@ class InviteLink extends Component
|
||||
|
||||
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 {
|
||||
$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');
|
||||
if ($member_emails->contains($this->email)) {
|
||||
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 ($invitation->exists()) {
|
||||
$created_at = $invitation->first()->created_at;
|
||||
$diff = $created_at->diffInMinutes(now());
|
||||
if ($diff <= config('constants.invitation.link.expiration')) {
|
||||
return general_error_handler(that: $this, customErrorMessage: "Invitation already sent to $this->email and waiting for action.");
|
||||
if (is_null($user)) {
|
||||
$password = Str::password();
|
||||
$user = User::create([
|
||||
'name' => Str::of($this->email)->before('@'),
|
||||
'email' => $this->email,
|
||||
'password' => Hash::make($password),
|
||||
'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 {
|
||||
$invitation->delete();
|
||||
}
|
||||
}
|
||||
|
||||
TeamInvitation::firstOrCreate([
|
||||
$invitation = TeamInvitation::firstOrCreate([
|
||||
'team_id' => currentTeam()->id,
|
||||
'uuid' => $uuid,
|
||||
'email' => $this->email,
|
||||
'role' => $this->role,
|
||||
'link' => $link,
|
||||
'via' => $isEmail ? 'email' : 'link',
|
||||
'via' => $sendEmail ? 'email' : 'link',
|
||||
]);
|
||||
if ($isEmail) {
|
||||
$user->first()->notify(new InvitationLink);
|
||||
if ($sendEmail) {
|
||||
$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('refreshInvitations');
|
||||
return;
|
||||
} else {
|
||||
$this->emit('success', 'Invitation link generated.');
|
||||
$this->emit('refreshInvitations');
|
||||
}
|
||||
$this->emit('refreshInvitations');
|
||||
} catch (\Throwable $e) {
|
||||
$error_message = $e->getMessage();
|
||||
if ($e->getCode() === '23505') {
|
||||
@ -75,9 +94,4 @@ class InviteLink extends Component
|
||||
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;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Livewire\Component;
|
||||
|
||||
class Member extends Component
|
||||
@ -24,6 +25,10 @@ class Member extends Component
|
||||
public function remove()
|
||||
{
|
||||
$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');
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ use App\Jobs\SendConfirmationForWaitlistJob;
|
||||
use App\Models\User;
|
||||
use App\Models\Waitlist;
|
||||
use Livewire\Component;
|
||||
use Str;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class Index extends Component
|
||||
{
|
||||
|
@ -5,6 +5,7 @@ namespace App\Http\Middleware;
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class IsBoardingFlow
|
||||
{
|
||||
@ -17,6 +18,9 @@ class IsBoardingFlow
|
||||
{
|
||||
// ray()->showQueries()->color('orange');
|
||||
if (showBoarding() && !in_array($request->path(), allowedPathsForBoardingAccounts())) {
|
||||
if (Str::startsWith($request->path(), 'invitations')) {
|
||||
return $next($request);
|
||||
}
|
||||
return redirect('boarding');
|
||||
}
|
||||
return $next($request);
|
||||
|
@ -5,6 +5,7 @@ namespace App\Http\Middleware;
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class IsSubscriptionValid
|
||||
{
|
||||
@ -31,6 +32,9 @@ class IsSubscriptionValid
|
||||
if (!isSubscriptionActive() && !isSubscriptionOnGracePeriod()) {
|
||||
// ray('SubscriptionValid Middleware');
|
||||
if (!in_array($request->path(), allowedPathsForUnsubscribedAccounts())) {
|
||||
if (Str::startsWith($request->path(), 'invitations')) {
|
||||
return $next($request);
|
||||
}
|
||||
return redirect('subscription');
|
||||
} else {
|
||||
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()
|
||||
{
|
||||
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.
|
||||
return 'exit 0';
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Models\TeamInvitation;
|
||||
use App\Models\Waitlist;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||
@ -32,7 +33,12 @@ class CleanupInstanceStuffsJob implements ShouldQueue, ShouldBeUnique, ShouldBeE
|
||||
} catch (\Throwable $e) {
|
||||
send_internal_notification('CleanupInstanceStuffsJob failed with error: ' . $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();
|
||||
}
|
||||
}
|
||||
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\Middleware\WithoutOverlapping;
|
||||
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;
|
||||
|
||||
@ -89,7 +89,6 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeUnique, ShouldBeEncrypt
|
||||
$labels = data_get($container, 'Config.Labels');
|
||||
$labels = Arr::undot(format_docker_labels_to_json($labels));
|
||||
$labelId = data_get($labels, 'coolify.applicationId');
|
||||
ray($labelId);
|
||||
if ($labelId) {
|
||||
if (str_contains($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\SerializesModels;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Str;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class SendMessageToTelegramJob implements ShouldQueue, ShouldBeEncrypted
|
||||
{
|
||||
|
@ -19,6 +19,13 @@ class Team extends Model implements SendsDiscord, SendsEmail
|
||||
'resend_api_key' => 'encrypted',
|
||||
];
|
||||
|
||||
protected static function booted()
|
||||
{
|
||||
static::saved(function () {
|
||||
refreshSession();
|
||||
});
|
||||
}
|
||||
|
||||
public function routeNotificationForDiscord()
|
||||
{
|
||||
return data_get($this, 'discord_webhook_url', null);
|
||||
|
@ -19,4 +19,13 @@ class TeamInvitation extends Model
|
||||
{
|
||||
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\TransactionalEmails\ResetPassword as TransactionalEmailsResetPassword;
|
||||
use Cache;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Laravel\Fortify\TwoFactorAuthenticatable;
|
||||
use Laravel\Sanctum\HasApiTokens;
|
||||
|
||||
@ -61,7 +61,7 @@ class User extends Authenticatable implements SendsEmail
|
||||
|
||||
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()
|
||||
@ -78,7 +78,8 @@ class User extends Authenticatable implements SendsEmail
|
||||
if ($is_part_of_root_team && $is_admin_of_root_team) {
|
||||
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';
|
||||
}
|
||||
|
||||
|
@ -39,7 +39,7 @@ trait ExecuteRemoteCommand
|
||||
$this->save = data_get($single_command, 'save');
|
||||
|
||||
$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();
|
||||
$new_log_entry = [
|
||||
'command' => $command,
|
||||
|
@ -24,6 +24,7 @@ class Textarea extends Component
|
||||
public bool $disabled = false,
|
||||
public bool $readonly = false,
|
||||
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"
|
||||
) {
|
||||
//
|
||||
|
@ -75,7 +75,6 @@ function getApplicationContainerStatus(Application $application) {
|
||||
}
|
||||
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);
|
||||
if (!$container) {
|
||||
return 'exited';
|
||||
|
@ -15,6 +15,7 @@ use Illuminate\Support\Facades\Process;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Sleep;
|
||||
use Spatie\Activitylog\Models\Activity;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
function remote_process(
|
||||
array $command,
|
||||
@ -49,20 +50,23 @@ function remote_process(
|
||||
])();
|
||||
}
|
||||
|
||||
function removePrivateKeyFromSshAgent(Server $server)
|
||||
{
|
||||
if (data_get($server, 'privateKey.private_key') === null) {
|
||||
throw new \Exception("Server {$server->name} does not have a private key");
|
||||
}
|
||||
processWithEnv()->run("echo '{$server->privateKey->private_key}' | ssh-add -d -");
|
||||
}
|
||||
// function removePrivateKeyFromSshAgent(Server $server)
|
||||
// {
|
||||
// if (data_get($server, 'privateKey.private_key') === null) {
|
||||
// throw new \Exception("Server {$server->name} does not have a private key");
|
||||
// }
|
||||
// // processWithEnv()->run("echo '{$server->privateKey->private_key}' | ssh-add -d -");
|
||||
// }
|
||||
function addPrivateKeyToSshAgent(Server $server)
|
||||
{
|
||||
if (data_get($server, 'privateKey.private_key') === null) {
|
||||
throw new \Exception("Server {$server->name} does not have a private key");
|
||||
}
|
||||
// ray('adding key', $server->privateKey->private_key);
|
||||
processWithEnv()->run("echo '{$server->privateKey->private_key}' | ssh-add -q -");
|
||||
$sshKeyFileLocation = "id.root@{$server->uuid}";
|
||||
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)
|
||||
@ -71,7 +75,7 @@ function generateSshCommand(string $server_ip, string $user, string $port, strin
|
||||
if (!$server) {
|
||||
throw new \Exception("Server with ip {$server_ip} not found");
|
||||
}
|
||||
addPrivateKeyToSshAgent($server);
|
||||
$privateKeyLocation = addPrivateKeyToSshAgent($server);
|
||||
$timeout = config('constants.ssh.command_timeout');
|
||||
$connectionTimeout = config('constants.ssh.connection_timeout');
|
||||
$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 ';
|
||||
}
|
||||
$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 ConnectTimeout=$connectionTimeout "
|
||||
. "-o ServerAliveInterval=$serverInterval "
|
||||
@ -97,28 +102,11 @@ function generateSshCommand(string $server_ip, string $user, string $port, strin
|
||||
// ray($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)
|
||||
{
|
||||
$command_string = implode("\n", $command);
|
||||
$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());
|
||||
$exitCode = $process->exitCode();
|
||||
if ($exitCode !== 0) {
|
||||
@ -169,14 +157,8 @@ function decode_remote_command_output(?ApplicationDeploymentQueue $application_d
|
||||
function refresh_server_connection(PrivateKey $private_key)
|
||||
{
|
||||
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());
|
||||
// check if user is authenticated
|
||||
// if (currentTeam()->id) {
|
||||
// currentTeam()->privateKeys = PrivateKey::where('team_id', currentTeam()->id)->get();
|
||||
// }
|
||||
}
|
||||
removePrivateKeyFromSshAgent($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)
|
||||
{
|
||||
$commands = collect(["jq", "jc"]);
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\Team;
|
||||
use App\Models\User;
|
||||
use App\Notifications\Channels\DiscordChannel;
|
||||
use App\Notifications\Channels\EmailChannel;
|
||||
use App\Notifications\Channels\TelegramChannel;
|
||||
@ -10,6 +11,7 @@ use DanHarrin\LivewireRateLimiting\Exceptions\TooManyRequestsException;
|
||||
use Illuminate\Database\QueryException;
|
||||
use Illuminate\Mail\Message;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
@ -60,7 +62,11 @@ function showBoarding(): bool
|
||||
function refreshSession(?Team $team = null): void
|
||||
{
|
||||
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::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
|
||||
->to($email)
|
||||
->replyTo($email)
|
||||
->cc($cc)
|
||||
->subject($mail->subject)
|
||||
->html((string) $mail->render())
|
||||
|
@ -56,7 +56,7 @@ function isSubscriptionActive()
|
||||
}
|
||||
$subscription = $team?->subscription;
|
||||
|
||||
if (!$subscription) {
|
||||
if (is_null($subscription)) {
|
||||
return false;
|
||||
}
|
||||
if (isLemon()) {
|
||||
|
@ -8,5 +8,4 @@ return [
|
||||
'dev_webhook' => env('SERVEO_URL'),
|
||||
'base_config_path' => env('BASE_CONFIG_PATH', '/data/coolify'),
|
||||
'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
|
||||
// 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'),
|
||||
// When left empty or `null` the Laravel environment will be used
|
||||
'environment' => config('app.env'),
|
||||
|
@ -1,3 +1,3 @@
|
||||
<?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"
|
||||
AUTORUN_LARAVEL_STORAGE_LINK: "false"
|
||||
AUTORUN_LARAVEL_MIGRATION: "false"
|
||||
SSH_AUTH_SOCK: "/tmp/coolify-ssh-agent.sock"
|
||||
volumes:
|
||||
- .:/var/www/html/:cached
|
||||
postgres:
|
||||
|
@ -64,7 +64,6 @@ services:
|
||||
- LEMON_SQUEEZY_BASIC_PLAN_IDS
|
||||
- LEMON_SQUEEZY_PRO_PLAN_IDS
|
||||
- LEMON_SQUEEZY_ULTIMATE_PLAN_IDS
|
||||
- SSH_AUTH_SOCK="/tmp/coolify-ssh-agent.sock"
|
||||
ports:
|
||||
- "${APP_PORT:-8000}:80"
|
||||
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>
|
||||
@endif
|
||||
<textarea placeholder="{{ $placeholder }}" {{ $attributes->merge(['class' => $defaultClass]) }}
|
||||
wire:model.defer={{ $id }} @disabled($disabled) @readonly($readonly) @required($required)
|
||||
id="{{ $id }}" name="{{ $name }}" name={{ $id }} wire:model.defer={{ $value ?? $id }}
|
||||
wire:dirty.class="input-warning"></textarea>
|
||||
@if ($realtimeValidation) wire:model.debounce.500ms="{{ $id }}"
|
||||
@else
|
||||
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)
|
||||
<label class="label">
|
||||
<span class="text-red-500 label-text-alt">{{ $message }}</span>
|
||||
|
@ -21,7 +21,8 @@
|
||||
</label>
|
||||
</fieldset>
|
||||
</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>Save <span class="font-bold text-warning">1 month</span> annually with the yearly plans.
|
||||
</div>
|
||||
@ -289,6 +290,170 @@
|
||||
</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>
|
||||
@isset($other)
|
||||
{{ $other }}
|
||||
|
@ -1,4 +1,5 @@
|
||||
<div class="pb-6">
|
||||
<livewire:server.proxy.modal :server="$server" />
|
||||
<div class="flex items-center gap-2">
|
||||
<h1>Server</h1>
|
||||
@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 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>
|
||||
|
@ -21,7 +21,8 @@
|
||||
Copy from Instance Settings
|
||||
</x-forms.button>
|
||||
@endif
|
||||
@if (isEmailEnabled($team) && auth()->user()->isAdminFromSession())
|
||||
@if (isEmailEnabled($team) &&
|
||||
auth()->user()->isAdminFromSession())
|
||||
<x-forms.button onclick="sendTestEmail.showModal()"
|
||||
class="text-white normal-case btn btn-xs no-animation btn-primary">
|
||||
Send Test Email
|
||||
@ -51,61 +52,52 @@
|
||||
</x-forms.button>
|
||||
</form>
|
||||
<div class="flex flex-col gap-4">
|
||||
<details class="border rounded collapse border-coolgray-500 collapse-arrow ">
|
||||
<summary class="text-xl collapse-title">
|
||||
<div>SMTP Server</div>
|
||||
<div class="w-32">
|
||||
<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 class="p-4 border border-coolgray-500">
|
||||
<h3>SMTP Server</h3>
|
||||
<div class="w-32">
|
||||
<x-forms.checkbox instantSave id="team.smtp_enabled" label="Enabled" />
|
||||
</div>
|
||||
</details>
|
||||
<details class="border rounded collapse border-coolgray-500 collapse-arrow">
|
||||
<summary class="text-xl collapse-title">
|
||||
<div>Resend</div>
|
||||
<div class="w-32">
|
||||
<x-forms.checkbox instantSave='instantSaveResend' id="team.resend_enabled" label="Enabled" />
|
||||
<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>
|
||||
</summary>
|
||||
<div class="collapse-content">
|
||||
<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 class="flex justify-end gap-4 pt-6">
|
||||
<x-forms.button type="submit">
|
||||
Save
|
||||
</x-forms.button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="p-4 border border-coolgray-500">
|
||||
<h3>Resend</h3>
|
||||
<div class="w-32">
|
||||
<x-forms.checkbox instantSave='instantSaveResend' id="team.resend_enabled" label="Enabled" />
|
||||
</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>
|
||||
@endif
|
||||
@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="description" label="Description" />
|
||||
</div>
|
||||
<x-forms.textarea id="value" rows="10" placeholder="-----BEGIN OPENSSH PRIVATE KEY-----"
|
||||
label="Private Key" required />
|
||||
<x-forms.textarea realtimeValidation id="value" rows="10"
|
||||
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">
|
||||
Save Private Key
|
||||
</x-forms.button>
|
||||
|
@ -1,16 +1,6 @@
|
||||
<div>
|
||||
@if ($server->isFunctional())
|
||||
@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">
|
||||
@if ($selectedProxy === 'TRAEFIK_V2')
|
||||
<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">
|
||||
@if ($server->proxy->status === 'running')
|
||||
<x-status.running text="Proxy Running" />
|
||||
|
@ -1,7 +1,34 @@
|
||||
<x-layout-subscription>
|
||||
@if ($settings->is_resale_license_active)
|
||||
<div class="flex justify-center mx-10">
|
||||
<div x-data>
|
||||
@if (auth()->user()->isAdminFromSession())
|
||||
<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">
|
||||
<h1>Subscription</h1>
|
||||
<livewire:switch-team />
|
||||
@ -10,22 +37,10 @@
|
||||
<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>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>
|
||||
</div>
|
||||
@endif
|
||||
@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
|
||||
</x-layout-subscription>
|
||||
|
@ -4,7 +4,7 @@
|
||||
"version": "3.12.36"
|
||||
},
|
||||
"v4": {
|
||||
"version": "4.0.0-beta.36"
|
||||
"version": "4.0.0-beta.37"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user