a lot hehe

This commit is contained in:
Andras Bacsai 2023-06-01 12:15:33 +02:00
parent c8f70a4e3b
commit 0aa816b4f2
42 changed files with 570 additions and 249 deletions

View File

@ -8,7 +8,9 @@ GROUPID=
PROJECT_PATH_ON_HOST=/Users/your-username-here/code/coollabsio/coolify
SERVEO_URL=<for receiving webhooks locally https://serveo.net/>
MUX_ENABLED=false
# If you are using the included Buggregator
RAY_HOST=ray@host.docker.internal
RAY_PORT=8001
############################################################################################################
APP_NAME=Coolify
@ -19,6 +21,10 @@ APP_DEBUG=true
APP_URL=http://localhost
APP_PORT=8000
MAIL_MAILER=smtp
MAIL_HOST=coolify-mail
MAIL_PORT=1025
SESSION_DRIVER=database
DUSK_DRIVER_URL=http://selenium:4444

View File

@ -1,4 +1,4 @@
# Secrets related to pushing to GH, Sync files to BunnyCDN etc.
# Secrets related to pushing to GH, Sync files to BunnyCDN etc. Only for maintainers.
# Not related to Coolify, but to how we publish new versions.
GITHUB_TOKEN=

View File

@ -4,7 +4,7 @@
use App\Models\Server;
use App\Models\Team;
use App\Notifications\DemoNotification;
use App\Notifications\TestNotification;
use Illuminate\Support\Facades\Notification;
use Livewire\Component;
@ -13,31 +13,40 @@ class DiscordSettings extends Component
public Team|Server $model;
protected $rules = [
'model.extra_attributes.discord_webhook' => 'nullable|url',
'model.extra_attributes.discord_active' => 'nullable|boolean',
'model.smtp_attributes.discord_active' => 'nullable|boolean',
'model.smtp_attributes.discord_webhook' => 'required|url',
];
protected $validationAttributes = [
'model.extra_attributes.discord_webhook' => 'Discord Webhook',
'model.smtp_attributes.discord_webhook' => 'Discord Webhook',
];
public function mount($model)
{
//
}
public function instantSave()
{
try {
$this->submit();
} catch (\Exception $e) {
$this->model->smtp_attributes->discord_active = false;
$this->addError('model.smtp_attributes.discord_webhook', $e->getMessage());
}
}
private function saveModel()
{
$this->model->save();
if (is_a($this->model, Team::class)) {
session(['currentTeam' => $this->model]);
}
}
public function submit()
{
$this->resetErrorBag();
$this->validate();
$this->model->save();
if ( is_a($this->model, Team::class)) {
session(['currentTeam' => $this->model]);
}
$this->saveModel();
}
public function sendTestNotification()
{
Notification::send($this->model, new DemoNotification);
}
public function render()
{
return view('livewire.notifications.discord-settings');
Notification::send($this->model, new TestNotification);
}
}

View File

