Compare commits

...

14 Commits

Author SHA1 Message Date
Andras Bacsai
cc8c6c5d16 Merge pull request #1221 from coollabsio/next
UI/UX fixes
2023-09-11 13:59:19 +02:00
Andras Bacsai
4782446f42 ui: show registered users on waitlist page 2023-09-11 13:53:08 +02:00
Andras Bacsai
230155312f ui: services are not availble yet 2023-09-11 13:52:54 +02:00
Andras Bacsai
3ab4365fca ui: user should know that the public key 2023-09-11 13:42:32 +02:00
Andras Bacsai
7349068b95 Merge pull request #1220 from coollabsio/next
Email issues fix
2023-09-11 12:55:52 +02:00
Andras Bacsai
da6cc151d1 fix: email sending error 2023-09-11 12:31:31 +02:00
Andras Bacsai
50527cf0a3 fix: testing email
fix: expired invitiation link
2023-09-11 12:16:26 +02:00
Andras Bacsai
26f490bb00 Merge pull request #1219 from coollabsio/next
Fixes
2023-09-11 10:50:18 +02:00
Andras Bacsai
dc4f412227 fix: recovery code 2023-09-11 10:41:39 +02:00
Andras Bacsai
ec4234e243 fix: only send internal notifcations to enabled channels 2023-09-11 10:19:46 +02:00
Andras Bacsai
f47fcb01ce Merge pull request #1218 from coollabsio/next
feat: generate public key from private keys
2023-09-11 10:16:48 +02:00
Andras Bacsai
02f6673345 feat: generate public key from private keys 2023-09-11 10:15:45 +02:00
Andras Bacsai
e6cd8702b5 Merge pull request #1216 from coollabsio/next
v4.0.0-beta.32
2023-09-10 15:34:08 +02:00
Andras Bacsai
fda4ea8cca fix: errors in views 2023-09-10 15:33:00 +02:00
23 changed files with 123 additions and 63 deletions

View File

@@ -2,12 +2,14 @@
namespace App\Console\Commands;
use App\Jobs\SendConfirmationForWaitlistJob;
use App\Models\Application;
use App\Models\ApplicationPreview;
use App\Models\ScheduledDatabaseBackup;
use App\Models\StandalonePostgresql;
use App\Models\TeamInvitation;
use App\Models\User;
use App\Models\Waitlist;
use App\Notifications\Application\DeploymentFailed;
use App\Notifications\Application\DeploymentSuccess;
use App\Notifications\Application\StatusChanged;
@@ -19,6 +21,7 @@ use Exception;
use Illuminate\Console\Command;
use Illuminate\Mail\Message;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Support\Facades\Process;
use Mail;
use Str;
@@ -148,23 +151,20 @@ class TestEmail extends Command
case 'waitlist-invitation-link':
$this->mail = new MailMessage();
$this->mail->view('emails.waitlist-invitation', [
'email' => 'test2@example.com',
'password' => "supersecretpassword",
'loginLink' => 'https://coolify.io',
]);
$this->mail->subject('Congratulations! You are invited to join Coolify Cloud.');
$this->sendEmail();
break;
case 'waitlist-confirmation':
$this->mail = new MailMessage();
$this->mail->view(
'emails.waitlist-confirmation',
[
'confirmation_url' => 'http://example.com',
'cancel_url' => 'http://example.com',
]
);
$this->mail->subject('You are on the waitlist!');
$this->sendEmail();
$found = Waitlist::where('email', $this->email)->first();
if ($found) {
SendConfirmationForWaitlistJob::dispatch($this->email, $found->uuid);
} else {
throw new Exception('Waitlist not found');
}
break;
}
}

View File

@@ -91,8 +91,6 @@ class WaitlistInvite extends Command
$loginLink = route('auth.link', ['token' => $token]);
$mail = new MailMessage();
$mail->view('emails.waitlist-invitation', [
'email' => $this->next_patient->email,
'password' => $this->password,
'loginLink' => $loginLink,
]);
$mail->subject('Congratulations! You are invited to join Coolify Cloud.');

View File