@ -4,7 +4,7 @@
use App\Models\Server;
use App\Models\Team;
use App\Notifications\DemoNotification;
use App\Notifications\TestNotification;
use Illuminate\Support\Facades\Notification;
use Livewire\Component;
@ -13,27 +13,27 @@ class EmailSettings extends Component
public Team|Server $model;
protected $rules = [
'model.extra_attributes.smtp_active' => 'nullable|boolean',
'model.extra_attributes.from_address' => 'nullable',
'model.extra_attributes.from_name' => 'nullable',
'model.extra_attributes.recipients' => 'nullable',
'model.extra_attributes.smtp_host' => 'nullable',
'model.extra_attributes.smtp_port' => 'nullable',
'model.extra_attributes.smtp_encryption' => 'nullable',
'model.extra_attributes.smtp_username' => 'nullable',
'model.extra_attributes.smtp_password' => 'nullable',
'model.extra_attributes.smtp_timeout' => 'nullable',
'model.smtp_attributes.smtp_active' => 'nullable|boolean',
'model.smtp_attributes.from_address' => 'required',
'model.smtp_attributes.from_name' => 'required',
'model.smtp_attributes.recipients' => 'required',
'model.smtp_attributes.smtp_host' => 'required',
'model.smtp_attributes.smtp_port' => 'required',
'model.smtp_attributes.smtp_encryption' => 'nullable',
'model.smtp_attributes.smtp_username' => 'nullable',
'model.smtp_attributes.smtp_password' => 'nullable',
'model.smtp_attributes.smtp_timeout' => 'nullable',
'model.smtp_attributes.test_address' => 'nullable',
];
protected $validationAttributes = [
'model.extra_attributes.from_address' => 'From Address',
'model.extra_attributes.from_name' => 'From Name',
'model.extra_attributes.recipients' => 'Recipients',
'model.extra_attributes.smtp_host' => 'Host',
'model.extra_attributes.smtp_port' => 'Port',
'model.extra_attributes.smtp_encryption' => 'Encryption',
'model.extra_attributes.smtp_username' => 'Username',
'model.extra_attributes.smtp_password' => 'Password',
'model.extra_attributes.smtp_timeout' => 'Timeout',
'model.smtp_attributes.from_address' => 'From Address',
'model.smtp_attributes.from_name' => 'From Name',
'model.smtp_attributes.recipients' => 'Recipients',
'model.smtp_attributes.smtp_host' => 'Host',
'model.smtp_attributes.smtp_port' => 'Port',
'model.smtp_attributes.smtp_encryption' => 'Encryption',
'model.smtp_attributes.smtp_username' => 'Username',
'model.smtp_attributes.smtp_password' => 'Password',
];
public function mount($model)
{
@ -43,17 +43,17 @@ public function submit()
{
$this->resetErrorBag();
$this->validate();
$this->saveModel();
}
private function saveModel()
{
$this->model->save();
if ( is_a($this->model, Team::class)) {
if (is_a($this->model, Team::class)) {
session(['currentTeam' => $this->model]);
}
}
public function sendTestNotification()
public function instantSave()
{
Notification::send($this->model, new DemoNotification);
}
public function render()
{
return view('livewire.notifications.email-settings');
$this->saveModel();
}
}

View File

@ -0,0 +1,19 @@
<?php
namespace App\Http\Livewire\Notifications;
use App\Models\Server;
use App\Models\Team;
use App\Notifications\TestNotification;
use Livewire\Component;
use Notification;
class Test extends Component
{
public Team|Server $model;
public function sendTestNotification()
{
Notification::send($this->model, new TestNotification);
$this->emit('saved', 'Test notification sent.');
}
}

View File

@ -0,0 +1,41 @@
<?php
namespace App\Http\Livewire\Settings;
use App\Models\InstanceSettings;
use Livewire\Component;
class Email extends Component
{
public InstanceSettings $model;
protected $rules = [
'model.extra_attributes.from_address' => 'nullable',
'model.extra_attributes.from_name' => 'nullable',
'model.extra_attributes.recipients' => 'nullable',
'model.extra_attributes.smtp_host' => 'nullable',
'model.extra_attributes.smtp_port' => 'nullable',
'model.extra_attributes.smtp_encryption' => 'nullable',
'model.extra_attributes.smtp_username' => 'nullable',
'model.extra_attributes.smtp_password' => 'nullable',
'model.extra_attributes.smtp_timeout' => 'nullable',
];
protected $validationAttributes = [
'model.extra_attributes.from_address' => 'From Address',
'model.extra_attributes.from_name' => 'From Name',
'model.extra_attributes.recipients' => 'Recipients',
'model.extra_attributes.smtp_host' => 'Host',
'model.extra_attributes.smtp_port' => 'Port',
'model.extra_attributes.smtp_encryption' => 'Encryption',
'model.extra_attributes.smtp_username' => 'Username',
'model.extra_attributes.smtp_password' => 'Password',
];
public function mount($model)
{
//
}
public function render()
{
return view('livewire.settings.email');
}
}

View File

@ -22,16 +22,23 @@ protected static function booted()
'port',
'team_id',
'private_key_id',
'extra_attributes',
'smtp_attributes',
];
public $casts = [
'extra_attributes' => SchemalessAttributes::class,
'smtp_attributes' => SchemalessAttributes::class,
];
public function scopeWithExtraAttributes(): Builder
{
return $this->extra_attributes->modelScope();
}
public function scopeWithSmtpAttributes(): Builder
{
return $this->smtp_attributes->modelScope();
}
public function standaloneDockers()
{
@ -43,8 +50,6 @@ public function swarmDockers()
return $this->hasMany(SwarmDocker::class);
}
public function privateKey()
{
return $this->belongsTo(PrivateKey::class);

View File

@ -2,42 +2,41 @@
namespace App\Models;
use App\Notifications\Channels\SendsCoolifyEmail;
use App\Notifications\Channels\SendsEmail;
use App\Notifications\Channels\SendsDiscord;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Notifications\Notifiable;
use Spatie\SchemalessAttributes\Casts\SchemalessAttributes;
class Team extends BaseModel implements SendsDiscord, SendsCoolifyEmail
class Team extends BaseModel implements SendsDiscord, SendsEmail
{
use Notifiable;
protected $casts = [
'extra_attributes' => SchemalessAttributes::class,
'smtp_attributes' => SchemalessAttributes::class,
'personal_team' => 'boolean',
];
protected $fillable = [
'id',
'name',
'personal_team',
'extra_attributes',
'smtp_attributes',
];
public function routeNotificationForDiscord()
{
return $this->extra_attributes->get('discord_webhook');
return $this->smtp_attributes->get('discord_webhook');
}
public function routeNotificationForCoolifyEmail()
public function routeNotificationForEmail(string $attribute = 'recipients')
{
$recipients = $this->extra_attributes->get('recipients', '');
$recipients = $this->smtp_attributes->get($attribute, '');
return explode(PHP_EOL, $recipients);
}
public function scopeWithExtraAttributes(): Builder
{
return $this->extra_attributes->modelScope();
return $this->smtp_attributes->modelScope();
}
public function projects()

View File

@ -8,10 +8,11 @@
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;
use Visus\Cuid2\Cuid2;
use Laravel\Fortify\TwoFactorAuthenticatable;
class User extends Authenticatable
{
use HasApiTokens, HasFactory, Notifiable;
use HasApiTokens, HasFactory, Notifiable, TwoFactorAuthenticatable;
protected $fillable = [
'id',
'name',

View File

@ -1,44 +0,0 @@
<?php
namespace App\Notifications\Channels;
use Illuminate\Mail\Message;
use Illuminate\Notifications\Notification;
use Illuminate\Support\Facades\Mail;
class CoolifyEmailChannel
{
/**
* Send the given notification.
*/
public function send(SendsCoolifyEmail $notifiable, Notification $notification): void
{
$this->bootConfigs($notifiable);
$bcc = $notifiable->routeNotificationForCoolifyEmail();
$mailMessage = $notification->toMail($notifiable);
Mail::send([], [], fn(Message $message) => $message
->from(
$notifiable->extra_attributes?->get('from_address'),
$notifiable->extra_attributes?->get('from_name')
)
->bcc($bcc)
->subject($mailMessage->subject)
->html((string)$mailMessage->render())
);
}
private function bootConfigs($notifiable): void
{
config()->set('mail.mailers.smtp', [
"transport" => "smtp",
"host" => $notifiable->extra_attributes?->get('smtp_host'),
"port" => $notifiable->extra_attributes?->get('smtp_port'),
"encryption" => $notifiable->extra_attributes?->get('smtp_encryption'),
"username" => $notifiable->extra_attributes?->get('smtp_username'),
"password" => $notifiable->extra_attributes?->get('smtp_password'),
"timeout" => $notifiable->extra_attributes?->get('smtp_timeout'),
"local_domain" => null,
]);
}
}

View File

@ -13,9 +13,7 @@ class DiscordChannel
public function send(SendsDiscord $notifiable, Notification $notification): void
{
$message = $notification->toDiscord($notifiable);
$webhookUrl = $notifiable->routeNotificationForDiscord();
dispatch(new SendMessageToDiscordJob($message, $webhookUrl));
}
}

View File

@ -0,0 +1,56 @@
<?php
namespace App\Notifications\Channels;
use Illuminate\Mail\Message;
use Illuminate\Notifications\Notification;
use Illuminate\Support\Facades\Mail;
class EmailChannel
{
/**
* Send the given notification.
*/
public function send(SendsEmail $notifiable, Notification $notification): void
{
$this->bootConfigs($notifiable);
if ($notification instanceof \App\Notifications\TestNotification) {
$bcc = $notifiable->routeNotificationForEmail('test_address');
if (count($bcc) === 1) {
$bcc = $notifiable->routeNotificationForEmail();
}
} else {
$bcc = $notifiable->routeNotificationForEmail();
}
$mailMessage = $notification->toMail($notifiable);
Mail::send(
[],
[],
fn (Message $message) => $message
->from(
$notifiable->smtp_attributes?->get('from_address'),
$notifiable->smtp_attributes?->get('from_name')
)
->cc($bcc)
->bcc($bcc)
->subject($mailMessage->subject)
->html((string)$mailMessage->render())
);
}
private function bootConfigs($notifiable): void
{
config()->set('mail.default', 'smtp');
config()->set('mail.mailers.smtp', [
"transport" => "smtp",
"host" => $notifiable->smtp_attributes?->get('smtp_host'),
"port" => $notifiable->smtp_attributes?->get('smtp_port'),
"encryption" => $notifiable->smtp_attributes?->get('smtp_encryption'),
"username" => $notifiable->smtp_attributes?->get('smtp_username'),
"password" => $notifiable->smtp_attributes?->get('smtp_password'),
"timeout" => $notifiable->smtp_attributes?->get('smtp_timeout'),
"local_domain" => null,
]);
}
}

View File

@ -1,8 +0,0 @@
<?php
namespace App\Notifications\Channels;
interface SendsCoolifyEmail
{
public function routeNotificationForCoolifyEmail();
}

View File

@ -0,0 +1,8 @@
<?php
namespace App\Notifications\Channels;
interface SendsEmail
{
public function routeNotificationForEmail();
}

View File

@ -2,14 +2,14 @@
namespace App\Notifications;
use App\Notifications\Channels\CoolifyEmailChannel;
use App\Notifications\Channels\EmailChannel;
use App\Notifications\Channels\DiscordChannel;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
class DemoNotification extends Notification implements ShouldQueue
class TestNotification extends Notification implements ShouldQueue
{
use Queueable;
@ -29,8 +29,8 @@ public function __construct()
public function via(object $notifiable): array
{
$channels = [];
$notifiable->extra_attributes?->get('smtp_active') && $channels[] = CoolifyEmailChannel::class;
$notifiable->extra_attributes?->get('discord_active') && $channels[] = DiscordChannel::class;
$notifiable->smtp_attributes?->get('smtp_active') && $channels[] = EmailChannel::class;
$notifiable->smtp_attributes?->get('discord_active') && $channels[] = DiscordChannel::class;
return $channels;
}
@ -40,15 +40,14 @@ public function via(object $notifiable): array
public function toMail(object $notifiable): MailMessage
{
return (new MailMessage)
->subject('Coolify demo notification')
->line('Welcome to Coolify!')
->action('Go to dashboard', url('/'))
->line('We need your attention for disk usage.');
->subject('Coolify Test Notification')
->line('Congratulations!')
->line('You have successfully received a test Email notification from Coolify. 🥳');
}
public function toDiscord(object $notifiable): string
{
return 'Welcome to Coolify! We need your attention for disk usage. [Go to dashboard]('.url('/').')';
return 'You have successfully received a test Discord notification from Coolify. 🥳 [Go to your dashboard](' . url('/') . ')';
}
/**

View File

@ -13,6 +13,7 @@
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\ServiceProvider;
use Laravel\Fortify\Contracts\RegisterResponse;
use Laravel\Fortify\Fortify;
class FortifyServiceProvider extends ServiceProvider
@ -22,7 +23,17 @@ class FortifyServiceProvider extends ServiceProvider
*/
public function register(): void
{
//
$this->app->instance(RegisterResponse::class, new class implements RegisterResponse
{
public function toResponse($request)
{
// First user (root) will be redirected to /settings instead of / on registration.
if ($request->user()->currentTeam->id === 0) {
return redirect('/settings');
}
return redirect('/');
}
});
}
/**
@ -30,6 +41,7 @@ public function register(): void
*/
public function boot(): void
{
Fortify::createUsersUsing(CreateNewUser::class);
Fortify::registerView(function () {
$settings = InstanceSettings::get();
if (!$settings->is_registration_enabled) {
@ -58,10 +70,21 @@ public function boot(): void
Fortify::requestPasswordResetLinkView(function () {
return view('auth.forgot-password');
});
Fortify::createUsersUsing(CreateNewUser::class);
Fortify::resetPasswordView(function ($request) {
return view('auth.reset-password', ['request' => $request]);
});
Fortify::resetUserPasswordsUsing(ResetUserPassword::class);
Fortify::updateUserProfileInformationUsing(UpdateUserProfileInformation::class);
Fortify::updateUserPasswordsUsing(UpdateUserPassword::class);
Fortify::resetUserPasswordsUsing(ResetUserPassword::class);
Fortify::confirmPasswordView(function () {
return view('auth.confirm-password');
});
Fortify::twoFactorChallengeView(function () {
return view('auth.two-factor-challenge');
});
RateLimiter::for('login', function (Request $request) {
$email = (string) $request->email;

View File

@ -13,7 +13,7 @@
|
*/
'default' => env('MAIL_MAILER', 'smtp'),
'default' => env('MAIL_MAILER', null),
/*
|--------------------------------------------------------------------------

View File

@ -16,7 +16,7 @@ public function up(): void
$table->string('uuid')->unique();
$table->string('name');
$table->boolean('personal_team')->default(false);
$table->schemalessAttributes('extra_attributes');
$table->schemalessAttributes('smtp_attributes');
$table->timestamps();
});
}

View File

@ -22,6 +22,7 @@ public function up(): void
$table->foreignId('team_id');
$table->foreignId('private_key_id');
$table->schemalessAttributes('extra_attributes');
$table->schemalessAttributes('smtp_attributes');
$table->timestamps();
});
}

View File

@ -64,13 +64,17 @@ services:
- "./_data/coolify/proxy/testing-host-2:/data/coolify/proxy"
mailpit:
image: 'axllent/mailpit:latest'
container_name: coolify-mail
ports:
- '${FORWARD_MAILPIT_PORT:-1025}:1025'
- '${FORWARD_MAILPIT_DASHBOARD_PORT:-8025}:8025'
networks:
- coolify
buggregator:
image: ghcr.io/buggregator/server:latest
container_name: coolify-debug
ports:
- 23517:8000
- 8001:8000
networks:
- coolify

View File

@ -1,11 +1,14 @@
{
"auth.login": "Login",
"auth.already-registered": "Already registered?",
"auth.register-now": "Register now!",
"auth.already_registered": "Already registered?",
"auth.confirm_password": "Confirm password",
"auth.forgot_password": "Forgot password",
"auth.forgot_password_send_email": "Send password reset link via email",
"auth.register_now": "Register now!",
"auth.logout": "Logout",
"auth.register": "Register",
"auth.registration_disabled": "Registration is disabled. Please contact the administrator.",
"auth.reset_password": "Reset Password",
"auth.reset_password": "Reset password",
"auth.failed": "These credentials do not match our records.",
"auth.failed.password": "The provided password is incorrect.",
"auth.failed.email": "We can't find a user with that e-mail address.",
@ -13,6 +16,8 @@
"input.name": "Name",
"input.email": "Email",
"input.password": "Password",
"input.password.again": "Password Again",
"input.password.again": "Password again",
"input.code": "One-time code",
"input.recovery_code": "Recovery code",
"button.save": "Save"
}

View File

@ -55,11 +55,14 @@ h1 {
@apply text-3xl font-bold pb-4 text-white;
}
h2 {
@apply text-2xl font-bold pb-4 text-white;
@apply text-2xl font-bold py-4 text-white;
}
h3 {
@apply text-xl font-bold py-4 text-white;
}
h4 {
@apply text-base font-bold pb-4 text-white;
}
a {
@apply text-neutral-400 hover:text-white text-sm link link-hover hover:bg-transparent;
}

View File

@ -0,0 +1,28 @@
<x-layout-simple>
<div class="flex items-center justify-center h-screen">
<div>
<div class="flex flex-col items-center pb-8">
<div class="text-5xl font-bold tracking-tight text-center text-white">Coolify</div>
<x-version />
</div>
<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.button type="submit">{{ __('auth.confirm_password') }}</x-forms.button>
</form>
@if ($errors->any())
<div class="text-center text-error">
<span>{{ __('auth.failed') }}</span>
</div>
@endif
@if (session('status'))
<div class="mb-4 text-sm font-medium text-green-600">
{{ session('status') }}
</div>
@endif
</div>
</div>
</div>
</x-layout-simple>

View File

@ -1,50 +1,32 @@
<x-layout-simple>
Forgot Password
<form>
@csrf
<x-forms.input required value="test@example.com" type="email" name="email" label="{{ __('input.email') }}"
autofocus />
</form>
@if (session('status'))
<div class="mb-4 text-sm font-medium text-green-600">
{{ session('status') }}
</div>
@endif
{{-- <div class="flex items-center justify-center h-screen">
<div class="flex items-center justify-center h-screen">
<div>
<div class="pb-8 text-5xl font-bold tracking-tight text-center text-white">Coolify</div>
<div class="flex flex-col items-center pb-8">
<div class="text-5xl font-bold tracking-tight text-center text-white">Coolify</div>
<x-version />
</div>
<div class="flex items-center gap-2">
<h1 class="pb-0">{{ __('auth.login') }}</h1>
@if ($is_registration_enabled)
<a href="/register" class="flex justify-center pt-2 hover:no-underline">
<button
class="normal-case rounded-none btn btn-sm btn-primary bg-coollabs-gradient">{{ __('auth.register-now') }}</button>
</a>
@endif
<h1 class="pb-0">{{ __('auth.forgot_password') }}</h1>
</div>
<div class="w-96">
<form action="/login" method="POST" class="flex flex-col gap-2">
<form action="/forgot-password" method="POST" class="flex flex-col gap-2">
@csrf
@env('local')
<x-forms.input required value="test@example.com" type="email" name="email"
label="{{ __('input.email') }}" autofocus />
<x-forms.input required value="password" type="password" name="password"
label="{{ __('input.password') }}" />
@else
<x-forms.input required type="email" name="email" label="{{ __('input.email') }}" autofocus />
<x-forms.input required type="password" name="password" label="{{ __('input.password') }}" />
@endenv
@if ($errors->any())
<div class="text-center text-error">
<span>{{ __('auth.failed') }}</span>
</div>
@endif
<x-forms.button type="submit">{{ __('auth.login') }}</x-forms.button>
@if (!$is_registration_enabled)
<div class="text-sm text-center">{{ __('auth.registration_disabled') }}</div>
@endif
<x-forms.button type="submit">{{ __('auth.forgot_password_send_email') }}</x-forms.button>
</form>
@if ($errors->any())
<div class="text-center text-error">
<span>{{ __('auth.failed') }}</span>
</div>
@endif
@if (session('status'))
<div class="mb-4 text-sm font-medium text-green-600">
{{ session('status') }}
</div>
@endif
</div>
</div>
</div> --}}
</div>
</x-layout-simple>

View File

@ -1,13 +1,16 @@
<x-layout-simple>
<div class="flex items-center justify-center h-screen">
<div>
<div class="pb-8 text-5xl font-bold tracking-tight text-center text-white">Coolify</div>
<div class="flex flex-col items-center pb-8">
<div class="text-5xl font-bold tracking-tight text-center text-white">Coolify</div>
<x-version />
</div>
<div class="flex items-center gap-2">
<h1 class="pb-0">{{ __('auth.login') }}</h1>
@if ($is_registration_enabled)
<a href="/register" class="flex justify-center pt-2 hover:no-underline">
<button
class="normal-case rounded-none btn btn-sm btn-primary bg-coollabs-gradient">{{ __('auth.register-now') }}</button>
class="normal-case rounded-none btn btn-sm btn-primary bg-coollabs-gradient">{{ __('auth.register_now') }}</button>
</a>
@endif
</div>
@ -17,6 +20,17 @@ class="normal-case rounded-none btn btn-sm btn-primary bg-coollabs-gradient">{{
@env('local')
<x-forms.input required value="test@example.com" type="email" name="email"
label="{{ __('input.email') }}" autofocus />
{{-- @if (config('mail.default'))
{{ dd('mailer configured') }}
@else
{{ dd('mailer not configured') }}
@endif --}}
@if (!config('mail.default'))
<a href="/forgot-password" class="text-xs">
{{ __('auth.forgot_password') }}?
</a>
@endif
<x-forms.input required value="password" type="password" name="password"
label="{{ __('input.password') }}" />
@else
@ -28,6 +42,11 @@ class="normal-case rounded-none btn btn-sm btn-primary bg-coollabs-gradient">{{
<span>{{ __('auth.failed') }}</span>
</div>
@endif
@if (session('status'))
<div class="mb-4 text-sm font-medium text-green-600">
{{ session('status') }}
</div>
@endif
<x-forms.button type="submit">{{ __('auth.login') }}</x-forms.button>
@if (!$is_registration_enabled)
<div class="text-sm text-center">{{ __('auth.registration_disabled') }}</div>

View File

@ -1,12 +1,15 @@
<x-layout-simple>
<div class="flex items-center justify-center min-h-screen">
<div>
<div class="pb-8 text-5xl font-bold tracking-tight text-center text-white">Coolify</div>
<div class="flex flex-col items-center pb-8">
<div class="text-5xl font-bold tracking-tight text-center text-white">Coolify</div>
<x-version />
</div>
<div class="flex items-center gap-2">
<h1 class="pb-0">{{ __('auth.register') }}</h1>
<a href="/login" class="flex justify-center pt-2 hover:no-underline">
<button
class="normal-case rounded-none btn btn-sm btn-primary bg-coollabs-gradient">{{ __('auth.already-registered') }}</button>
class="normal-case rounded-none btn btn-sm btn-primary bg-coollabs-gradient">{{ __('auth.already_registered') }}</button>
</a>
</div>
<form action="/register" method="POST" class="flex flex-col gap-2">

View File

@ -0,0 +1,38 @@
<x-layout-simple>
<div class="flex items-center justify-center h-screen mx-auto">
<div>
<div class="flex flex-col items-center pb-8">
<div class="text-5xl font-bold tracking-tight text-center text-white">Coolify</div>
<x-version />
</div>
<div class="flex items-center gap-2">
<h1 class="pb-0">{{ __('auth.reset_password') }}</h1>
</div>
<div>
<form action="/reset-password" method="POST" class="flex flex-col gap-2">
@csrf
<input hidden id="token" name="token" value="{{ request()->route('token') }}">
<input hidden value="{{ request()->query('email') }}" type="email" name="email"
label="{{ __('input.email') }}" />
<div class="flex gap-2">
<x-forms.input required type="password" id="password" name="password"
label="{{ __('input.password') }}" autofocus />
<x-forms.input required type="password" id="password_confirmation" name="password_confirmation"
label="{{ __('input.password.again') }}" />
</div>
<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>
@endif
@if (session('status'))
<div class="mb-4 text-sm font-medium text-green-600">
{{ session('status') }}
</div>
@endif
</div>
</div>
</div>
</x-layout-simple>

View File

@ -0,0 +1,46 @@
<x-layout-simple>
<div class="flex items-center justify-center h-screen">
<div>
<div class="flex flex-col items-center pb-8">
<div class="text-5xl font-bold tracking-tight text-center text-white">Coolify</div>
<x-version />
</div>
<div class="w-96" x-data="{ showRecovery: false }">
<form action="/two-factor-challenge" method="POST" class="flex flex-col gap-2">
@csrf
<template x-if="!showRecovery">
<div>
<x-forms.input required type="number" name="code" label="{{ __('input.code') }}"
autofocus />
<div class="pt-2 text-xs cursor-pointer hover:underline hover:text-white"
x-on:click="showRecovery = !showRecovery">Use
Recovery Code
</div>
</div>
</template>
<template x-if="showRecovery">
<div>
<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
One-Time Code
</div>
</div>
</template>
<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>
@endif
@if (session('status'))
<div class="mb-4 text-sm font-medium text-green-600">
{{ session('status') }}
</div>
@endif
</div>
</div>
</div>
</x-layout-simple>

View File

@ -25,8 +25,7 @@
<main>
{{ $slot }}
</main>
<a
class="fixed text-xs cursor-pointer left-2 bottom-1 opacity-20 hover:opacity-100 hover:text-white">v{{ config('version') }}</a>
<x-version class="fixed left-2 bottom-1" />
</body>
</html>

View File

@ -35,8 +35,7 @@
<main>
{{ $slot }}
</main>
<a
class="fixed text-xs cursor-pointer right-2 bottom-1 opacity-60 hover:opacity-100 hover:text-white">v{{ config('version') }}</a>
<x-version class="fixed left-2 bottom-1" />
@auth
<script>
window.addEventListener("keydown", function(event) {

View File

@ -0,0 +1,2 @@
<a
{{ $attributes->merge(['class' => 'text-xs cursor-pointer opacity-20 hover:opacity-100 hover:text-white']) }}>v{{ config('version') }}</a>

View File

@ -1,23 +1,19 @@
<div class="">
<div class="text-xl">Discord</div>
<div class="mt-2"></div>
<div>
<form wire:submit.prevent='submit' class="flex flex-col">
<div class="flex flex-col gap-2 xl:flex-row w-96">
<x-inputs.input type="checkbox" id="model.extra_attributes.discord_active" label="Active?" />
<div class="flex items-center gap-2">
<h3>Discord</h3>
<x-forms.button class="w-16 mt-4" type="submit">
Save
</x-forms.button>
</div>
<div class="flex flex-col gap-2 xl:flex-row w-96">
<x-inputs.input id="model.extra_attributes.discord_webhook" label="Discord Webhook" />
<x-forms.checkbox instantSave id="model.smtp_attributes.discord_active" label="Notification Enabled" />
</div>
<div class="flex flex-col gap-2 xl:flex-row w-96">
<x-forms.input required id="model.smtp_attributes.discord_webhook" label="Webhook" />
</div>
<div>
<x-inputs.button class="w-16 mt-4" type="submit">
Submit
</x-inputs.button>
<x-inputs.button
class="mt-4 btn btn-xs no-animation normal-case text-white btn-primary"
wire:click="sendTestNotification"
>
Send test message
</x-inputs.button>
</div>
</form>
</div>

View File

@ -1,45 +1,35 @@
<div class="mt-10">
<div class="text-xl">E-mail - SMTP</div>
<div class="mt-2"></div>
<form wire:submit.prevent='submit' class="flex flex-col">
<div>
<form wire:submit.prevent='submit' class="flex flex-col mt-2">
<div class="flex items-center gap-2">
<h3>E-mail (SMTP)</h3>
<x-forms.button class="w-16 mt-4" type="submit">
Save
</x-forms.button>
</div>
<div class="flex flex-col w-96">
<x-inputs.input type="checkbox" id="model.extra_attributes.smtp_active" label="Active?" />
<x-forms.checkbox instantSave id="model.smtp_attributes.smtp_active" label="Notification Enabled" />
</div>
<div class="flex flex-col gap-2 xl:flex-row">
<div class="flex flex-col w-96">
<x-inputs.textarea
id="model.extra_attributes.recipients"
helper="E-mails, one per line"
<x-forms.textarea required id="model.smtp_attributes.recipients" helper="E-mails, one per line"
label="Recipients" />
</div>
</div>
<div class="flex flex-col gap-2 xl:flex-row">
<div class="flex flex-col w-96">
<x-inputs.input id="model.extra_attributes.smtp_host" label="Host" />
<x-inputs.input id="model.extra_attributes.smtp_port" label="Port" />
<x-inputs.input id="model.extra_attributes.smtp_encryption" label="Encryption" />
<x-forms.input required id="model.smtp_attributes.smtp_host" label="Host" />
<x-forms.input required id="model.smtp_attributes.smtp_port" label="Port" />
<x-forms.input id="model.smtp_attributes.smtp_encryption" label="Encryption" />
</div>
<div class="flex flex-col w-96">
<x-inputs.input id="model.extra_attributes.smtp_username" label="Username" />
<x-inputs.input id="model.extra_attributes.smtp_password" label="Password" />
<x-inputs.input id="model.extra_attributes.smtp_timeout" label="Timeout" />
<x-forms.input id="model.smtp_attributes.smtp_username" label="Username" />
<x-forms.input id="model.smtp_attributes.smtp_password" label="Password" />
<x-forms.input id="model.smtp_attributes.smtp_timeout" label="Timeout" />
</div>
<div class="flex flex-col w-96">
<x-inputs.input id="model.extra_attributes.from_address" label="From Address" />
<x-inputs.input id="model.extra_attributes.from_name" label="From Name" />
<x-inputs.input id="model.extra_attributes.test_address" label="Send test e-mails to" />
<x-forms.input required id="model.smtp_attributes.from_address" label="From Address" />
<x-forms.input required id="model.smtp_attributes.from_name" label="From Name" />
</div>
</div>
<div class="flex">
<x-inputs.button class="w-16 mt-4" type="submit">
Submit
</x-inputs.button>
<x-inputs.button
class="mt-4 btn btn-xs no-animation normal-case text-white btn-primary"
wire:click="sendTestNotification"
>
Send test message
</x-inputs.button>
</div>
</form>
</div>

View File

@ -0,0 +1,4 @@
<x-forms.button isHighlighted class="mt-4 text-white normal-case btn btn-xs no-animation btn-primary"
wire:click="sendTestNotification">
Send Test Notifications
</x-forms.button>

View File

@ -1,10 +1,12 @@
<div>
<form wire:submit.prevent='submit'>
<form wire:submit.prevent='submit' class="flex flex-col">
<div class="flex items-center gap-2">
<h3>Profile</h3>
<h3>General</h3>
<x-forms.button type="submit" label="Save">Save</x-forms.button>
</div>
<x-forms.input id="name" label="Name" required />
<x-forms.input id="email" label="Email" readonly />
<div class="flex gap-2">
<x-forms.input id="name" label="Name" required />
<x-forms.input id="email" label="Email" readonly />
</div>
</form>
</div>

View File

@ -0,0 +1,18 @@
<form>
<div class="flex flex-col gap-2">
<div class="flex gap-2">
<x-forms.input id="model.extra_attributes.smtp_host" label="Host" />
<x-forms.input id="model.extra_attributes.smtp_port" label="Port" />
<x-forms.input id="model.extra_attributes.smtp_encryption" label="Encryption" />
</div>
<div class="flex gap-2">
<x-forms.input id="model.extra_attributes.smtp_username" label="Username" />
<x-forms.input id="model.extra_attributes.smtp_password" label="Password" />
<x-forms.input id="model.extra_attributes.smtp_timeout" label="Timeout" />
</div>
<div class="flex gap-2">
<x-forms.input id="model.extra_attributes.from_address" label="From Address" />
<x-forms.input id="model.extra_attributes.from_name" label="From Name" />
</div>
</div>
</form>

View File

@ -6,8 +6,7 @@
Save
</x-forms.button>
</div>
<div class="pb-4 text-sm">Instance wide settings for Coolify.
</div>
<div class="pb-4 text-sm">Instance wide settings for Coolify.</div>
<div class="flex flex-col gap-2">
<div class="flex gap-2">
<x-forms.input id="settings.fqdn" label="Coolify's Domain" />
@ -22,7 +21,6 @@
</div>
</div>
</form>
<h3>Advanced</h3>
<div class="flex flex-col text-right w-52">
<x-forms.checkbox instantSave id="is_auto_update_enabled" label="Auto Update Coolify" />
@ -30,7 +28,5 @@
{{-- <x-forms.checkbox instantSave id="is_https_forced" label="Force https?" /> --}}
<x-forms.checkbox instantSave id="do_not_track" label="Do Not Track" />
</div>
@if (auth()->user()->isPartOfRootTeam())
<livewire:force-upgrade />
@endif
</div>

View File

@ -1,12 +1,9 @@
<div class="pt-4">
<h3>Switch Team</h3>
@if (auth()->user()->otherTeams()->count() > 0)
<div class="flex gap-2">
@foreach (auth()->user()->otherTeams() as $team)
<x-forms.button isHighlighted wire:key="{{ $team->id }}"
wire:click="switch_to('{{ $team->id }}')">
{{ $team->name }}</x-forms.button>
@endforeach
</div>
@endif
<div class="flex gap-2">
@foreach (auth()->user()->otherTeams() as $team)
<x-forms.button isHighlighted wire:key="{{ $team->id }}" wire:click="switch_to('{{ $team->id }}')">
{{ $team->name }}</x-forms.button>
@endforeach
</div>
</div>

View File

@ -1,3 +1,73 @@
<x-layout>
<livewire:profile.form />
<h1>Profile</h1>
<livewire:profile.form :request="$request" />
<h3>2FA</h3>
@if (session('status') == 'two-factor-authentication-enabled')
<div class="mb-4 text-sm font-medium">
Please finish configuring two factor authentication below. Read the QR code or enter the secret key
manually.
</div>
<div class="flex flex-col gap-2">
<form action="/user/confirmed-two-factor-authentication" method="POST" class="flex items-end w-32 gap-2">
@csrf
<x-forms.input type="number" id="code" label="One-time code" required />
<x-forms.button type="submit">Validate 2FA</x-forms.button>
</form>
<div>
<div>{!! $request->user()->twoFactorQrCodeSvg() !!}</div>
<div x-data="{ showCode: false }">
<x-forms.button x-on:click="showCode = !showCode">Show secret key to manually enter</x-forms.button>
<template x-if="showCode">
<div class="text-sm">{!! decrypt($request->user()->two_factor_secret) !!}</div>
</template>
</div>
</div>
</div>
@elseif(session('status') == 'two-factor-authentication-confirmed')
<div class="mb-4 text-sm">
Two factor authentication confirmed and enabled successfully.
</div>
<div>
<div class="pb-6 text-sm">Here are the recovery codes for your account. Please store them in a secure
location.</div>
<div class="text-white">
@foreach ($request->user()->recoveryCodes() as $code)
<div>{{ $code }}</div>
@endforeach
</div>
</div>
@else
@if ($request->user()->two_factor_confirmed_at)
<div class="text-sm"> Two factor authentication is <span class="text-helper">enabled</span>.</div>
<div class="flex gap-2">
<form action="/user/two-factor-authentication" method="POST">
@csrf
@method ('DELETE')
<x-forms.button type="submit">Disable</x-forms.button>
</form>
<form action="/user/two-factor-recovery-codes" method="POST">
@csrf
<x-forms.button type="submit">Regenerate Recovery Codes</x-forms.button>
</form>
</div>
@if (session('status') == 'recovery-codes-generated')
<div>
<div class="py-6 text-sm">Here are the recovery codes for your account. Please store them in a
secure
location.</div>
<div class="text-white">
@foreach ($request->user()->recoveryCodes() as $code)
<div>{{ $code }}</div>
@endforeach
</div>
</div>
@endif
@else
<div class="text-sm">Two factor authentication is <span class="text-helper">disabled</span>.</div>
<form action="/user/two-factor-authentication" method="POST">
@csrf
<x-forms.button type="submit">Configure 2FA</x-forms.button>
</form>
@endif
@endif
</x-layout>

View File

@ -1,4 +1,10 @@
<x-layout>
<h1>Settings</h1>
<livewire:settings.form :settings="$settings" />
@if (auth()->user()->isPartOfRootTeam())
<livewire:force-upgrade />
@endif
<h3 class="pb-0">Notification</h3>
<div class="pb-4 text-sm">Notification (email, discord, etc) settings for Coolify.</div>
<h4>Email</h4>
<livewire:settings.email :model="$settings" />
</x-layout>

View File

@ -1,13 +1,12 @@
<x-layout>
<div>
<h3>Current Team</h3>
<p>Name: {{ session('currentTeam.name') }}</p>
<h1>Team</h1>
<p>Current Team: {{ session('currentTeam.name') }}</p>
@if (auth()->user()->otherTeams()->count() > 0)
<livewire:switch-team />
<div class="h-12"></div>
<h3>Notifications</h3>
<livewire:notifications.discord-settings :model="session('currentTeam')" />
<livewire:notifications.email-settings :model="session('currentTeam')" />
<div class="h-12"></div>
</div>
<livewire:switch-team>
@endif
<h2>Notifications</h2>
<livewire:notifications.test :model="session('currentTeam')" />
<livewire:notifications.email-settings :model="session('currentTeam')" />
<livewire:notifications.discord-settings :model="session('currentTeam')" />
<div class="h-12"></div>
</x-layout>

View File

@ -125,8 +125,10 @@
]);
})->name('dashboard');
Route::get('/profile', function () {
return view('profile');
Route::get('/profile', function (Request $request) {
return view('profile', [
'request' => $request,
]);
})->name('profile');
Route::get('/profile/team', function () {
return view('team');