@@ -135,7 +135,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
{
$this->selectedExistingPrivateKey = null;
$this->privateKeyType = $type;
if ($type === 'create' && !isDev()) {
if ($type === 'create') {
$this->createNewPrivateKey();
}
$this->currentState = 'create-private-key';

View File

@@ -8,7 +8,7 @@ use Livewire\Component;
class Change extends Component
{
public PrivateKey $private_key;
public $public_key;
protected $rules = [
'private_key.name' => 'required|string',
'private_key.description' => 'nullable|string',
@@ -21,6 +21,14 @@ class Change extends Component
'private_key.private_key' => 'private key'
];
public function mount()
{
try {
$this->public_key = $this->private_key->publicKey();
}catch(\Exception $e) {
return general_error_handler(err: $e, that: $this);
}
}
public function delete()
{
try {

View File

@@ -10,6 +10,7 @@ use Livewire\Component;
class Index extends Component
{
public string $email;
public int $users = 0;
public int $waitingInLine = 0;
protected $rules = [
@@ -22,6 +23,7 @@ class Index extends Component
public function mount()
{
$this->waitingInLine = Waitlist::whereVerified(true)->count();
$this->users = User::count();
if (isDev()) {
$this->email = 'waitlist@example.com';
}

View File

@@ -27,7 +27,7 @@ class CleanupInstanceStuffsJob implements ShouldQueue, ShouldBeUnique
public function handle(): void
{
try {
$this->cleanup_waitlist();
// $this->cleanup_waitlist();
} catch (\Exception $e) {
send_internal_notification('CleanupInstanceStuffsJob failed with error: ' . $e->getMessage());
ray($e->getMessage());

View File

@@ -25,10 +25,8 @@ class SendConfirmationForWaitlistJob implements ShouldQueue
{
try {
$mail = new MailMessage();
$confirmation_url = base_url() . '/webhooks/waitlist/confirm?email=' . $this->email . '&confirmation_code=' . $this->uuid;
$cancel_url = base_url() . '/webhooks/waitlist/cancel?email=' . $this->email . '&confirmation_code=' . $this->uuid;
$mail->view('emails.waitlist-confirmation',
[
'confirmation_url' => $confirmation_url,
@@ -37,7 +35,7 @@ class SendConfirmationForWaitlistJob implements ShouldQueue
$mail->subject('You are on the waitlist!');
send_user_an_email($mail, $this->email);
} catch (\Throwable $th) {
send_internal_notification("SendConfirmationForWaitlistJob failed for {$mail} with error: " . $th->getMessage());
send_internal_notification("SendConfirmationForWaitlistJob failed for {$this->email} with error: " . $th->getMessage());
ray($th->getMessage());
throw $th;
}

View File

@@ -2,6 +2,7 @@
namespace App\Models;
use phpseclib3\Crypt\PublicKeyLoader;
class PrivateKey extends BaseModel
{
@@ -19,6 +20,15 @@ class PrivateKey extends BaseModel
return PrivateKey::whereTeamId(currentTeam()->id)->select($selectArray->all());
}
public function publicKey()
{
try {
return PublicKeyLoader::load($this->private_key)->getPublicKey()->toString('OpenSSH',['comment' => '']);
} catch (\Exception $e) {
return 'Error loading private key';
}
}
public function isEmpty()
{
if ($this->servers()->count() === 0 && $this->applications()->count() === 0 && $this->githubApps()->count() === 0 && $this->gitlabApps()->count() === 0) {

View File

@@ -19,7 +19,17 @@ class GeneralNotification extends Notification implements ShouldQueue
public function via(object $notifiable): array
{
return [TelegramChannel::class, DiscordChannel::class];
$channels = [];
$isDiscordEnabled = data_get($notifiable, 'discord_enabled');
$isTelegramEnabled = data_get($notifiable, 'telegram_enabled');
if ($isDiscordEnabled) {
$channels[] = DiscordChannel::class;
}
if ($isTelegramEnabled) {
$channels[] = TelegramChannel::class;
}
return $channels;
}
public function toDiscord(): string

View File

@@ -7,7 +7,7 @@ return [
// The release version of your application
// Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD'))
'release' => '4.0.0-beta.31',
'release' => '4.0.0-beta.33',
'server_name' => env('APP_ID', 'coolify'),
// When left empty or `null` the Laravel environment will be used
'environment' => config('app.env'),

View File

@@ -1,3 +1,3 @@
<?php
return '4.0.0-beta.31';
return '4.0.0-beta.33';

View File

@@ -8,13 +8,14 @@
<div class="w-96">
<form action="/user/confirm-password" method="POST" class="flex flex-col gap-2">
@csrf
<x-forms.input required type="password" name="password" label="{{ __('input.password') }}"
autofocus />
<x-forms.input required type="password" name="password" label="{{ __('input.password') }}" autofocus />
<x-forms.button type="submit">{{ __('auth.confirm_password') }}</x-forms.button>
</form>
@if ($errors->any())
<div class="text-center text-error">
<span>{{ __('auth.failed') }}</span>
<div class="text-xs text-center text-error">
@foreach ($errors->all() as $error)
<p>{{ $error }}</p>
@endforeach
</div>
@endif
@if (session('status'))

View File

@@ -28,7 +28,9 @@
@endif
@if ($errors->any())
<div class="text-xs text-center text-error">
<span>{{ __('auth.failed') }}</span>
@foreach ($errors->all() as $error)
<p>{{ $error }}</p>
@endforeach
</div>
@endif
@if (session('status'))

View File

@@ -43,7 +43,9 @@
@endif
@if ($errors->any())
<div class="text-xs text-center text-error">
<span>{{ __('auth.failed') }}</span>
@foreach ($errors->all() as $error)
<p>{{ $error }}</p>
@endforeach
</div>
@endif
@if (session('status'))

View File

@@ -38,7 +38,9 @@
</form>
@if ($errors->any())
<div class="text-xs text-center text-error">
<span>{{ __('auth.failed') }}</span>
@foreach ($errors->all() as $error)
<p>{{ $error }}</p>
@endforeach
</div>
@endif
</div>

View File

@@ -24,8 +24,10 @@
<x-forms.button type="submit">{{ __('auth.reset_password') }}</x-forms.button>
</form>
@if ($errors->any())
<div class="text-center text-error">
<span>{{ __('auth.failed') }}</span>
<div class="text-xs text-center text-error">
@foreach ($errors->all() as $error)
<p>{{ $error }}</p>
@endforeach
</div>
@endif
@if (session('status'))

View File

@@ -20,7 +20,7 @@
</template>
<template x-if="showRecovery">
<div>
<x-forms.input required type="text" name="recovery_code "
<x-forms.input required type="text" name="recovery_code"
label="{{ __('input.recovery_code') }}" />
<div class="pt-2 text-xs cursor-pointer hover:underline hover:text-white"
x-on:click="showRecovery = !showRecovery">Use
@@ -31,8 +31,10 @@
<x-forms.button type="submit">{{ __('auth.login') }}</x-forms.button>
</form>
@if ($errors->any())
<div class="text-center text-error">
<span>{{ __('auth.failed') }}</span>
<div class="text-xs text-center text-error">
@foreach ($errors->all() as $error)
<p>{{ $error }}</p>
@endforeach
</div>
@endif
@if (session('status'))

View File

@@ -114,10 +114,10 @@
label="Description" id="privateKeyDescription" />
<x-forms.textarea required placeholder="-----BEGIN OPENSSH PRIVATE KEY-----" label="Private Key"
id="privateKey" />
@if ($privateKeyType === 'create' && !isDev())
<span class="font-bold text-warning">Copy this to your server's ~/.ssh/authorized_keys
file.</span>
<x-forms.textarea rows="7" readonly label="Public Key" id="publicKey" />
@if ($privateKeyType === 'create')
<x-forms.textarea rows="7" readonly label="Public Key" id="publicKey" />
<span class="font-bold text-warning">ACTION REQUIRED: Copy the 'Public Key' to your server's ~/.ssh/authorized_keys
file.</span>
@endif
<x-forms.button type="submit">Save</x-forms.button>
</form>

View File

@@ -19,6 +19,10 @@
<x-forms.input id="private_key.name" label="Name" required />
<x-forms.input id="private_key.description" label="Description" />
<div>
<div class="flex items-end gap-2 py-2 ">
<div class="pl-1 ">Public Key</div>
</div>
<x-forms.input readonly id="public_key" />
<div class="flex items-end gap-2 py-2 ">
<div class="pl-1 ">Private Key <span class='text-helper'>*</span></div>
<div class="text-xs text-white underline cursor-pointer" x-cloak x-show="!showPrivateKey"
@@ -43,5 +47,6 @@
<x-forms.textarea rows="10" id="private_key.private_key" required />
</div>
</div>
</form>
</div>

View File

@@ -76,6 +76,10 @@
</div>
</div> --}}
</div>
<h2 class="py-4">Services</h2>
<div class="grid justify-start grid-cols-1 gap-2 text-left xl:grid-cols-3">
Ghost, Plausible, Wordpress, etc... Coming very very soon...
</div>
@endif
@if ($current_step === 'servers')
<ul class="pb-10 steps">
@@ -135,10 +139,11 @@
</div>
@endif
@if ($current_step === 'existing-postgresql')
<form wire:submit.prevent='addExistingPostgresql' class="flex items-end gap-2">
<x-forms.input placeholder="postgres://username:password@database:5432" label="Database URL" id="existingPostgresqlUrl" />
<x-forms.button type="submit">Add Database</x-forms.button>
</form>
<form wire:submit.prevent='addExistingPostgresql' class="flex items-end gap-2">
<x-forms.input placeholder="postgres://username:password@database:5432" label="Database URL"
id="existingPostgresqlUrl" />
<x-forms.button type="submit">Add Database</x-forms.button>
</form>
@endif
</div>
</div>

View File

@@ -23,7 +23,8 @@
<x-forms.input id="email" type="email" label="Email" placeholder="youareawesome@protonmail.com" />
<x-forms.button type="submit">Join Waitlist</x-forms.button>
</form>
Waiting in the line: <span class="font-bold text-warning">{{ $waitingInLine }}</span>
<div>People waiting in the line: <span class="font-bold text-warning">{{ $waitingInLine }}</div>
<div>Already using Coolify Cloud: <span class="font-bold text-warning">{{ $users }}</div>
<div class="pt-8">
This is a paid & hosted version of Coolify.<br> See the pricing <a href="https://coolify.io/pricing" class="text-warning">here</a>.
</div>

View File

@@ -181,14 +181,24 @@ Route::get('/waitlist/confirm', function () {
ray($email, $confirmation_code);
try {
$found = Waitlist::where('uuid', $confirmation_code)->where('email', $email)->first();
if ($found && !$found->verified && $found->created_at > now()->subMinutes(config('constants.waitlist.expiration'))) {
$found->verified = true;
$found->save();
send_internal_notification('Waitlist confirmed: ' . $email);
return 'Thank you for confirming your email address. We will notify you when you are next in line.';
if ($found) {
if (!$found->verified) {
if ($found->created_at > now()->subMinutes(config('constants.waitlist.expiration'))) {
$found->verified = true;
$found->save();
send_internal_notification('Waitlist confirmed: ' . $email);
return 'Thank you for confirming your email address. We will notify you when you are next in line.';
} else {
$found->delete();
send_internal_notification('Waitlist expired: ' . $email);
return 'Your confirmation code has expired. Please sign up again.';
}
}
}
return redirect()->route('dashboard');
} catch (error) {
} catch (Exception $e) {
send_internal_notification('Waitlist confirmation failed: ' . $e->getMessage());
ray($e->getMessage());
return redirect()->route('dashboard');
}
})->name('webhooks.waitlist.confirm');
@@ -203,7 +213,9 @@ Route::get('/waitlist/cancel', function () {
return 'Your email address has been removed from the waitlist.';
}
return redirect()->route('dashboard');
} catch (error) {
} catch (Exception $e) {
send_internal_notification('Waitlist cancellation failed: ' . $e->getMessage());
ray($e->getMessage());
return redirect()->route('dashboard');
}
})->name('webhooks.waitlist.cancel');
@@ -225,7 +237,7 @@ Route::post('/payments/stripe/events', function () {
]);
$type = data_get($event, 'type');
$data = data_get($event, 'data.object');
ray('Event: '. $type);
ray('Event: ' . $type);
switch ($type) {
case 'checkout.session.completed':
$clientReferenceId = data_get($data, 'client_reference_id');
@@ -263,13 +275,13 @@ Route::post('/payments/stripe/events', function () {
'stripe_invoice_paid' => true,
]);
break;
// case 'invoice.payment_failed':
// $customerId = data_get($data, 'customer');
// $subscription = Subscription::where('stripe_customer_id', $customerId)->first();
// if ($subscription) {
// SubscriptionInvoiceFailedJob::dispatch($subscription->team);
// }
// break;
// case 'invoice.payment_failed':
// $customerId = data_get($data, 'customer');
// $subscription = Subscription::where('stripe_customer_id', $customerId)->first();
// if ($subscription) {
// SubscriptionInvoiceFailedJob::dispatch($subscription->team);
// }
// break;
case 'customer.subscription.updated':
$customerId = data_get($data, 'customer');
$subscription = Subscription::where('stripe_customer_id', $customerId)->firstOrFail();
@@ -287,9 +299,9 @@ Route::post('/payments/stripe/events', function () {
]);
ray($feedback, $comment, $alreadyCancelAtPeriodEnd, $cancelAtPeriodEnd);
if ($feedback) {
$reason = "Cancellation feedback for {$subscription->team->id}: '" . $feedback ."'";
$reason = "Cancellation feedback for {$subscription->team->id}: '" . $feedback . "'";
if ($comment) {
$reason .= ' with comment: \'' . $comment ."'";
$reason .= ' with comment: \'' . $comment . "'";
}
send_internal_notification($reason);
}
@@ -307,7 +319,7 @@ Route::post('/payments/stripe/events', function () {
$subscription = Subscription::where('stripe_customer_id', $customerId)->firstOrFail();
$subscription->update([
'stripe_subscription_id' => null,
'stripe_plan_id'=> null,
'stripe_plan_id' => null,
'stripe_cancel_at_period_end' => false,
'stripe_invoice_paid' => false,
]);

View File

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