commit
7a180c7310
@ -9,6 +9,7 @@ class SaveConfigurationSync
|
|||||||
{
|
{
|
||||||
public function __invoke(Server $server, string $configuration)
|
public function __invoke(Server $server, string $configuration)
|
||||||
{
|
{
|
||||||
|
try {
|
||||||
$proxy_path = get_proxy_path();
|
$proxy_path = get_proxy_path();
|
||||||
$docker_compose_yml_base64 = base64_encode($configuration);
|
$docker_compose_yml_base64 = base64_encode($configuration);
|
||||||
|
|
||||||
@ -19,5 +20,9 @@ public function __invoke(Server $server, string $configuration)
|
|||||||
"mkdir -p $proxy_path",
|
"mkdir -p $proxy_path",
|
||||||
"echo '$docker_compose_yml_base64' | base64 -d > $proxy_path/docker-compose.yml",
|
"echo '$docker_compose_yml_base64' | base64 -d > $proxy_path/docker-compose.yml",
|
||||||
], $server);
|
], $server);
|
||||||
|
} catch (\Throwable $th) {
|
||||||
|
ray($th);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,12 +12,6 @@ class StartProxy
|
|||||||
{
|
{
|
||||||
public function __invoke(Server $server): Activity
|
public function __invoke(Server $server): Activity
|
||||||
{
|
{
|
||||||
// TODO: check for other proxies
|
|
||||||
if (is_null(data_get($server, 'proxy.type'))) {
|
|
||||||
$server->proxy->type = ProxyTypes::TRAEFIK_V2->value;
|
|
||||||
$server->proxy->status = ProxyStatus::EXITED->value;
|
|
||||||
$server->save();
|
|
||||||
}
|
|
||||||
$proxy_path = get_proxy_path();
|
$proxy_path = get_proxy_path();
|
||||||
$networks = collect($server->standaloneDockers)->map(function ($docker) {
|
$networks = collect($server->standaloneDockers)->map(function ($docker) {
|
||||||
return $docker['network'];
|
return $docker['network'];
|
||||||
|
@ -37,12 +37,15 @@ public function __invoke(Server $server, Team $team)
|
|||||||
"docker network create --attachable coolify",
|
"docker network create --attachable coolify",
|
||||||
"echo ####### Done!"
|
"echo ####### Done!"
|
||||||
], $server);
|
], $server);
|
||||||
|
$found = StandaloneDocker::where('server_id', $server->id);
|
||||||
|
if ($found->count() == 0) {
|
||||||
StandaloneDocker::create([
|
StandaloneDocker::create([
|
||||||
'name' => 'coolify',
|
'name' => 'coolify',
|
||||||
'network' => 'coolify',
|
'network' => 'coolify',
|
||||||
'server_id' => $server->id,
|
'server_id' => $server->id,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
return $activity;
|
return $activity;
|
||||||
|
@ -7,9 +7,9 @@
|
|||||||
|
|
||||||
class UpdateCoolify
|
class UpdateCoolify
|
||||||
{
|
{
|
||||||
public Server $server;
|
public ?Server $server = null;
|
||||||
public string $latest_version;
|
public ?string $latestVersion = null;
|
||||||
public string $current_version;
|
public ?string $currentVersion = null;
|
||||||
|
|
||||||
public function __invoke(bool $force)
|
public function __invoke(bool $force)
|
||||||
{
|
{
|
||||||
@ -17,13 +17,16 @@ public function __invoke(bool $force)
|
|||||||
$settings = InstanceSettings::get();
|
$settings = InstanceSettings::get();
|
||||||
ray('Running InstanceAutoUpdateJob');
|
ray('Running InstanceAutoUpdateJob');
|
||||||
$localhost_name = 'localhost';
|
$localhost_name = 'localhost';
|
||||||
$this->server = Server::where('name', $localhost_name)->firstOrFail();
|
$this->server = Server::where('name', $localhost_name)->first();
|
||||||
$this->latest_version = get_latest_version_of_coolify();
|
if (!$this->server) {
|
||||||
$this->current_version = config('version');
|
return;
|
||||||
ray('latest version:' . $this->latest_version . " current version: " . $this->current_version . ' force: ' . $force);
|
}
|
||||||
|
$this->latestVersion = get_latest_version_of_coolify();
|
||||||
|
$this->currentVersion = config('version');
|
||||||
|
ray('latest version:' . $this->latestVersion . " current version: " . $this->currentVersion . ' force: ' . $force);
|
||||||
if ($settings->next_channel) {
|
if ($settings->next_channel) {
|
||||||
ray('next channel enabled');
|
ray('next channel enabled');
|
||||||
$this->latest_version = 'next';
|
$this->latestVersion = 'next';
|
||||||
}
|
}
|
||||||
if ($force) {
|
if ($force) {
|
||||||
$this->update();
|
$this->update();
|
||||||
@ -31,15 +34,15 @@ public function __invoke(bool $force)
|
|||||||
if (!$settings->is_auto_update_enabled) {
|
if (!$settings->is_auto_update_enabled) {
|
||||||
return 'Auto update is disabled';
|
return 'Auto update is disabled';
|
||||||
}
|
}
|
||||||
if ($this->latest_version === $this->current_version) {
|
if ($this->latestVersion === $this->currentVersion) {
|
||||||
return 'Already on latest version';
|
return 'Already on latest version';
|
||||||
}
|
}
|
||||||
if (version_compare($this->latest_version, $this->current_version, '<')) {
|
if (version_compare($this->latestVersion, $this->currentVersion, '<')) {
|
||||||
return 'Latest version is lower than current version?!';
|
return 'Latest version is lower than current version?!';
|
||||||
}
|
}
|
||||||
$this->update();
|
$this->update();
|
||||||
}
|
}
|
||||||
send_internal_notification('InstanceAutoUpdateJob done to version: ' . $this->latest_version . ' from version: ' . $this->current_version);
|
send_internal_notification('InstanceAutoUpdateJob done to version: ' . $this->latestVersion . ' from version: ' . $this->currentVersion);
|
||||||
} catch (\Exception $th) {
|
} catch (\Exception $th) {
|
||||||
ray('InstanceAutoUpdateJob failed');
|
ray('InstanceAutoUpdateJob failed');
|
||||||
ray($th->getMessage());
|
ray($th->getMessage());
|
||||||
@ -51,7 +54,7 @@ public function __invoke(bool $force)
|
|||||||
private function update()
|
private function update()
|
||||||
{
|
{
|
||||||
if (isDev()) {
|
if (isDev()) {
|
||||||
ray("Running update on local docker container. Updating to $this->latest_version");
|
ray("Running update on local docker container. Updating to $this->latestVersion");
|
||||||
remote_process([
|
remote_process([
|
||||||
"sleep 10"
|
"sleep 10"
|
||||||
], $this->server);
|
], $this->server);
|
||||||
@ -61,7 +64,7 @@ private function update()
|
|||||||
ray('Running update on production server');
|
ray('Running update on production server');
|
||||||
remote_process([
|
remote_process([
|
||||||
"curl -fsSL https://cdn.coollabs.io/coolify/upgrade.sh -o /data/coolify/source/upgrade.sh",
|
"curl -fsSL https://cdn.coollabs.io/coolify/upgrade.sh -o /data/coolify/source/upgrade.sh",
|
||||||
"bash /data/coolify/source/upgrade.sh $this->latest_version"
|
"bash /data/coolify/source/upgrade.sh $this->latestVersion"
|
||||||
], $this->server);
|
], $this->server);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -25,15 +25,15 @@ protected function schedule(Schedule $schedule): void
|
|||||||
$schedule->job(new CleanupInstanceStuffsJob)->everyMinute();
|
$schedule->job(new CleanupInstanceStuffsJob)->everyMinute();
|
||||||
|
|
||||||
// $schedule->job(new CheckResaleLicenseJob)->hourly();
|
// $schedule->job(new CheckResaleLicenseJob)->hourly();
|
||||||
// $schedule->job(new DockerCleanupJob)->everyOddHour();
|
$schedule->job(new DockerCleanupJob)->everyOddHour();
|
||||||
// $schedule->job(new InstanceAutoUpdateJob(true))->everyMinute();
|
// $schedule->job(new InstanceAutoUpdateJob(true))->everyMinute();
|
||||||
} else {
|
} else {
|
||||||
$schedule->command('horizon:snapshot')->everyFiveMinutes();
|
$schedule->command('horizon:snapshot')->everyFiveMinutes();
|
||||||
$schedule->job(new CleanupInstanceStuffsJob)->everyMinute();
|
$schedule->job(new CleanupInstanceStuffsJob)->everyMinute()->onOneServer();
|
||||||
$schedule->job(new ResourceStatusJob)->everyMinute();
|
$schedule->job(new ResourceStatusJob)->everyMinute()->onOneServer();
|
||||||
$schedule->job(new CheckResaleLicenseJob)->hourly();
|
$schedule->job(new CheckResaleLicenseJob)->hourly()->onOneServer();
|
||||||
$schedule->job(new ProxyCheckJob)->everyFiveMinutes();
|
$schedule->job(new ProxyCheckJob)->everyFiveMinutes()->onOneServer();
|
||||||
$schedule->job(new DockerCleanupJob)->everyTenMinutes();
|
$schedule->job(new DockerCleanupJob)->everyTenMinutes()->onOneServer();
|
||||||
$schedule->job(new InstanceAutoUpdateJob)->everyTenMinutes();
|
$schedule->job(new InstanceAutoUpdateJob)->everyTenMinutes();
|
||||||
}
|
}
|
||||||
$this->check_scheduled_backups($schedule);
|
$this->check_scheduled_backups($schedule);
|
||||||
|
@ -5,11 +5,9 @@
|
|||||||
use App\Models\InstanceSettings;
|
use App\Models\InstanceSettings;
|
||||||
use App\Models\Project;
|
use App\Models\Project;
|
||||||
use App\Models\S3Storage;
|
use App\Models\S3Storage;
|
||||||
use App\Models\Server;
|
|
||||||
use App\Models\StandalonePostgresql;
|
use App\Models\StandalonePostgresql;
|
||||||
use App\Models\TeamInvitation;
|
use App\Models\TeamInvitation;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use App\Models\Waitlist;
|
|
||||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||||
use Illuminate\Foundation\Validation\ValidatesRequests;
|
use Illuminate\Foundation\Validation\ValidatesRequests;
|
||||||
use Illuminate\Routing\Controller as BaseController;
|
use Illuminate\Routing\Controller as BaseController;
|
||||||
@ -19,25 +17,19 @@ class Controller extends BaseController
|
|||||||
{
|
{
|
||||||
use AuthorizesRequests, ValidatesRequests;
|
use AuthorizesRequests, ValidatesRequests;
|
||||||
|
|
||||||
public function waitlist() {
|
|
||||||
$waiting_in_line = Waitlist::whereVerified(true)->count();
|
|
||||||
return view('auth.waitlist', [
|
|
||||||
'waiting_in_line' => $waiting_in_line,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
public function subscription()
|
public function subscription()
|
||||||
{
|
{
|
||||||
if (!is_cloud()) {
|
if (!isCloud()) {
|
||||||
abort(404);
|
abort(404);
|
||||||
}
|
}
|
||||||
return view('subscription.show', [
|
return view('subscription.index', [
|
||||||
'settings' => InstanceSettings::get(),
|
'settings' => InstanceSettings::get(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function license()
|
public function license()
|
||||||
{
|
{
|
||||||
if (!is_cloud()) {
|
if (!isCloud()) {
|
||||||
abort(404);
|
abort(404);
|
||||||
}
|
}
|
||||||
return view('settings.license', [
|
return view('settings.license', [
|
||||||
@ -48,23 +40,6 @@ public function license()
|
|||||||
public function force_passoword_reset() {
|
public function force_passoword_reset() {
|
||||||
return view('auth.force-password-reset');
|
return view('auth.force-password-reset');
|
||||||
}
|
}
|
||||||
public function dashboard()
|
|
||||||
{
|
|
||||||
$projects = Project::ownedByCurrentTeam()->get();
|
|
||||||
$servers = Server::ownedByCurrentTeam()->get();
|
|
||||||
$s3s = S3Storage::ownedByCurrentTeam()->get();
|
|
||||||
$resources = 0;
|
|
||||||
foreach ($projects as $project) {
|
|
||||||
$resources += $project->applications->count();
|
|
||||||
$resources += $project->postgresqls->count();
|
|
||||||
}
|
|
||||||
return view('dashboard', [
|
|
||||||
'servers' => $servers->count(),
|
|
||||||
'projects' => $projects->count(),
|
|
||||||
'resources' => $resources,
|
|
||||||
's3s' => $s3s,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
public function boarding() {
|
public function boarding() {
|
||||||
if (currentTeam()->boarding || isDev()) {
|
if (currentTeam()->boarding || isDev()) {
|
||||||
return view('boarding');
|
return view('boarding');
|
||||||
@ -97,7 +72,7 @@ public function team()
|
|||||||
if (auth()->user()->isAdminFromSession()) {
|
if (auth()->user()->isAdminFromSession()) {
|
||||||
$invitations = TeamInvitation::whereTeamId(currentTeam()->id)->get();
|
$invitations = TeamInvitation::whereTeamId(currentTeam()->id)->get();
|
||||||
}
|
}
|
||||||
return view('team.show', [
|
return view('team.index', [
|
||||||
'invitations' => $invitations,
|
'invitations' => $invitations,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@ -146,7 +121,7 @@ public function acceptInvitation()
|
|||||||
if ($diff <= config('constants.invitation.link.expiration')) {
|
if ($diff <= config('constants.invitation.link.expiration')) {
|
||||||
$user->teams()->attach($invitation->team->id, ['role' => $invitation->role]);
|
$user->teams()->attach($invitation->team->id, ['role' => $invitation->role]);
|
||||||
$invitation->delete();
|
$invitation->delete();
|
||||||
return redirect()->route('team.show');
|
return redirect()->route('team.index');
|
||||||
} else {
|
} else {
|
||||||
$invitation->delete();
|
$invitation->delete();
|
||||||
abort(401);
|
abort(401);
|
||||||
@ -168,7 +143,7 @@ public function revokeInvitation()
|
|||||||
abort(401);
|
abort(401);
|
||||||
}
|
}
|
||||||
$invitation->delete();
|
$invitation->delete();
|
||||||
return redirect()->route('team.show');
|
return redirect()->route('team.index');
|
||||||
} catch (Throwable $th) {
|
} catch (Throwable $th) {
|
||||||
throw $th;
|
throw $th;
|
||||||
}
|
}
|
||||||
|
@ -43,6 +43,7 @@ public function new()
|
|||||||
{
|
{
|
||||||
$type = request()->query('type');
|
$type = request()->query('type');
|
||||||
$destination_uuid = request()->query('destination');
|
$destination_uuid = request()->query('destination');
|
||||||
|
$server = requesT()->query('server');
|
||||||
|
|
||||||
$project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
|
$project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
|
||||||
if (!$project) {
|
if (!$project) {
|
||||||
@ -59,6 +60,9 @@ public function new()
|
|||||||
'environment_name' => $environment->name,
|
'environment_name' => $environment->name,
|
||||||
'database_uuid' => $standalone_postgresql->uuid,
|
'database_uuid' => $standalone_postgresql->uuid,
|
||||||
]);
|
]);
|
||||||
|
}
|
||||||
|
if ($server) {
|
||||||
|
|
||||||
}
|
}
|
||||||
return view('project.new', [
|
return view('project.new', [
|
||||||
'type' => $type
|
'type' => $type
|
||||||
|
@ -12,20 +12,21 @@ class ServerController extends Controller
|
|||||||
|
|
||||||
public function new_server()
|
public function new_server()
|
||||||
{
|
{
|
||||||
if (!is_cloud() || isInstanceAdmin()) {
|
$privateKeys = PrivateKey::ownedByCurrentTeam()->get();
|
||||||
|
if (!isCloud()) {
|
||||||
return view('server.create', [
|
return view('server.create', [
|
||||||
'limit_reached' => false,
|
'limit_reached' => false,
|
||||||
'private_keys' => PrivateKey::ownedByCurrentTeam()->get(),
|
'private_keys' => $privateKeys,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
$servers = currentTeam()->servers->count();
|
$team = currentTeam();
|
||||||
$subscription = currentTeam()?->subscription->type();
|
$servers = $team->servers->count();
|
||||||
$your_limit = config('constants.limits.server')[strtolower($subscription)];
|
['serverLimit' => $serverLimit] = $team->limits;
|
||||||
$limit_reached = $servers >= $your_limit;
|
$limit_reached = $servers >= $serverLimit;
|
||||||
|
|
||||||
return view('server.create', [
|
return view('server.create', [
|
||||||
'limit_reached' => $limit_reached,
|
'limit_reached' => $limit_reached,
|
||||||
'private_keys' => PrivateKey::ownedByCurrentTeam()->get(),
|
'private_keys' => $privateKeys,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -38,7 +38,7 @@ class Kernel extends HttpKernel
|
|||||||
\App\Http\Middleware\VerifyCsrfToken::class,
|
\App\Http\Middleware\VerifyCsrfToken::class,
|
||||||
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||||
\App\Http\Middleware\CheckForcePasswordReset::class,
|
\App\Http\Middleware\CheckForcePasswordReset::class,
|
||||||
\App\Http\Middleware\SubscriptionValid::class,
|
\App\Http\Middleware\IsSubscriptionValid::class,
|
||||||
\App\Http\Middleware\IsBoardingFlow::class,
|
\App\Http\Middleware\IsBoardingFlow::class,
|
||||||
|
|
||||||
],
|
],
|
||||||
|
@ -1,17 +1,20 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Http\Livewire;
|
namespace App\Http\Livewire\Boarding;
|
||||||
|
|
||||||
use App\Actions\Server\InstallDocker;
|
use App\Actions\Server\InstallDocker;
|
||||||
use App\Models\PrivateKey;
|
use App\Models\PrivateKey;
|
||||||
use App\Models\Project;
|
use App\Models\Project;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
class Boarding extends Component
|
class Index extends Component
|
||||||
{
|
{
|
||||||
public string $currentState = 'welcome';
|
public string $currentState = 'welcome';
|
||||||
|
|
||||||
|
public ?Collection $privateKeys = null;
|
||||||
|
public ?int $selectedExistingPrivateKey = null;
|
||||||
public ?string $privateKeyType = null;
|
public ?string $privateKeyType = null;
|
||||||
public ?string $privateKey = null;
|
public ?string $privateKey = null;
|
||||||
public ?string $publicKey = null;
|
public ?string $publicKey = null;
|
||||||
@ -19,6 +22,8 @@ class Boarding extends Component
|
|||||||
public ?string $privateKeyDescription = null;
|
public ?string $privateKeyDescription = null;
|
||||||
public ?PrivateKey $createdPrivateKey = null;
|
public ?PrivateKey $createdPrivateKey = null;
|
||||||
|
|
||||||
|
public ?Collection $servers = null;
|
||||||
|
public ?int $selectedExistingServer = null;
|
||||||
public ?string $remoteServerName = null;
|
public ?string $remoteServerName = null;
|
||||||
public ?string $remoteServerDescription = null;
|
public ?string $remoteServerDescription = null;
|
||||||
public ?string $remoteServerHost = null;
|
public ?string $remoteServerHost = null;
|
||||||
@ -26,6 +31,8 @@ class Boarding extends Component
|
|||||||
public ?string $remoteServerUser = 'root';
|
public ?string $remoteServerUser = 'root';
|
||||||
public ?Server $createdServer = null;
|
public ?Server $createdServer = null;
|
||||||
|
|
||||||
|
public Collection|array $projects = [];
|
||||||
|
public ?int $selectedExistingProject = null;
|
||||||
public ?Project $createdProject = null;
|
public ?Project $createdProject = null;
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
@ -45,6 +52,12 @@ public function mount()
|
|||||||
$this->remoteServerHost = 'coolify-testing-host';
|
$this->remoteServerHost = 'coolify-testing-host';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public function welcome() {
|
||||||
|
if (isCloud()) {
|
||||||
|
return $this->setServerType('remote');
|
||||||
|
}
|
||||||
|
$this->currentState = 'select-server-type';
|
||||||
|
}
|
||||||
public function restartBoarding()
|
public function restartBoarding()
|
||||||
{
|
{
|
||||||
if ($this->createdServer) {
|
if ($this->createdServer) {
|
||||||
@ -63,20 +76,62 @@ public function skipBoarding()
|
|||||||
refreshSession();
|
refreshSession();
|
||||||
return redirect()->route('dashboard');
|
return redirect()->route('dashboard');
|
||||||
}
|
}
|
||||||
public function setServer(string $type)
|
|
||||||
|
public function setServerType(string $type)
|
||||||
{
|
{
|
||||||
if ($type === 'localhost') {
|
if ($type === 'localhost') {
|
||||||
$this->createdServer = Server::find(0);
|
$this->createdServer = Server::find(0);
|
||||||
if (!$this->createdServer) {
|
if (!$this->createdServer) {
|
||||||
return $this->emit('error', 'Localhost server is not found. Something went wrong during installation. Please try to reinstall or contact support.');
|
return $this->emit('error', 'Localhost server is not found. Something went wrong during installation. Please try to reinstall or contact support.');
|
||||||
}
|
}
|
||||||
$this->currentState = 'select-proxy';
|
return $this->validateServer();
|
||||||
} elseif ($type === 'remote') {
|
} elseif ($type === 'remote') {
|
||||||
|
$this->privateKeys = PrivateKey::ownedByCurrentTeam(['name'])->where('id', '!=', 0)->get();
|
||||||
|
if ($this->privateKeys->count() > 0) {
|
||||||
|
$this->selectedExistingPrivateKey = $this->privateKeys->first()->id;
|
||||||
|
}
|
||||||
|
$this->servers = Server::ownedByCurrentTeam(['name'])->where('id', '!=', 0)->get();
|
||||||
|
if ($this->servers->count() > 0) {
|
||||||
|
$this->selectedExistingServer = $this->servers->first()->id;
|
||||||
|
$this->currentState = 'select-existing-server';
|
||||||
|
return;
|
||||||
|
}
|
||||||
$this->currentState = 'private-key';
|
$this->currentState = 'private-key';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public function selectExistingServer()
|
||||||
|
{
|
||||||
|
$this->createdServer = Server::find($this->selectedExistingServer);
|
||||||
|
if (!$this->createdServer) {
|
||||||
|
$this->emit('error', 'Server is not found.');
|
||||||
|
$this->currentState = 'private-key';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$this->selectedExistingPrivateKey = $this->createdServer->privateKey->id;
|
||||||
|
$this->validateServer();
|
||||||
|
$this->getProxyType();
|
||||||
|
$this->getProjects();
|
||||||
|
}
|
||||||
|
public function getProxyType() {
|
||||||
|
$proxyTypeSet = $this->createdServer->proxy->type;
|
||||||
|
if (!$proxyTypeSet) {
|
||||||
|
$this->currentState = 'select-proxy';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$this->getProjects();
|
||||||
|
}
|
||||||
|
public function selectExistingPrivateKey()
|
||||||
|
{
|
||||||
|
$this->currentState = 'create-server';
|
||||||
|
}
|
||||||
|
public function createNewServer()
|
||||||
|
{
|
||||||
|
$this->selectedExistingServer = null;
|
||||||
|
$this->currentState = 'private-key';
|
||||||
|
}
|
||||||
public function setPrivateKey(string $type)
|
public function setPrivateKey(string $type)
|
||||||
{
|
{
|
||||||
|
$this->selectedExistingPrivateKey = null;
|
||||||
$this->privateKeyType = $type;
|
$this->privateKeyType = $type;
|
||||||
if ($type === 'create' && !isDev()) {
|
if ($type === 'create' && !isDev()) {
|
||||||
$this->createNewPrivateKey();
|
$this->createNewPrivateKey();
|
||||||
@ -115,11 +170,12 @@ public function saveServer()
|
|||||||
'private_key_id' => $this->createdPrivateKey->id,
|
'private_key_id' => $this->createdPrivateKey->id,
|
||||||
'team_id' => currentTeam()->id
|
'team_id' => currentTeam()->id
|
||||||
]);
|
]);
|
||||||
|
$this->validateServer();
|
||||||
|
}
|
||||||
|
public function validateServer() {
|
||||||
try {
|
try {
|
||||||
['uptime' => $uptime, 'dockerVersion' => $dockerVersion] = validateServer($this->createdServer);
|
['uptime' => $uptime, 'dockerVersion' => $dockerVersion] = validateServer($this->createdServer);
|
||||||
if (!$uptime) {
|
if (!$uptime) {
|
||||||
$this->createdServer->delete();
|
|
||||||
$this->createdPrivateKey->delete();
|
|
||||||
throw new \Exception('Server is not reachable.');
|
throw new \Exception('Server is not reachable.');
|
||||||
} else {
|
} else {
|
||||||
$this->createdServer->settings->update([
|
$this->createdServer->settings->update([
|
||||||
@ -127,11 +183,14 @@ public function saveServer()
|
|||||||
]);
|
]);
|
||||||
$this->emit('success', 'Server is reachable.');
|
$this->emit('success', 'Server is reachable.');
|
||||||
}
|
}
|
||||||
if ($dockerVersion) {
|
ray($dockerVersion, $uptime);
|
||||||
|
if (!$dockerVersion) {
|
||||||
$this->emit('error', 'Docker is not installed on the server.');
|
$this->emit('error', 'Docker is not installed on the server.');
|
||||||
$this->currentState = 'install-docker';
|
$this->currentState = 'install-docker';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
$this->getProxyType();
|
||||||
|
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
return general_error_handler(customErrorMessage: "Server is not reachable. Reason: {$e->getMessage()}", that: $this);
|
return general_error_handler(customErrorMessage: "Server is not reachable. Reason: {$e->getMessage()}", that: $this);
|
||||||
}
|
}
|
||||||
@ -145,13 +204,25 @@ public function installDocker()
|
|||||||
public function selectProxy(string|null $proxyType = null)
|
public function selectProxy(string|null $proxyType = null)
|
||||||
{
|
{
|
||||||
if (!$proxyType) {
|
if (!$proxyType) {
|
||||||
return $this->currentState = 'create-project';
|
return $this->getProjects();
|
||||||
}
|
}
|
||||||
$this->createdServer->proxy->type = $proxyType;
|
$this->createdServer->proxy->type = $proxyType;
|
||||||
$this->createdServer->proxy->status = 'exited';
|
$this->createdServer->proxy->status = 'exited';
|
||||||
$this->createdServer->save();
|
$this->createdServer->save();
|
||||||
|
$this->getProjects();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getProjects() {
|
||||||
|
$this->projects = Project::ownedByCurrentTeam(['name'])->get();
|
||||||
|
if ($this->projects->count() > 0) {
|
||||||
|
$this->selectedExistingProject = $this->projects->first()->id;
|
||||||
|
}
|
||||||
$this->currentState = 'create-project';
|
$this->currentState = 'create-project';
|
||||||
}
|
}
|
||||||
|
public function selectExistingProject() {
|
||||||
|
$this->createdProject = Project::find($this->selectedExistingProject);
|
||||||
|
$this->currentState = 'create-resource';
|
||||||
|
}
|
||||||
public function createNewProject()
|
public function createNewProject()
|
||||||
{
|
{
|
||||||
$this->createdProject = Project::create([
|
$this->createdProject = Project::create([
|
||||||
@ -168,7 +239,7 @@ public function showNewResource()
|
|||||||
[
|
[
|
||||||
'project_uuid' => $this->createdProject->uuid,
|
'project_uuid' => $this->createdProject->uuid,
|
||||||
'environment_name' => 'production',
|
'environment_name' => 'production',
|
||||||
|
'server'=> $this->createdServer->id,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -178,4 +249,8 @@ private function createNewPrivateKey()
|
|||||||
$this->privateKeyDescription = 'Created by Coolify';
|
$this->privateKeyDescription = 'Created by Coolify';
|
||||||
['private' => $this->privateKey, 'public' => $this->publicKey] = generateSSHKey();
|
['private' => $this->privateKey, 'public' => $this->publicKey] = generateSSHKey();
|
||||||
}
|
}
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.boarding.index')->layout('layouts.boarding');
|
||||||
|
}
|
||||||
}
|
}
|
32
app/Http/Livewire/Dashboard.php
Normal file
32
app/Http/Livewire/Dashboard.php
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Livewire;
|
||||||
|
|
||||||
|
use App\Models\Project;
|
||||||
|
use App\Models\S3Storage;
|
||||||
|
use App\Models\Server;
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class Dashboard extends Component
|
||||||
|
{
|
||||||
|
public int $projects = 0;
|
||||||
|
public int $servers = 0;
|
||||||
|
public int $s3s = 0;
|
||||||
|
public int $resources = 0;
|
||||||
|
|
||||||
|
public function mount()
|
||||||
|
{
|
||||||
|
$this->servers = Server::ownedByCurrentTeam()->get()->count();
|
||||||
|
$this->s3s = S3Storage::ownedByCurrentTeam()->get()->count();
|
||||||
|
$projects = Project::ownedByCurrentTeam()->get();
|
||||||
|
foreach ($projects as $project) {
|
||||||
|
$this->resources += $project->applications->count();
|
||||||
|
$this->resources += $project->postgresqls->count();
|
||||||
|
}
|
||||||
|
$this->projects = $projects->count();
|
||||||
|
}
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.dashboard');
|
||||||
|
}
|
||||||
|
}
|
@ -1,41 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Livewire\Dev;
|
|
||||||
|
|
||||||
use App\Models\S3Storage;
|
|
||||||
use Illuminate\Support\Facades\Storage;
|
|
||||||
use Livewire\Component;
|
|
||||||
use Livewire\WithFileUploads;
|
|
||||||
|
|
||||||
class S3Test extends Component
|
|
||||||
{
|
|
||||||
use WithFileUploads;
|
|
||||||
|
|
||||||
public $s3;
|
|
||||||
public $file;
|
|
||||||
|
|
||||||
public function mount()
|
|
||||||
{
|
|
||||||
$this->s3 = S3Storage::first();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function save()
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$this->validate([
|
|
||||||
'file' => 'required|max:150', // 1MB Max
|
|
||||||
]);
|
|
||||||
set_s3_target($this->s3);
|
|
||||||
$this->file->storeAs('files', $this->file->getClientOriginalName(), 'custom-s3');
|
|
||||||
$this->emit('success', 'File uploaded successfully.');
|
|
||||||
} catch (\Throwable $th) {
|
|
||||||
return general_error_handler($th, $this, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function get_files()
|
|
||||||
{
|
|
||||||
set_s3_target($this->s3);
|
|
||||||
dd(Storage::disk('custom-s3')->files('files'));
|
|
||||||
}
|
|
||||||
}
|
|
@ -6,55 +6,143 @@
|
|||||||
use App\Models\Team;
|
use App\Models\Team;
|
||||||
use App\Notifications\Test;
|
use App\Notifications\Test;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
use Log;
|
||||||
|
|
||||||
class EmailSettings extends Component
|
class EmailSettings extends Component
|
||||||
{
|
{
|
||||||
public Team $model;
|
public Team $team;
|
||||||
public string $emails;
|
public string $emails;
|
||||||
|
public bool $sharedEmailEnabled = false;
|
||||||
|
|
||||||
protected $rules = [
|
protected $rules = [
|
||||||
'model.smtp_enabled' => 'nullable|boolean',
|
'team.smtp_enabled' => 'nullable|boolean',
|
||||||
'model.smtp_from_address' => 'required|email',
|
'team.smtp_from_address' => 'required|email',
|
||||||
'model.smtp_from_name' => 'required',
|
'team.smtp_from_name' => 'required',
|
||||||
'model.smtp_recipients' => 'nullable',
|
'team.smtp_recipients' => 'nullable',
|
||||||
'model.smtp_host' => 'required',
|
'team.smtp_host' => 'required',
|
||||||
'model.smtp_port' => 'required',
|
'team.smtp_port' => 'required',
|
||||||
'model.smtp_encryption' => 'nullable',
|
'team.smtp_encryption' => 'nullable',
|
||||||
'model.smtp_username' => 'nullable',
|
'team.smtp_username' => 'nullable',
|
||||||
'model.smtp_password' => 'nullable',
|
'team.smtp_password' => 'nullable',
|
||||||
'model.smtp_timeout' => 'nullable',
|
'team.smtp_timeout' => 'nullable',
|
||||||
'model.smtp_notifications_test' => 'nullable|boolean',
|
'team.smtp_notifications_test' => 'nullable|boolean',
|
||||||
'model.smtp_notifications_deployments' => 'nullable|boolean',
|
'team.smtp_notifications_deployments' => 'nullable|boolean',
|
||||||
'model.smtp_notifications_status_changes' => 'nullable|boolean',
|
'team.smtp_notifications_status_changes' => 'nullable|boolean',
|
||||||
'model.smtp_notifications_database_backups' => 'nullable|boolean',
|
'team.smtp_notifications_database_backups' => 'nullable|boolean',
|
||||||
|
'team.use_instance_email_settings' => 'boolean',
|
||||||
|
'team.resend_enabled' => 'nullable|boolean',
|
||||||
|
'team.resend_api_key' => 'nullable',
|
||||||
];
|
];
|
||||||
protected $validationAttributes = [
|
protected $validationAttributes = [
|
||||||
'model.smtp_from_address' => 'From Address',
|
'team.smtp_from_address' => 'From Address',
|
||||||
'model.smtp_from_name' => 'From Name',
|
'team.smtp_from_name' => 'From Name',
|
||||||
'model.smtp_recipients' => 'Recipients',
|
'team.smtp_recipients' => 'Recipients',
|
||||||
'model.smtp_host' => 'Host',
|
'team.smtp_host' => 'Host',
|
||||||
'model.smtp_port' => 'Port',
|
'team.smtp_port' => 'Port',
|
||||||
'model.smtp_encryption' => 'Encryption',
|
'team.smtp_encryption' => 'Encryption',
|
||||||
'model.smtp_username' => 'Username',
|
'team.smtp_username' => 'Username',
|
||||||
'model.smtp_password' => 'Password',
|
'team.smtp_password' => 'Password',
|
||||||
|
'team.smtp_timeout' => 'Timeout',
|
||||||
|
'team.resend_enabled' => 'Resend Enabled',
|
||||||
|
'team.resend_api_key' => 'Resend API Key',
|
||||||
];
|
];
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
$this->decrypt();
|
['sharedEmailEnabled' => $this->sharedEmailEnabled] = $this->team->limits;
|
||||||
$this->emails = auth()->user()->email;
|
$this->emails = auth()->user()->email;
|
||||||
}
|
}
|
||||||
|
public function submitFromFields()
|
||||||
private function decrypt()
|
|
||||||
{
|
{
|
||||||
if (data_get($this->model, 'smtp_password')) {
|
|
||||||
try {
|
try {
|
||||||
$this->model->smtp_password = decrypt($this->model->smtp_password);
|
$this->resetErrorBag();
|
||||||
|
$this->validate([
|
||||||
|
'team.smtp_from_address' => 'required|email',
|
||||||
|
'team.smtp_from_name' => 'required',
|
||||||
|
]);
|
||||||
|
$this->team->save();
|
||||||
|
$this->emit('success', 'Settings saved successfully.');
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
|
return general_error_handler($e, $this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public function sendTestNotification()
|
||||||
|
{
|
||||||
|
$this->team->notify(new Test($this->emails));
|
||||||
|
$this->emit('success', 'Test Email sent successfully.');
|
||||||
|
}
|
||||||
|
public function instantSaveInstance()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
if (!$this->sharedEmailEnabled) {
|
||||||
|
throw new \Exception('Not allowed to change settings. Please upgrade your subscription.');
|
||||||
|
}
|
||||||
|
$this->team->smtp_enabled = false;
|
||||||
|
$this->team->resend_enabled = false;
|
||||||
|
$this->team->save();
|
||||||
|
$this->emit('success', 'Settings saved successfully.');
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return general_error_handler($e, $this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function instantSaveResend()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$this->team->smtp_enabled = false;
|
||||||
|
$this->submitResend();
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->team->smtp_enabled = false;
|
||||||
|
return general_error_handler($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function instantSave()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$this->team->resend_enabled = false;
|
||||||
|
$this->submit();
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->team->smtp_enabled = false;
|
||||||
|
return general_error_handler($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function submit()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$this->resetErrorBag();
|
||||||
|
$this->validate([
|
||||||
|
'team.smtp_from_address' => 'required|email',
|
||||||
|
'team.smtp_from_name' => 'required',
|
||||||
|
'team.smtp_host' => 'required',
|
||||||
|
'team.smtp_port' => 'required|numeric',
|
||||||
|
'team.smtp_encryption' => 'nullable',
|
||||||
|
'team.smtp_username' => 'nullable',
|
||||||
|
'team.smtp_password' => 'nullable',
|
||||||
|
'team.smtp_timeout' => 'nullable',
|
||||||
|
]);
|
||||||
|
$this->team->save();
|
||||||
|
$this->emit('success', 'Settings saved successfully.');
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->team->smtp_enabled = false;
|
||||||
|
return general_error_handler($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function submitResend()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$this->resetErrorBag();
|
||||||
|
$this->validate([
|
||||||
|
'team.resend_api_key' => 'required'
|
||||||
|
]);
|
||||||
|
$this->team->save();
|
||||||
|
refreshSession();
|
||||||
|
$this->emit('success', 'Settings saved successfully.');
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->team->resend_enabled = false;
|
||||||
|
return general_error_handler($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
public function copyFromInstanceSettings()
|
public function copyFromInstanceSettings()
|
||||||
{
|
{
|
||||||
$settings = InstanceSettings::get();
|
$settings = InstanceSettings::get();
|
||||||
@ -72,55 +160,22 @@ public function copyFromInstanceSettings()
|
|||||||
'smtp_password' => $settings->smtp_password,
|
'smtp_password' => $settings->smtp_password,
|
||||||
'smtp_timeout' => $settings->smtp_timeout,
|
'smtp_timeout' => $settings->smtp_timeout,
|
||||||
]);
|
]);
|
||||||
$this->decrypt();
|
|
||||||
if (is_a($team, Team::class)) {
|
|
||||||
refreshSession();
|
refreshSession();
|
||||||
}
|
$this->team = $team;
|
||||||
$this->model = $team;
|
|
||||||
$this->emit('success', 'Settings saved.');
|
$this->emit('success', 'Settings saved.');
|
||||||
} else {
|
return;
|
||||||
$this->emit('error', 'Instance SMTP settings are not enabled.');
|
|
||||||
}
|
}
|
||||||
}
|
if ($settings->resend_enabled) {
|
||||||
|
$team = currentTeam();
|
||||||
public function sendTestNotification()
|
$team->update([
|
||||||
{
|
'resend_enabled' => $settings->resend_enabled,
|
||||||
$this->model->notify(new Test($this->emails));
|
'resend_api_key' => $settings->resend_api_key,
|
||||||
$this->emit('success', 'Test Email sent successfully.');
|
]);
|
||||||
}
|
|
||||||
|
|
||||||
public function instantSave()
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$this->submit();
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
$this->model->smtp_enabled = false;
|
|
||||||
$this->validate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function submit()
|
|
||||||
{
|
|
||||||
$this->resetErrorBag();
|
|
||||||
$this->validate();
|
|
||||||
|
|
||||||
if ($this->model->smtp_password) {
|
|
||||||
$this->model->smtp_password = encrypt($this->model->smtp_password);
|
|
||||||
} else {
|
|
||||||
$this->model->smtp_password = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->model->smtp_recipients = str_replace(' ', '', $this->model->smtp_recipients);
|
|
||||||
$this->saveModel();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function saveModel()
|
|
||||||
{
|
|
||||||
$this->model->save();
|
|
||||||
$this->decrypt();
|
|
||||||
if (is_a($this->model, Team::class)) {
|
|
||||||
refreshSession();
|
refreshSession();
|
||||||
}
|
$this->team = $team;
|
||||||
$this->emit('success', 'Settings saved.');
|
$this->emit('success', 'Settings saved.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$this->emit('error', 'Instance SMTP/Resend settings are not enabled.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,15 +7,19 @@
|
|||||||
use App\Models\Project;
|
use App\Models\Project;
|
||||||
use App\Models\StandaloneDocker;
|
use App\Models\StandaloneDocker;
|
||||||
use App\Models\SwarmDocker;
|
use App\Models\SwarmDocker;
|
||||||
|
use App\Traits\SaveFromRedirect;
|
||||||
use Illuminate\Support\Facades\Http;
|
use Illuminate\Support\Facades\Http;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
use Route;
|
||||||
|
|
||||||
class GithubPrivateRepository extends Component
|
class GithubPrivateRepository extends Component
|
||||||
{
|
{
|
||||||
|
use SaveFromRedirect;
|
||||||
public $current_step = 'github_apps';
|
public $current_step = 'github_apps';
|
||||||
public $github_apps;
|
public $github_apps;
|
||||||
public GithubApp $github_app;
|
public GithubApp $github_app;
|
||||||
public $parameters;
|
public $parameters;
|
||||||
|
public $currentRoute;
|
||||||
public $query;
|
public $query;
|
||||||
public $type;
|
public $type;
|
||||||
|
|
||||||
@ -36,14 +40,30 @@ class GithubPrivateRepository extends Component
|
|||||||
public string|null $publish_directory = null;
|
public string|null $publish_directory = null;
|
||||||
protected int $page = 1;
|
protected int $page = 1;
|
||||||
|
|
||||||
|
// public function saveFromRedirect(string $route, ?Collection $parameters = null){
|
||||||
|
// session()->forget('from');
|
||||||
|
// if (!$parameters || $parameters->count() === 0) {
|
||||||
|
// $parameters = $this->parameters;
|
||||||
|
// }
|
||||||
|
// $parameters = collect($parameters) ?? collect([]);
|
||||||
|
// $queries = collect($this->query) ?? collect([]);
|
||||||
|
// $parameters = $parameters->merge($queries);
|
||||||
|
// session(['from'=> [
|
||||||
|
// 'back'=> $this->currentRoute,
|
||||||
|
// 'route' => $route,
|
||||||
|
// 'parameters' => $parameters
|
||||||
|
// ]]);
|
||||||
|
// return redirect()->route($route);
|
||||||
|
// }
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
|
$this->currentRoute = Route::currentRouteName();
|
||||||
$this->parameters = get_route_parameters();
|
$this->parameters = get_route_parameters();
|
||||||
$this->query = request()->query();
|
$this->query = request()->query();
|
||||||
$this->repositories = $this->branches = collect();
|
$this->repositories = $this->branches = collect();
|
||||||
$this->github_apps = GithubApp::private();
|
$this->github_apps = GithubApp::private();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function loadRepositories($github_app_id)
|
public function loadRepositories($github_app_id)
|
||||||
{
|
{
|
||||||
$this->repositories = collect();
|
$this->repositories = collect();
|
||||||
|
@ -3,19 +3,28 @@
|
|||||||
namespace App\Http\Livewire\Project\New;
|
namespace App\Http\Livewire\Project\New;
|
||||||
|
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
|
use App\Models\StandaloneDocker;
|
||||||
|
use App\Models\SwarmDocker;
|
||||||
use Countable;
|
use Countable;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
use Route;
|
||||||
|
|
||||||
class Select extends Component
|
class Select extends Component
|
||||||
{
|
{
|
||||||
public $current_step = 'type';
|
public $current_step = 'type';
|
||||||
|
public ?int $server = null;
|
||||||
public string $type;
|
public string $type;
|
||||||
public string $server_id;
|
public string $server_id;
|
||||||
public string $destination_uuid;
|
public string $destination_uuid;
|
||||||
public Countable|array|Server $servers;
|
public Countable|array|Server $servers;
|
||||||
public $destinations = [];
|
public Collection|array $standaloneDockers = [];
|
||||||
|
public Collection|array $swarmDockers = [];
|
||||||
public array $parameters;
|
public array $parameters;
|
||||||
|
|
||||||
|
protected $queryString = [
|
||||||
|
'server',
|
||||||
|
];
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
$this->parameters = get_route_parameters();
|
$this->parameters = get_route_parameters();
|
||||||
@ -31,13 +40,20 @@ public function set_type(string $type)
|
|||||||
$this->set_destination($server->destinations()->first()->uuid);
|
$this->set_destination($server->destinations()->first()->uuid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!is_null($this->server)) {
|
||||||
|
$foundServer = $this->servers->where('id', $this->server)->first();
|
||||||
|
if ($foundServer) {
|
||||||
|
return $this->set_server($foundServer);
|
||||||
|
}
|
||||||
|
}
|
||||||
$this->current_step = 'servers';
|
$this->current_step = 'servers';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function set_server(Server $server)
|
public function set_server(Server $server)
|
||||||
{
|
{
|
||||||
$this->server_id = $server->id;
|
$this->server_id = $server->id;
|
||||||
$this->destinations = $server->destinations();
|
$this->standaloneDockers = $server->standaloneDockers;
|
||||||
|
$this->swarmDockers = $server->swarmDockers;
|
||||||
$this->current_step = 'destinations';
|
$this->current_step = 'destinations';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
20
app/Http/Livewire/Server/All.php
Normal file
20
app/Http/Livewire/Server/All.php
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Livewire\Server;
|
||||||
|
|
||||||
|
use App\Models\Server;
|
||||||
|
use Illuminate\Database\Eloquent\Collection;
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class All extends Component
|
||||||
|
{
|
||||||
|
public ?Collection $servers = null;
|
||||||
|
|
||||||
|
public function mount () {
|
||||||
|
$this->servers = Server::ownedByCurrentTeam()->get();
|
||||||
|
}
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.server.all');
|
||||||
|
}
|
||||||
|
}
|
@ -4,10 +4,12 @@
|
|||||||
|
|
||||||
use App\Actions\Server\InstallDocker;
|
use App\Actions\Server\InstallDocker;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
|
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
class Form extends Component
|
class Form extends Component
|
||||||
{
|
{
|
||||||
|
use AuthorizesRequests;
|
||||||
public Server $server;
|
public Server $server;
|
||||||
public $uptime;
|
public $uptime;
|
||||||
public $dockerVersion;
|
public $dockerVersion;
|
||||||
@ -64,14 +66,20 @@ public function validateServer()
|
|||||||
|
|
||||||
public function delete()
|
public function delete()
|
||||||
{
|
{
|
||||||
|
try {
|
||||||
|
$this->authorize('delete', $this->server);
|
||||||
if (!$this->server->isEmpty()) {
|
if (!$this->server->isEmpty()) {
|
||||||
$this->emit('error', 'Server has defined resources. Please delete them first.');
|
$this->emit('error', 'Server has defined resources. Please delete them first.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$this->server->delete();
|
$this->server->delete();
|
||||||
redirect()->route('server.all');
|
return redirect()->route('server.all');
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return general_error_handler(err: $e, that: $this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
public function submit()
|
public function submit()
|
||||||
{
|
{
|
||||||
$this->validate();
|
$this->validate();
|
||||||
|
@ -12,6 +12,7 @@ class Status extends Component
|
|||||||
|
|
||||||
public function get_status()
|
public function get_status()
|
||||||
{
|
{
|
||||||
|
if (data_get($this->server,'settings.is_usable')) {
|
||||||
dispatch_sync(new ProxyContainerStatusJob(
|
dispatch_sync(new ProxyContainerStatusJob(
|
||||||
server: $this->server
|
server: $this->server
|
||||||
));
|
));
|
||||||
@ -19,3 +20,4 @@ public function get_status()
|
|||||||
$this->emit('proxyStatusUpdated');
|
$this->emit('proxyStatusUpdated');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
25
app/Http/Livewire/Server/Show.php
Normal file
25
app/Http/Livewire/Server/Show.php
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Livewire\Server;
|
||||||
|
|
||||||
|
use App\Models\Server;
|
||||||
|
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class Show extends Component
|
||||||
|
{
|
||||||
|
use AuthorizesRequests;
|
||||||
|
public ?Server $server = null;
|
||||||
|
public function mount()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$this->server = Server::ownedByCurrentTeam(['name', 'description', 'ip', 'port', 'user', 'proxy'])->whereUuid(request()->server_uuid)->firstOrFail();
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return general_error_handler(err: $e, that: $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.server.show');
|
||||||
|
}
|
||||||
|
}
|
@ -6,7 +6,7 @@
|
|||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
use Masmerise\Toaster\Toaster;
|
use Masmerise\Toaster\Toaster;
|
||||||
|
|
||||||
class PrivateKey extends Component
|
class ShowPrivateKey extends Component
|
||||||
{
|
{
|
||||||
public Server $server;
|
public Server $server;
|
||||||
public $privateKeys;
|
public $privateKeys;
|
@ -20,6 +20,9 @@ class Email extends Component
|
|||||||
'settings.smtp_timeout' => 'nullable',
|
'settings.smtp_timeout' => 'nullable',
|
||||||
'settings.smtp_from_address' => 'required|email',
|
'settings.smtp_from_address' => 'required|email',
|
||||||
'settings.smtp_from_name' => 'required',
|
'settings.smtp_from_name' => 'required',
|
||||||
|
'settings.resend_enabled' => 'nullable|boolean',
|
||||||
|
'settings.resend_api_key' => 'nullable'
|
||||||
|
|
||||||
];
|
];
|
||||||
protected $validationAttributes = [
|
protected $validationAttributes = [
|
||||||
'settings.smtp_from_address' => 'From Address',
|
'settings.smtp_from_address' => 'From Address',
|
||||||
@ -30,48 +33,68 @@ class Email extends Component
|
|||||||
'settings.smtp_encryption' => 'Encryption',
|
'settings.smtp_encryption' => 'Encryption',
|
||||||
'settings.smtp_username' => 'Username',
|
'settings.smtp_username' => 'Username',
|
||||||
'settings.smtp_password' => 'Password',
|
'settings.smtp_password' => 'Password',
|
||||||
|
'settings.smtp_timeout' => 'Timeout',
|
||||||
|
'settings.resend_api_key' => 'Resend API Key'
|
||||||
];
|
];
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
$this->decrypt();
|
|
||||||
$this->emails = auth()->user()->email;
|
$this->emails = auth()->user()->email;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function decrypt()
|
public function submitFromFields() {
|
||||||
{
|
|
||||||
if (data_get($this->settings, 'smtp_password')) {
|
|
||||||
try {
|
try {
|
||||||
$this->settings->smtp_password = decrypt($this->settings->smtp_password);
|
$this->resetErrorBag();
|
||||||
|
$this->validate([
|
||||||
|
'settings.smtp_from_address' => 'required|email',
|
||||||
|
'settings.smtp_from_name' => 'required',
|
||||||
|
]);
|
||||||
|
$this->settings->save();
|
||||||
|
$this->emit('success', 'Settings saved successfully.');
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
|
return general_error_handler($e, $this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public function submitResend() {
|
||||||
|
try {
|
||||||
|
$this->resetErrorBag();
|
||||||
|
$this->validate([
|
||||||
|
'settings.resend_api_key' => 'required'
|
||||||
|
]);
|
||||||
|
$this->settings->smtp_enabled = false;
|
||||||
|
$this->settings->save();
|
||||||
|
$this->emit('success', 'Settings saved successfully.');
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->settings->resend_enabled = false;
|
||||||
|
return general_error_handler($e, $this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function instantSave()
|
public function instantSave()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$this->submit();
|
$this->submit();
|
||||||
$this->emit('success', 'Settings saved successfully.');
|
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
$this->settings->smtp_enabled = false;
|
return general_error_handler($e, $this);
|
||||||
$this->validate();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function submit()
|
public function submit()
|
||||||
{
|
{
|
||||||
|
try {
|
||||||
$this->resetErrorBag();
|
$this->resetErrorBag();
|
||||||
$this->validate();
|
$this->validate([
|
||||||
if ($this->settings->smtp_password) {
|
'settings.smtp_host' => 'required',
|
||||||
$this->settings->smtp_password = encrypt($this->settings->smtp_password);
|
'settings.smtp_port' => 'required|numeric',
|
||||||
} else {
|
'settings.smtp_encryption' => 'nullable',
|
||||||
$this->settings->smtp_password = null;
|
'settings.smtp_username' => 'nullable',
|
||||||
}
|
'settings.smtp_password' => 'nullable',
|
||||||
|
'settings.smtp_timeout' => 'nullable',
|
||||||
|
]);
|
||||||
|
$this->settings->resend_enabled = false;
|
||||||
$this->settings->save();
|
$this->settings->save();
|
||||||
$this->emit('success', 'Transaction email settings updated successfully.');
|
$this->emit('success', 'Settings saved successfully.');
|
||||||
$this->decrypt();
|
} catch (\Exception $e) {
|
||||||
|
return general_error_handler($e, $this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function sendTestNotification()
|
public function sendTestNotification()
|
||||||
|
@ -37,10 +37,14 @@ class Change extends Component
|
|||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
|
if (isCloud() && !isDev()) {
|
||||||
|
$this->webhook_endpoint = config('app.url');
|
||||||
|
} else {
|
||||||
$this->webhook_endpoint = $this->ipv4;
|
$this->webhook_endpoint = $this->ipv4;
|
||||||
$this->parameters = get_route_parameters();
|
|
||||||
$this->is_system_wide = $this->github_app->is_system_wide;
|
$this->is_system_wide = $this->github_app->is_system_wide;
|
||||||
}
|
}
|
||||||
|
$this->parameters = get_route_parameters();
|
||||||
|
}
|
||||||
|
|
||||||
public function submit()
|
public function submit()
|
||||||
{
|
{
|
||||||
|
@ -42,6 +42,9 @@ public function createGitHubApp()
|
|||||||
'is_system_wide' => $this->is_system_wide,
|
'is_system_wide' => $this->is_system_wide,
|
||||||
'team_id' => currentTeam()->id,
|
'team_id' => currentTeam()->id,
|
||||||
]);
|
]);
|
||||||
|
if (session('from')) {
|
||||||
|
session(['from' => session('from') + ['source_id' => $github_app->id]]);
|
||||||
|
}
|
||||||
redirect()->route('source.github.show', ['github_app_uuid' => $github_app->uuid]);
|
redirect()->route('source.github.show', ['github_app_uuid' => $github_app->uuid]);
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
return general_error_handler(err: $e, that: $this);
|
return general_error_handler(err: $e, that: $this);
|
||||||
|
@ -48,8 +48,8 @@ public function subscribeStripe($type)
|
|||||||
'enabled' => true,
|
'enabled' => true,
|
||||||
],
|
],
|
||||||
'mode' => 'subscription',
|
'mode' => 'subscription',
|
||||||
'success_url' => route('subscription.success'),
|
'success_url' => route('dashboard', ['success' => true]),
|
||||||
'cancel_url' => route('subscription.show',['cancelled' => true]),
|
'cancel_url' => route('subscription.index', ['cancelled' => true]),
|
||||||
];
|
];
|
||||||
$customer = currentTeam()->subscription?->stripe_customer_id ?? null;
|
$customer = currentTeam()->subscription?->stripe_customer_id ?? null;
|
||||||
if ($customer) {
|
if ($customer) {
|
||||||
|
@ -30,7 +30,7 @@ public function submit()
|
|||||||
]);
|
]);
|
||||||
auth()->user()->teams()->attach($team, ['role' => 'admin']);
|
auth()->user()->teams()->attach($team, ['role' => 'admin']);
|
||||||
refreshSession();
|
refreshSession();
|
||||||
return redirect()->route('team.show');
|
return redirect()->route('team.index');
|
||||||
} catch (\Throwable $th) {
|
} catch (\Throwable $th) {
|
||||||
return general_error_handler($th, $this);
|
return general_error_handler($th, $this);
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,6 @@ public function delete()
|
|||||||
});
|
});
|
||||||
|
|
||||||
refreshSession();
|
refreshSession();
|
||||||
return redirect()->route('team.show');
|
return redirect()->route('team.index');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,6 @@ public function submit()
|
|||||||
try {
|
try {
|
||||||
$this->team->save();
|
$this->team->save();
|
||||||
refreshSession();
|
refreshSession();
|
||||||
$this->emit('reloadWindow');
|
|
||||||
} catch (\Throwable $th) {
|
} catch (\Throwable $th) {
|
||||||
return general_error_handler($th, $this);
|
return general_error_handler($th, $this);
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,7 @@ public function deleteInvitation(int $invitation_id)
|
|||||||
{
|
{
|
||||||
TeamInvitation::find($invitation_id)->delete();
|
TeamInvitation::find($invitation_id)->delete();
|
||||||
$this->refreshInvitations();
|
$this->refreshInvitations();
|
||||||
|
$this->emit('success', 'Invitation revoked.');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function refreshInvitations()
|
public function refreshInvitations()
|
||||||
|
@ -1,22 +1,27 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Http\Livewire;
|
namespace App\Http\Livewire\Waitlist;
|
||||||
|
|
||||||
use App\Jobs\SendConfirmationForWaitlistJob;
|
use App\Jobs\SendConfirmationForWaitlistJob;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use App\Models\Waitlist as ModelsWaitlist;
|
use App\Models\Waitlist;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
class Waitlist extends Component
|
class Index extends Component
|
||||||
{
|
{
|
||||||
public string $email;
|
public string $email;
|
||||||
public int $waiting_in_line = 0;
|
public int $waitingInLine = 0;
|
||||||
|
|
||||||
protected $rules = [
|
protected $rules = [
|
||||||
'email' => 'required|email',
|
'email' => 'required|email',
|
||||||
];
|
];
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.waitlist.index')->layout('layouts.simple');
|
||||||
|
}
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
|
$this->waitingInLine = Waitlist::whereVerified(true)->count();
|
||||||
if (isDev()) {
|
if (isDev()) {
|
||||||
$this->email = 'waitlist@example.com';
|
$this->email = 'waitlist@example.com';
|
||||||
}
|
}
|
||||||
@ -29,7 +34,7 @@ public function submit()
|
|||||||
if ($already_registered) {
|
if ($already_registered) {
|
||||||
throw new \Exception('You are already on the waitlist or registered. <br>Please check your email to verify your email address or contact support.');
|
throw new \Exception('You are already on the waitlist or registered. <br>Please check your email to verify your email address or contact support.');
|
||||||
}
|
}
|
||||||
$found = ModelsWaitlist::where('email', $this->email)->first();
|
$found = Waitlist::where('email', $this->email)->first();
|
||||||
if ($found) {
|
if ($found) {
|
||||||
if (!$found->verified) {
|
if (!$found->verified) {
|
||||||
$this->emit('error', 'You are already on the waitlist. <br>Please check your email to verify your email address.');
|
$this->emit('error', 'You are already on the waitlist. <br>Please check your email to verify your email address.');
|
||||||
@ -38,7 +43,7 @@ public function submit()
|
|||||||
$this->emit('error', 'You are already on the waitlist. <br>You will be notified when your turn comes. <br>Thank you.');
|
$this->emit('error', 'You are already on the waitlist. <br>You will be notified when your turn comes. <br>Thank you.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$waitlist = ModelsWaitlist::create([
|
$waitlist = Waitlist::create([
|
||||||
'email' => $this->email,
|
'email' => $this->email,
|
||||||
'type' => 'registration',
|
'type' => 'registration',
|
||||||
]);
|
]);
|
@ -15,7 +15,7 @@ class IsBoardingFlow
|
|||||||
*/
|
*/
|
||||||
public function handle(Request $request, Closure $next): Response
|
public function handle(Request $request, Closure $next): Response
|
||||||
{
|
{
|
||||||
// ray('IsBoardingFlow Middleware');
|
ray()->showQueries()->color('orange');
|
||||||
if (showBoarding() && !in_array($request->path(), allowedPathsForBoardingAccounts())) {
|
if (showBoarding() && !in_array($request->path(), allowedPathsForBoardingAccounts())) {
|
||||||
return redirect('boarding');
|
return redirect('boarding');
|
||||||
}
|
}
|
||||||
|
@ -6,14 +6,14 @@
|
|||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
|
||||||
class SubscriptionValid
|
class IsSubscriptionValid
|
||||||
{
|
{
|
||||||
public function handle(Request $request, Closure $next): Response
|
public function handle(Request $request, Closure $next): Response
|
||||||
{
|
{
|
||||||
if (isInstanceAdmin()) {
|
if (isInstanceAdmin()) {
|
||||||
return $next($request);
|
return $next($request);
|
||||||
}
|
}
|
||||||
if (!auth()->user() || !is_cloud()) {
|
if (!auth()->user() || !isCloud()) {
|
||||||
if ($request->path() === 'subscription') {
|
if ($request->path() === 'subscription') {
|
||||||
return redirect('/');
|
return redirect('/');
|
||||||
} else {
|
} else {
|
@ -20,6 +20,7 @@
|
|||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
use Illuminate\Foundation\Bus\Dispatchable;
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
use Illuminate\Queue\InteractsWithQueue;
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||||
use Illuminate\Queue\SerializesModels;
|
use Illuminate\Queue\SerializesModels;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
@ -65,6 +66,12 @@ class ApplicationDeploymentJob implements ShouldQueue
|
|||||||
private $log_model;
|
private $log_model;
|
||||||
private Collection $saved_outputs;
|
private Collection $saved_outputs;
|
||||||
|
|
||||||
|
public function middleware(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
(new WithoutOverlapping("dockerimagejobs"))->shared(),
|
||||||
|
];
|
||||||
|
}
|
||||||
public function __construct(int $application_deployment_queue_id)
|
public function __construct(int $application_deployment_queue_id)
|
||||||
{
|
{
|
||||||
ray()->clearScreen();
|
ray()->clearScreen();
|
||||||
|
@ -37,7 +37,7 @@ public function handle(): void
|
|||||||
|
|
||||||
private function cleanup_waitlist()
|
private function cleanup_waitlist()
|
||||||
{
|
{
|
||||||
$waitlist = Waitlist::whereVerified(false)->where('created_at', '<', now()->subMinutes(config('constants.waitlist.confirmation_valid_for_minutes')))->get();
|
$waitlist = Waitlist::whereVerified(false)->where('created_at', '<', now()->subMinutes(config('constants.waitlist.expiration')))->get();
|
||||||
foreach ($waitlist as $item) {
|
foreach ($waitlist as $item) {
|
||||||
$item->delete();
|
$item->delete();
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
use Illuminate\Foundation\Bus\Dispatchable;
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
use Illuminate\Queue\InteractsWithQueue;
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||||
use Illuminate\Queue\SerializesModels;
|
use Illuminate\Queue\SerializesModels;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
@ -15,51 +16,69 @@ class DockerCleanupJob implements ShouldQueue
|
|||||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
public $timeout = 500;
|
public $timeout = 500;
|
||||||
|
public ?string $dockerRootFilesystem = null;
|
||||||
|
public ?int $usageBefore = null;
|
||||||
|
|
||||||
/**
|
public function middleware(): array
|
||||||
* Create a new job instance.
|
{
|
||||||
*/
|
return [
|
||||||
|
(new WithoutOverlapping("dockerimagejobs"))->shared(),
|
||||||
|
];
|
||||||
|
}
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
//
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Execute the job.
|
|
||||||
*/
|
|
||||||
public function handle(): void
|
public function handle(): void
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
|
ray()->showQueries()->color('orange');
|
||||||
$servers = Server::all();
|
$servers = Server::all();
|
||||||
foreach ($servers as $server) {
|
foreach ($servers as $server) {
|
||||||
if (isDev()) {
|
if (
|
||||||
$docker_root_filesystem = "/";
|
!$server->settings->is_reachable && !$server->settings->is_usable
|
||||||
} else {
|
) {
|
||||||
$docker_root_filesystem = instant_remote_process(['stat --printf=%m $(docker info --format "{{json .DockerRootDir}}" |sed \'s/"//g\')'], $server);
|
continue;
|
||||||
}
|
}
|
||||||
$disk_percentage_before = $this->get_disk_usage($server, $docker_root_filesystem);
|
if (isDev()) {
|
||||||
if ($disk_percentage_before >= $server->settings->cleanup_after_percentage) {
|
$this->dockerRootFilesystem = "/";
|
||||||
|
} else {
|
||||||
|
$this->dockerRootFilesystem = instant_remote_process(
|
||||||
|
[
|
||||||
|
"stat --printf=%m $(docker info --format '{{json .DockerRootDir}}'' |sed 's/\"//g')"
|
||||||
|
],
|
||||||
|
$server,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (!$this->dockerRootFilesystem) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$this->usageBefore = $this->getFilesystemUsage($server);
|
||||||
|
if ($this->usageBefore >= $server->settings->cleanup_after_percentage) {
|
||||||
|
ray('Cleaning up ' . $server->name)->color('orange');
|
||||||
instant_remote_process(['docker image prune -af'], $server);
|
instant_remote_process(['docker image prune -af'], $server);
|
||||||
instant_remote_process(['docker container prune -f --filter "label=coolify.managed=true"'], $server);
|
instant_remote_process(['docker container prune -f --filter "label=coolify.managed=true"'], $server);
|
||||||
instant_remote_process(['docker builder prune -af'], $server);
|
instant_remote_process(['docker builder prune -af'], $server);
|
||||||
$disk_percentage_after = $this->get_disk_usage($server, $docker_root_filesystem);
|
$usageAfter = $this->getFilesystemUsage($server);
|
||||||
if ($disk_percentage_after < $disk_percentage_before) {
|
if ($usageAfter < $this->usageBefore) {
|
||||||
ray('Saved ' . ($disk_percentage_before - $disk_percentage_after) . '% disk space on ' . $server->name);
|
ray('Saved ' . ($this->usageBefore - $usageAfter) . '% disk space on ' . $server->name)->color('orange');
|
||||||
|
send_internal_notification('DockerCleanupJob done: Saved ' . ($this->usageBefore - $usageAfter) . '% disk space on ' . $server->name);
|
||||||
|
} else {
|
||||||
|
ray('DockerCleanupJob failed to save disk space on ' . $server->name)->color('orange');
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
ray('No need to clean up ' . $server->name)->color('orange');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
send_internal_notification('DockerCleanupJob failed with: ' . $e->getMessage());
|
send_internal_notification('DockerCleanupJob failed with: ' . $e->getMessage());
|
||||||
ray($e->getMessage());
|
ray($e->getMessage())->color('orange');
|
||||||
throw $e;
|
throw $e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function get_disk_usage(Server $server, string $docker_root_filesystem)
|
private function getFilesystemUsage(Server $server)
|
||||||
{
|
{
|
||||||
$disk_usage = json_decode(instant_remote_process(['df -hP | awk \'BEGIN {printf"{\\"disks\\":["}{if($1=="Filesystem")next;if(a)printf",";printf"{\\"mount\\":\\""$6"\\",\\"size\\":\\""$2"\\",\\"used\\":\\""$3"\\",\\"avail\\":\\""$4"\\",\\"use%\\":\\""$5"\\"}";a++;}END{print"]}";}\''], $server), true);
|
return instant_remote_process(["df '{$this->dockerRootFilesystem}'| tail -1 | awk '{ print $5}' | sed 's/%//g'"], $server, false);
|
||||||
$mount_point = collect(data_get($disk_usage, 'disks'))->where('mount', $docker_root_filesystem)->first();
|
|
||||||
ray($mount_point);
|
|
||||||
return Str::of(data_get($mount_point, 'use%'))->trim()->replace('%', '')->value();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,9 +33,10 @@ public function handle()
|
|||||||
if ($status === 'running') {
|
if ($status === 'running') {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// $server->team->notify(new ProxyStoppedNotification($server));
|
if (data_get($server, 'proxy.type')) {
|
||||||
resolve(StartProxy::class)($server);
|
resolve(StartProxy::class)($server);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} catch (\Throwable $th) {
|
} catch (\Throwable $th) {
|
||||||
ray($th->getMessage());
|
ray($th->getMessage());
|
||||||
send_internal_notification('ProxyCheckJob failed with: ' . $th->getMessage());
|
send_internal_notification('ProxyCheckJob failed with: ' . $th->getMessage());
|
||||||
|
@ -39,9 +39,9 @@ public function uniqueId(): int
|
|||||||
public function handle(): void
|
public function handle(): void
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$container = getContainerStatus(server: $this->server, all_data: true, container_id: 'coolify-proxy', throwError: true);
|
$container = getContainerStatus(server: $this->server, all_data: true, container_id: 'coolify-proxy', throwError: false);
|
||||||
$status = data_get($container, 'State.Status');
|
$status = data_get($container, 'State.Status');
|
||||||
if (data_get($this->server,'proxy.status') !== $status) {
|
if ($status && data_get($this->server, 'proxy.status') !== $status) {
|
||||||
$this->server->proxy->status = $status;
|
$this->server->proxy->status = $status;
|
||||||
if ($this->server->proxy->status === 'running') {
|
if ($this->server->proxy->status === 'running') {
|
||||||
$traefik = $container['Config']['Labels']['org.opencontainers.image.title'];
|
$traefik = $container['Config']['Labels']['org.opencontainers.image.title'];
|
||||||
|
@ -3,6 +3,8 @@
|
|||||||
namespace App\Jobs;
|
namespace App\Jobs;
|
||||||
|
|
||||||
use App\Actions\Proxy\StartProxy;
|
use App\Actions\Proxy\StartProxy;
|
||||||
|
use App\Enums\ProxyStatus;
|
||||||
|
use App\Enums\ProxyTypes;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use Illuminate\Bus\Queueable;
|
use Illuminate\Bus\Queueable;
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
@ -27,6 +29,11 @@ public function handle()
|
|||||||
if ($status === 'running') {
|
if ($status === 'running') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (is_null(data_get($this->server, 'proxy.type'))) {
|
||||||
|
$this->server->proxy->type = ProxyTypes::TRAEFIK_V2->value;
|
||||||
|
$this->server->proxy->status = ProxyStatus::EXITED->value;
|
||||||
|
$this->server->save();
|
||||||
|
}
|
||||||
resolve(StartProxy::class)($this->server);
|
resolve(StartProxy::class)($this->server);
|
||||||
} catch (\Throwable $th) {
|
} catch (\Throwable $th) {
|
||||||
send_internal_notification('ProxyStartJob failed with: ' . $th->getMessage());
|
send_internal_notification('ProxyStartJob failed with: ' . $th->getMessage());
|
||||||
|
@ -37,7 +37,7 @@ public function handle()
|
|||||||
$mail->subject('You are on the waitlist!');
|
$mail->subject('You are on the waitlist!');
|
||||||
send_user_an_email($mail, $this->email);
|
send_user_an_email($mail, $this->email);
|
||||||
} catch (\Throwable $th) {
|
} catch (\Throwable $th) {
|
||||||
send_internal_notification('SendConfirmationForWaitlistJob failed with error: ' . $th->getMessage());
|
send_internal_notification("SendConfirmationForWaitlistJob failed for {$mail} with error: " . $th->getMessage());
|
||||||
ray($th->getMessage());
|
ray($th->getMessage());
|
||||||
throw $th;
|
throw $th;
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,6 @@
|
|||||||
use Illuminate\Notifications\Messages\MailMessage;
|
use Illuminate\Notifications\Messages\MailMessage;
|
||||||
use Illuminate\Queue\InteractsWithQueue;
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
use Illuminate\Queue\SerializesModels;
|
use Illuminate\Queue\SerializesModels;
|
||||||
use Stripe\Stripe;
|
|
||||||
|
|
||||||
class SubscriptionInvoiceFailedJob implements ShouldQueue
|
class SubscriptionInvoiceFailedJob implements ShouldQueue
|
||||||
{
|
{
|
||||||
|
@ -13,6 +13,7 @@ class InstanceSettings extends Model implements SendsEmail
|
|||||||
protected $guarded = [];
|
protected $guarded = [];
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
'resale_license' => 'encrypted',
|
'resale_license' => 'encrypted',
|
||||||
|
'smtp_password' => 'encrypted',
|
||||||
];
|
];
|
||||||
|
|
||||||
public static function get()
|
public static function get()
|
||||||
|
@ -33,6 +33,9 @@ protected static function booted()
|
|||||||
|
|
||||||
});
|
});
|
||||||
static::deleting(function ($server) {
|
static::deleting(function ($server) {
|
||||||
|
$server->destinations()->each(function ($destination) {
|
||||||
|
$destination->delete();
|
||||||
|
});
|
||||||
$server->settings()->delete();
|
$server->settings()->delete();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -70,8 +73,6 @@ static public function destinationsByServer(string $server_id)
|
|||||||
return $standaloneDocker->concat($swarmDocker);
|
return $standaloneDocker->concat($swarmDocker);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public function settings()
|
public function settings()
|
||||||
{
|
{
|
||||||
return $this->hasOne(ServerSetting::class);
|
return $this->hasOne(ServerSetting::class);
|
||||||
@ -84,12 +85,20 @@ public function scopeWithProxy(): Builder
|
|||||||
|
|
||||||
public function isEmpty()
|
public function isEmpty()
|
||||||
{
|
{
|
||||||
if ($this->applications()->count() === 0) {
|
$applications = $this->applications()->count() === 0;
|
||||||
|
$databases = $this->databases()->count() === 0;
|
||||||
|
if ($applications && $databases) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function databases() {
|
||||||
|
return $this->destinations()->map(function ($standaloneDocker) {
|
||||||
|
$postgresqls = $standaloneDocker->postgresqls;
|
||||||
|
return $postgresqls?->concat([]) ?? collect([]);
|
||||||
|
})->flatten();
|
||||||
|
}
|
||||||
public function applications()
|
public function applications()
|
||||||
{
|
{
|
||||||
return $this->destinations()->map(function ($standaloneDocker) {
|
return $this->destinations()->map(function ($standaloneDocker) {
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
class Subscription extends Model
|
class Subscription extends Model
|
||||||
{
|
{
|
||||||
@ -14,6 +15,7 @@ public function team()
|
|||||||
}
|
}
|
||||||
public function type()
|
public function type()
|
||||||
{
|
{
|
||||||
|
if (isLemon()) {
|
||||||
$basic = explode(',', config('subscription.lemon_squeezy_basic_plan_ids'));
|
$basic = explode(',', config('subscription.lemon_squeezy_basic_plan_ids'));
|
||||||
$pro = explode(',', config('subscription.lemon_squeezy_pro_plan_ids'));
|
$pro = explode(',', config('subscription.lemon_squeezy_pro_plan_ids'));
|
||||||
$ultimate = explode(',', config('subscription.lemon_squeezy_ultimate_plan_ids'));
|
$ultimate = explode(',', config('subscription.lemon_squeezy_ultimate_plan_ids'));
|
||||||
@ -28,6 +30,30 @@ public function type()
|
|||||||
if (in_array($subscription, $ultimate)) {
|
if (in_array($subscription, $ultimate)) {
|
||||||
return 'ultimate';
|
return 'ultimate';
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if (isStripe()) {
|
||||||
|
if (!$this->stripe_plan_id) {
|
||||||
|
return 'unknown';
|
||||||
|
}
|
||||||
|
$subscription = Subscription::where('id', $this->id)->first();
|
||||||
|
if (!$subscription) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
$subscriptionPlanId = data_get($subscription,'stripe_plan_id');
|
||||||
|
if (!$subscriptionPlanId) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
$subscriptionConfigs = collect(config('subscription'));
|
||||||
|
$stripePlanId = null;
|
||||||
|
$subscriptionConfigs->map(function ($value, $key) use ($subscriptionPlanId, &$stripePlanId) {
|
||||||
|
if ($value === $subscriptionPlanId){
|
||||||
|
$stripePlanId = $key;
|
||||||
|
};
|
||||||
|
})->first();
|
||||||
|
if ($stripePlanId) {
|
||||||
|
return Str::of($stripePlanId)->after('stripe_price_id_')->before('_')->lower();
|
||||||
|
}
|
||||||
|
}
|
||||||
return 'unknown';
|
return 'unknown';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
use App\Notifications\Channels\SendsDiscord;
|
use App\Notifications\Channels\SendsDiscord;
|
||||||
use App\Notifications\Channels\SendsEmail;
|
use App\Notifications\Channels\SendsEmail;
|
||||||
|
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Notifications\Notifiable;
|
use Illuminate\Notifications\Notifiable;
|
||||||
|
|
||||||
@ -14,6 +15,8 @@ class Team extends Model implements SendsDiscord, SendsEmail
|
|||||||
protected $guarded = [];
|
protected $guarded = [];
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
'personal_team' => 'boolean',
|
'personal_team' => 'boolean',
|
||||||
|
'smtp_password' => 'encrypted',
|
||||||
|
'resend_api_key' => 'encrypted',
|
||||||
];
|
];
|
||||||
|
|
||||||
public function routeNotificationForDiscord()
|
public function routeNotificationForDiscord()
|
||||||
@ -30,6 +33,27 @@ public function getRecepients($notification)
|
|||||||
}
|
}
|
||||||
return explode(',', $recipients);
|
return explode(',', $recipients);
|
||||||
}
|
}
|
||||||
|
public function limits(): Attribute
|
||||||
|
{
|
||||||
|
return Attribute::make(
|
||||||
|
get: function () {
|
||||||
|
if (config('coolify.self_hosted') || $this->id === 0) {
|
||||||
|
$subscription = 'self-hosted';
|
||||||
|
} else {
|
||||||
|
$subscription = data_get($this, 'subscription');
|
||||||
|
if (is_null($subscription)) {
|
||||||
|
$subscription = 'zero';
|
||||||
|
} else {
|
||||||
|
$subscription = $subscription->type();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$serverLimit = config('constants.limits.server')[strtolower($subscription)];
|
||||||
|
$sharedEmailEnabled = config('constants.limits.email')[strtolower($subscription)];
|
||||||
|
return ['serverLimit' => $serverLimit, 'sharedEmailEnabled' => $sharedEmailEnabled];
|
||||||
|
}
|
||||||
|
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public function members()
|
public function members()
|
||||||
{
|
{
|
||||||
|
@ -32,6 +32,7 @@ protected static function boot()
|
|||||||
$team = [
|
$team = [
|
||||||
'name' => $user->name . "'s Team",
|
'name' => $user->name . "'s Team",
|
||||||
'personal_team' => true,
|
'personal_team' => true,
|
||||||
|
'show_boarding' => true
|
||||||
];
|
];
|
||||||
if ($user->id === 0) {
|
if ($user->id === 0) {
|
||||||
$team['id'] = 0;
|
$team['id'] = 0;
|
||||||
@ -91,29 +92,20 @@ public function isInstanceAdmin()
|
|||||||
return $found_root_team->count() > 0;
|
return $found_root_team->count() > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function personalTeam()
|
|
||||||
{
|
|
||||||
return $this->teams()->where('personal_team', true)->first();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function currentTeam()
|
public function currentTeam()
|
||||||
{
|
{
|
||||||
return $this->teams()->where('team_id', session('currentTeam')->id)->first();
|
return Team::find(session('currentTeam')->id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function otherTeams()
|
public function otherTeams()
|
||||||
{
|
{
|
||||||
$team_id = currentTeam()->id;
|
return auth()->user()->teams->filter(function ($team) {
|
||||||
return auth()->user()->teams->filter(function ($team) use ($team_id) {
|
return $team->id != currentTeam()->id;
|
||||||
return $team->id != $team_id;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function role()
|
public function role()
|
||||||
{
|
{
|
||||||
if ($this->teams()->where('team_id', 0)->first()) {
|
return session('currentTeam')->pivot->role;
|
||||||
return 'admin';
|
|
||||||
}
|
|
||||||
return $this->teams()->where('team_id', currentTeam()->id)->first()->pivot->role;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -44,7 +44,7 @@ public function __construct(Application $application, string $deployment_uuid, A
|
|||||||
public function via(object $notifiable): array
|
public function via(object $notifiable): array
|
||||||
{
|
{
|
||||||
$channels = [];
|
$channels = [];
|
||||||
$isEmailEnabled = data_get($notifiable, 'smtp_enabled');
|
$isEmailEnabled = isEmailEnabled($notifiable);
|
||||||
$isDiscordEnabled = data_get($notifiable, 'discord_enabled');
|
$isDiscordEnabled = data_get($notifiable, 'discord_enabled');
|
||||||
$isSubscribedToEmailEvent = data_get($notifiable, 'smtp_notifications_deployments');
|
$isSubscribedToEmailEvent = data_get($notifiable, 'smtp_notifications_deployments');
|
||||||
$isSubscribedToDiscordEvent = data_get($notifiable, 'discord_notifications_deployments');
|
$isSubscribedToDiscordEvent = data_get($notifiable, 'discord_notifications_deployments');
|
||||||
|
@ -6,10 +6,10 @@
|
|||||||
use Illuminate\Mail\Message;
|
use Illuminate\Mail\Message;
|
||||||
use Illuminate\Notifications\Notification;
|
use Illuminate\Notifications\Notification;
|
||||||
use Illuminate\Support\Facades\Mail;
|
use Illuminate\Support\Facades\Mail;
|
||||||
use Illuminate\Support\Str;
|
|
||||||
|
|
||||||
class EmailChannel
|
class EmailChannel
|
||||||
{
|
{
|
||||||
|
private bool $isResend = false;
|
||||||
public function send(SendsEmail $notifiable, Notification $notification): void
|
public function send(SendsEmail $notifiable, Notification $notification): void
|
||||||
{
|
{
|
||||||
$this->bootConfigs($notifiable);
|
$this->bootConfigs($notifiable);
|
||||||
@ -20,6 +20,22 @@ public function send(SendsEmail $notifiable, Notification $notification): void
|
|||||||
}
|
}
|
||||||
|
|
||||||
$mailMessage = $notification->toMail($notifiable);
|
$mailMessage = $notification->toMail($notifiable);
|
||||||
|
if ($this->isResend) {
|
||||||
|
foreach ($recepients as $receipient) {
|
||||||
|
Mail::send(
|
||||||
|
[],
|
||||||
|
[],
|
||||||
|
fn (Message $message) => $message
|
||||||
|
->from(
|
||||||
|
data_get($notifiable, 'smtp_from_address'),
|
||||||
|
data_get($notifiable, 'smtp_from_name'),
|
||||||
|
)
|
||||||
|
->to($receipient)
|
||||||
|
->subject($mailMessage->subject)
|
||||||
|
->html((string)$mailMessage->render())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
Mail::send(
|
Mail::send(
|
||||||
[],
|
[],
|
||||||
[],
|
[],
|
||||||
@ -33,16 +49,26 @@ public function send(SendsEmail $notifiable, Notification $notification): void
|
|||||||
->html((string)$mailMessage->render())
|
->html((string)$mailMessage->render())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private function bootConfigs($notifiable): void
|
private function bootConfigs($notifiable): void
|
||||||
{
|
{
|
||||||
$password = data_get($notifiable, 'smtp_password');
|
if (data_get($notifiable, 'use_instance_email_settings')) {
|
||||||
if ($password) $password = decrypt($password);
|
$type = set_transanctional_email_settings();
|
||||||
|
if (!$type) {
|
||||||
if (Str::contains(data_get($notifiable, 'smtp_host'),'resend.com')) {
|
throw new Exception('No email settings found.');
|
||||||
|
}
|
||||||
|
if ($type === 'resend') {
|
||||||
|
$this->isResend = true;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (data_get($notifiable, 'resend_enabled')) {
|
||||||
|
$this->isResend = true;
|
||||||
config()->set('mail.default', 'resend');
|
config()->set('mail.default', 'resend');
|
||||||
config()->set('resend.api_key', $password);
|
config()->set('resend.api_key', data_get($notifiable, 'resend_api_key'));
|
||||||
} else {
|
}
|
||||||
|
if (data_get($notifiable, 'smtp_enabled')) {
|
||||||
config()->set('mail.default', 'smtp');
|
config()->set('mail.default', 'smtp');
|
||||||
config()->set('mail.mailers.smtp', [
|
config()->set('mail.mailers.smtp', [
|
||||||
"transport" => "smtp",
|
"transport" => "smtp",
|
||||||
@ -50,7 +76,7 @@ private function bootConfigs($notifiable): void
|
|||||||
"port" => data_get($notifiable, 'smtp_port'),
|
"port" => data_get($notifiable, 'smtp_port'),
|
||||||
"encryption" => data_get($notifiable, 'smtp_encryption'),
|
"encryption" => data_get($notifiable, 'smtp_encryption'),
|
||||||
"username" => data_get($notifiable, 'smtp_username'),
|
"username" => data_get($notifiable, 'smtp_username'),
|
||||||
"password" => $password,
|
"password" => data_get($notifiable, 'smtp_password'),
|
||||||
"timeout" => data_get($notifiable, 'smtp_timeout'),
|
"timeout" => data_get($notifiable, 'smtp_timeout'),
|
||||||
"local_domain" => null,
|
"local_domain" => null,
|
||||||
]);
|
]);
|
||||||
|
@ -4,16 +4,20 @@
|
|||||||
|
|
||||||
use App\Models\InstanceSettings;
|
use App\Models\InstanceSettings;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
|
use Exception;
|
||||||
use Illuminate\Mail\Message;
|
use Illuminate\Mail\Message;
|
||||||
use Illuminate\Notifications\Notification;
|
use Illuminate\Notifications\Notification;
|
||||||
use Illuminate\Support\Facades\Mail;
|
use Illuminate\Support\Facades\Mail;
|
||||||
|
use Log;
|
||||||
|
|
||||||
class TransactionalEmailChannel
|
class TransactionalEmailChannel
|
||||||
{
|
{
|
||||||
|
private bool $isResend = false;
|
||||||
public function send(User $notifiable, Notification $notification): void
|
public function send(User $notifiable, Notification $notification): void
|
||||||
{
|
{
|
||||||
$settings = InstanceSettings::get();
|
$settings = InstanceSettings::get();
|
||||||
if (data_get($settings, 'smtp_enabled') !== true) {
|
if (!data_get($settings, 'smtp_enabled') && !data_get($settings, 'resend_enabled')) {
|
||||||
|
Log::info('SMTP/Resend not enabled');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$email = $notifiable->email;
|
$email = $notifiable->email;
|
||||||
@ -22,22 +26,43 @@ public function send(User $notifiable, Notification $notification): void
|
|||||||
}
|
}
|
||||||
$this->bootConfigs();
|
$this->bootConfigs();
|
||||||
$mailMessage = $notification->toMail($notifiable);
|
$mailMessage = $notification->toMail($notifiable);
|
||||||
|
if ($this->isResend) {
|
||||||
Mail::send(
|
Mail::send(
|
||||||
[],
|
[],
|
||||||
[],
|
[],
|
||||||
fn (Message $message) => $message
|
fn (Message $message) => $message
|
||||||
->from(
|
->from(
|
||||||
data_get($settings, 'smtp_from_address'),
|
data_get($settings, 'smtp_from_address'),
|
||||||
data_get($settings, 'smtp_from_name')
|
data_get($settings, 'smtp_from_name'),
|
||||||
)
|
)
|
||||||
->to($email)
|
->to($email)
|
||||||
->subject($mailMessage->subject)
|
->subject($mailMessage->subject)
|
||||||
->html((string)$mailMessage->render())
|
->html((string)$mailMessage->render())
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
Mail::send(
|
||||||
|
[],
|
||||||
|
[],
|
||||||
|
fn (Message $message) => $message
|
||||||
|
->from(
|
||||||
|
data_get($settings, 'smtp_from_address'),
|
||||||
|
data_get($settings, 'smtp_from_name'),
|
||||||
|
)
|
||||||
|
->bcc($email)
|
||||||
|
->subject($mailMessage->subject)
|
||||||
|
->html((string)$mailMessage->render())
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function bootConfigs(): void
|
private function bootConfigs(): void
|
||||||
{
|
{
|
||||||
set_transanctional_email_settings();
|
$type = set_transanctional_email_settings();
|
||||||
|
if (!$type) {
|
||||||
|
throw new Exception('No email settings found.');
|
||||||
|
}
|
||||||
|
if ($type === 'resend') {
|
||||||
|
$this->isResend = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,7 @@ public function __construct(ScheduledDatabaseBackup $backup, public $database, p
|
|||||||
public function via(object $notifiable): array
|
public function via(object $notifiable): array
|
||||||
{
|
{
|
||||||
$channels = [];
|
$channels = [];
|
||||||
$isEmailEnabled = data_get($notifiable, 'smtp_enabled');
|
$isEmailEnabled = isEmailEnabled($notifiable);
|
||||||
$isDiscordEnabled = data_get($notifiable, 'discord_enabled');
|
$isDiscordEnabled = data_get($notifiable, 'discord_enabled');
|
||||||
$isSubscribedToEmailEvent = data_get($notifiable, 'smtp_notifications_database_backups');
|
$isSubscribedToEmailEvent = data_get($notifiable, 'smtp_notifications_database_backups');
|
||||||
$isSubscribedToDiscordEvent = data_get($notifiable, 'discord_notifications_database_backups');
|
$isSubscribedToDiscordEvent = data_get($notifiable, 'discord_notifications_database_backups');
|
||||||
|
@ -25,7 +25,7 @@ public function __construct(ScheduledDatabaseBackup $backup, public $database)
|
|||||||
public function via(object $notifiable): array
|
public function via(object $notifiable): array
|
||||||
{
|
{
|
||||||
$channels = [];
|
$channels = [];
|
||||||
$isEmailEnabled = data_get($notifiable, 'smtp_enabled');
|
$isEmailEnabled = isEmailEnabled($notifiable);
|
||||||
$isDiscordEnabled = data_get($notifiable, 'discord_enabled');
|
$isDiscordEnabled = data_get($notifiable, 'discord_enabled');
|
||||||
$isSubscribedToEmailEvent = data_get($notifiable, 'smtp_notifications_database_backups');
|
$isSubscribedToEmailEvent = data_get($notifiable, 'smtp_notifications_database_backups');
|
||||||
$isSubscribedToDiscordEvent = data_get($notifiable, 'discord_notifications_database_backups');
|
$isSubscribedToDiscordEvent = data_get($notifiable, 'discord_notifications_database_backups');
|
||||||
|
@ -23,7 +23,7 @@ public function __construct(public Server $server)
|
|||||||
public function via(object $notifiable): array
|
public function via(object $notifiable): array
|
||||||
{
|
{
|
||||||
$channels = [];
|
$channels = [];
|
||||||
$isEmailEnabled = data_get($notifiable, 'smtp_enabled');
|
$isEmailEnabled = isEmailEnabled($notifiable);
|
||||||
$isDiscordEnabled = data_get($notifiable, 'discord_enabled');
|
$isDiscordEnabled = data_get($notifiable, 'discord_enabled');
|
||||||
$isSubscribedToEmailEvent = data_get($notifiable, 'smtp_notifications_status_changes');
|
$isSubscribedToEmailEvent = data_get($notifiable, 'smtp_notifications_status_changes');
|
||||||
$isSubscribedToDiscordEvent = data_get($notifiable, 'discord_notifications_status_changes');
|
$isSubscribedToDiscordEvent = data_get($notifiable, 'discord_notifications_status_changes');
|
||||||
|
@ -20,7 +20,7 @@ public function __construct(public string|null $emails = null)
|
|||||||
public function via(object $notifiable): array
|
public function via(object $notifiable): array
|
||||||
{
|
{
|
||||||
$channels = [];
|
$channels = [];
|
||||||
$isEmailEnabled = data_get($notifiable, 'smtp_enabled');
|
$isEmailEnabled = isEmailEnabled($notifiable);
|
||||||
$isDiscordEnabled = data_get($notifiable, 'discord_enabled');
|
$isDiscordEnabled = data_get($notifiable, 'discord_enabled');
|
||||||
|
|
||||||
if ($isDiscordEnabled && empty($this->emails)) {
|
if ($isDiscordEnabled && empty($this->emails)) {
|
||||||
|
@ -31,24 +31,11 @@ public static function toMailUsing($callback)
|
|||||||
|
|
||||||
public function via($notifiable)
|
public function via($notifiable)
|
||||||
{
|
{
|
||||||
if ($this->settings->smtp_enabled) {
|
$type = set_transanctional_email_settings();
|
||||||
$password = data_get($this->settings, 'smtp_password');
|
if (!$type) {
|
||||||
if ($password) $password = decrypt($password);
|
throw new \Exception('No email settings found.');
|
||||||
|
|
||||||
config()->set('mail.default', 'smtp');
|
|
||||||
config()->set('mail.mailers.smtp', [
|
|
||||||
"transport" => "smtp",
|
|
||||||
"host" => data_get($this->settings, 'smtp_host'),
|
|
||||||
"port" => data_get($this->settings, 'smtp_port'),
|
|
||||||
"encryption" => data_get($this->settings, 'smtp_encryption'),
|
|
||||||
"username" => data_get($this->settings, 'smtp_username'),
|
|
||||||
"password" => $password,
|
|
||||||
"timeout" => data_get($this->settings, 'smtp_timeout'),
|
|
||||||
"local_domain" => null,
|
|
||||||
]);
|
|
||||||
return ['mail'];
|
|
||||||
}
|
}
|
||||||
throw new \Exception('SMTP is not enabled');
|
return ['mail'];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function toMail($notifiable)
|
public function toMail($notifiable)
|
||||||
|
66
app/Policies/ServerPolicy.php
Normal file
66
app/Policies/ServerPolicy.php
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Policies;
|
||||||
|
|
||||||
|
use App\Models\Server;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Auth\Access\Response;
|
||||||
|
|
||||||
|
class ServerPolicy
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Determine whether the user can view any models.
|
||||||
|
*/
|
||||||
|
public function viewAny(User $user): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether the user can view the model.
|
||||||
|
*/
|
||||||
|
public function view(User $user, Server $server): bool
|
||||||
|
{
|
||||||
|
return $user->teams()->get()->firstWhere('id', $server->team_id) !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether the user can create models.
|
||||||
|
*/
|
||||||
|
public function create(User $user): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether the user can update the model.
|
||||||
|
*/
|
||||||
|
public function update(User $user, Server $server): bool
|
||||||
|
{
|
||||||
|
return $user->teams()->get()->firstWhere('id', $server->team_id) !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether the user can delete the model.
|
||||||
|
*/
|
||||||
|
public function delete(User $user, Server $server): bool
|
||||||
|
{
|
||||||
|
return $user->teams()->get()->firstWhere('id', $server->team_id) !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether the user can restore the model.
|
||||||
|
*/
|
||||||
|
public function restore(User $user, Server $server): bool
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether the user can permanently delete the model.
|
||||||
|
*/
|
||||||
|
public function forceDelete(User $user, Server $server): bool
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
@ -43,22 +43,16 @@ public function toResponse($request)
|
|||||||
*/
|
*/
|
||||||
public function boot(): void
|
public function boot(): void
|
||||||
{
|
{
|
||||||
|
|
||||||
Fortify::createUsersUsing(CreateNewUser::class);
|
Fortify::createUsersUsing(CreateNewUser::class);
|
||||||
Fortify::registerView(function () {
|
Fortify::registerView(function () {
|
||||||
$settings = InstanceSettings::get();
|
$settings = InstanceSettings::get();
|
||||||
$waiting_in_line = Waitlist::whereVerified(true)->count();
|
|
||||||
if (!$settings->is_registration_enabled) {
|
if (!$settings->is_registration_enabled) {
|
||||||
return redirect()->route('login');
|
return redirect()->route('login');
|
||||||
}
|
}
|
||||||
if (config('coolify.waitlist')) {
|
if (config('coolify.waitlist')) {
|
||||||
return view('auth.waitlist',[
|
return redirect()->route('waitlist.index');
|
||||||
'waiting_in_line' => $waiting_in_line,
|
|
||||||
]);
|
|
||||||
} else {
|
} else {
|
||||||
return view('auth.register',[
|
return view('auth.register');
|
||||||
'waiting_in_line' => $waiting_in_line,
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -41,6 +41,7 @@ public function execute_remote_command(...$commands)
|
|||||||
|
|
||||||
$remote_command = generate_ssh_command($private_key_location, $ip, $user, $port, $command);
|
$remote_command = generate_ssh_command($private_key_location, $ip, $user, $port, $command);
|
||||||
$process = Process::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 = [
|
$new_log_entry = [
|
||||||
'command' => $command,
|
'command' => $command,
|
||||||
'output' => $output,
|
'output' => $output,
|
||||||
|
25
app/Traits/SaveFromRedirect.php
Normal file
25
app/Traits/SaveFromRedirect.php
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Traits;
|
||||||
|
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
|
||||||
|
trait SaveFromRedirect
|
||||||
|
{
|
||||||
|
public function saveFromRedirect(string $route, ?Collection $parameters = null)
|
||||||
|
{
|
||||||
|
session()->forget('from');
|
||||||
|
if (!$parameters || $parameters->count() === 0) {
|
||||||
|
$parameters = $this->parameters;
|
||||||
|
}
|
||||||
|
$parameters = collect($parameters) ?? collect([]);
|
||||||
|
$queries = collect($this->query) ?? collect([]);
|
||||||
|
$parameters = $parameters->merge($queries);
|
||||||
|
session(['from' => [
|
||||||
|
'back' => $this->currentRoute,
|
||||||
|
'route' => $route,
|
||||||
|
'parameters' => $parameters
|
||||||
|
]]);
|
||||||
|
return redirect()->route($route);
|
||||||
|
}
|
||||||
|
}
|
@ -15,7 +15,7 @@ public function __construct(
|
|||||||
public bool $disabled = false,
|
public bool $disabled = false,
|
||||||
public bool $isModal = false,
|
public bool $isModal = false,
|
||||||
public bool $noStyle = false,
|
public bool $noStyle = false,
|
||||||
public string|null $modalId = null,
|
public ?string $modalId = null,
|
||||||
public string $defaultClass = "btn btn-primary btn-sm font-normal text-white normal-case no-animation rounded border-none"
|
public string $defaultClass = "btn btn-primary btn-sm font-normal text-white normal-case no-animation rounded border-none"
|
||||||
) {
|
) {
|
||||||
if ($this->noStyle) {
|
if ($this->noStyle) {
|
||||||
@ -23,9 +23,6 @@ public function __construct(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the view / contents that represent the component.
|
|
||||||
*/
|
|
||||||
public function render(): View|Closure|string
|
public function render(): View|Closure|string
|
||||||
{
|
{
|
||||||
return view('components.forms.button');
|
return view('components.forms.button');
|
||||||
|
@ -17,7 +17,7 @@ public function __construct(
|
|||||||
public string|null $value = null,
|
public string|null $value = null,
|
||||||
public string|null $label = null,
|
public string|null $label = null,
|
||||||
public string|null $helper = null,
|
public string|null $helper = null,
|
||||||
public bool $instantSave = false,
|
public string|bool $instantSave = false,
|
||||||
public bool $disabled = false,
|
public bool $disabled = false,
|
||||||
public string $defaultClass = "toggle toggle-xs toggle-warning rounded disabled:bg-coolgray-200 disabled:opacity-50 placeholder:text-neutral-700"
|
public string $defaultClass = "toggle toggle-xs toggle-warning rounded disabled:bg-coolgray-200 disabled:opacity-50 placeholder:text-neutral-700"
|
||||||
) {
|
) {
|
||||||
|
@ -58,9 +58,11 @@ function format_docker_envs_to_json($rawOutput)
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getApplicationContainerStatus(Application $application) {
|
function getApplicationContainerStatus(Application $application) {
|
||||||
$server = $application->destination->server;
|
$server = data_get($application,'destination.server');
|
||||||
$id = $application->id;
|
$id = $application->id;
|
||||||
|
if (!$server) {
|
||||||
|
return 'exited';
|
||||||
|
}
|
||||||
$containers = getCurrentApplicationContainerStatus($server, $id);
|
$containers = getCurrentApplicationContainerStatus($server, $id);
|
||||||
if ($containers->count() > 0) {
|
if ($containers->count() > 0) {
|
||||||
$status = data_get($containers[0], 'State', 'exited');
|
$status = data_get($containers[0], 'State', 'exited');
|
||||||
|
@ -16,11 +16,6 @@
|
|||||||
use Illuminate\Support\Sleep;
|
use Illuminate\Support\Sleep;
|
||||||
use Spatie\Activitylog\Models\Activity;
|
use Spatie\Activitylog\Models\Activity;
|
||||||
|
|
||||||
/**
|
|
||||||
* Run a Remote Process, which SSH's asynchronously into a machine to run the command(s).
|
|
||||||
* @TODO Change 'root' to 'coolify' when it's able to run Docker commands without sudo
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
function remote_process(
|
function remote_process(
|
||||||
array $command,
|
array $command,
|
||||||
Server $server,
|
Server $server,
|
||||||
@ -167,17 +162,23 @@ function validateServer(Server $server)
|
|||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
refresh_server_connection($server->privateKey);
|
refresh_server_connection($server->privateKey);
|
||||||
$uptime = instant_remote_process(['uptime'], $server);
|
$uptime = instant_remote_process(['uptime'], $server, false);
|
||||||
if (!$uptime) {
|
if (!$uptime) {
|
||||||
$uptime = 'Server not reachable.';
|
$server->settings->is_reachable = false;
|
||||||
throw new \Exception('Server not reachable.');
|
return [
|
||||||
|
"uptime" => null,
|
||||||
|
"dockerVersion" => null,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
$server->settings->is_reachable = true;
|
$server->settings->is_reachable = true;
|
||||||
|
|
||||||
$dockerVersion = instant_remote_process(['docker version|head -2|grep -i version'], $server, false);
|
$dockerVersion = instant_remote_process(['docker version|head -2|grep -i version'], $server, false);
|
||||||
if (!$dockerVersion) {
|
if (!$dockerVersion) {
|
||||||
$dockerVersion = 'Not installed.';
|
$dockerVersion = null;
|
||||||
throw new \Exception('Docker not installed.');
|
return [
|
||||||
|
"uptime" => $uptime,
|
||||||
|
"dockerVersion" => null,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
$server->settings->is_usable = true;
|
$server->settings->is_usable = true;
|
||||||
return [
|
return [
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
use Illuminate\Database\QueryException;
|
use Illuminate\Database\QueryException;
|
||||||
use Illuminate\Mail\Message;
|
use Illuminate\Mail\Message;
|
||||||
use Illuminate\Notifications\Messages\MailMessage;
|
use Illuminate\Notifications\Messages\MailMessage;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
use Illuminate\Support\Facades\Http;
|
use Illuminate\Support\Facades\Http;
|
||||||
use Illuminate\Support\Facades\Mail;
|
use Illuminate\Support\Facades\Mail;
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
@ -52,12 +53,13 @@ function showBoarding(): bool
|
|||||||
}
|
}
|
||||||
function refreshSession(): void
|
function refreshSession(): void
|
||||||
{
|
{
|
||||||
$team = currentTeam();
|
$team = Team::find(currentTeam()->id);
|
||||||
session(['currentTeam' => $team]);
|
session(['currentTeam' => $team]);
|
||||||
}
|
}
|
||||||
function general_error_handler(Throwable | null $err = null, $that = null, $isJson = false, $customErrorMessage = null): mixed
|
function general_error_handler(Throwable | null $err = null, $that = null, $isJson = false, $customErrorMessage = null): mixed
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
|
ray($err);
|
||||||
ray('ERROR OCCURRED: ' . $err->getMessage());
|
ray('ERROR OCCURRED: ' . $err->getMessage());
|
||||||
if ($err instanceof QueryException) {
|
if ($err instanceof QueryException) {
|
||||||
if ($err->errorInfo[0] === '23505') {
|
if ($err->errorInfo[0] === '23505') {
|
||||||
@ -70,6 +72,9 @@ function general_error_handler(Throwable | null $err = null, $that = null, $isJs
|
|||||||
} elseif ($err instanceof TooManyRequestsException) {
|
} elseif ($err instanceof TooManyRequestsException) {
|
||||||
throw new Exception($customErrorMessage ?? "Too many requests. Please try again in {$err->secondsUntilAvailable} seconds.");
|
throw new Exception($customErrorMessage ?? "Too many requests. Please try again in {$err->secondsUntilAvailable} seconds.");
|
||||||
} else {
|
} else {
|
||||||
|
if ($err->getMessage() === 'This action is unauthorized.') {
|
||||||
|
return redirect()->route('dashboard')->with('error', $customErrorMessage ?? $err->getMessage());
|
||||||
|
}
|
||||||
throw new Exception($customErrorMessage ?? $err->getMessage());
|
throw new Exception($customErrorMessage ?? $err->getMessage());
|
||||||
}
|
}
|
||||||
} catch (Throwable $error) {
|
} catch (Throwable $error) {
|
||||||
@ -120,7 +125,8 @@ function generateSSHKey()
|
|||||||
'public' => $key->getPublicKey()->toString('OpenSSH', ['comment' => 'coolify-generated-ssh-key'])
|
'public' => $key->getPublicKey()->toString('OpenSSH', ['comment' => 'coolify-generated-ssh-key'])
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
function formatPrivateKey(string $privateKey) {
|
function formatPrivateKey(string $privateKey)
|
||||||
|
{
|
||||||
$privateKey = trim($privateKey);
|
$privateKey = trim($privateKey);
|
||||||
if (!str_ends_with($privateKey, "\n")) {
|
if (!str_ends_with($privateKey, "\n")) {
|
||||||
$privateKey .= "\n";
|
$privateKey .= "\n";
|
||||||
@ -135,19 +141,20 @@ function generate_application_name(string $git_repository, string $git_branch):
|
|||||||
|
|
||||||
function is_transactional_emails_active(): bool
|
function is_transactional_emails_active(): bool
|
||||||
{
|
{
|
||||||
return data_get(InstanceSettings::get(), 'smtp_enabled');
|
return isEmailEnabled(InstanceSettings::get());
|
||||||
}
|
}
|
||||||
|
|
||||||
function set_transanctional_email_settings(InstanceSettings | null $settings = null): void
|
function set_transanctional_email_settings(InstanceSettings | null $settings = null): string|null
|
||||||
{
|
{
|
||||||
if (!$settings) {
|
if (!$settings) {
|
||||||
$settings = InstanceSettings::get();
|
$settings = InstanceSettings::get();
|
||||||
}
|
}
|
||||||
$password = data_get($settings, 'smtp_password');
|
if (data_get($settings, 'resend_enabled')) {
|
||||||
if (isset($password)) {
|
config()->set('mail.default', 'resend');
|
||||||
$password = decrypt($password);
|
config()->set('resend.api_key', data_get($settings, 'resend_api_key'));
|
||||||
|
return 'resend';
|
||||||
}
|
}
|
||||||
|
if (data_get($settings, 'smtp_enabled')) {
|
||||||
config()->set('mail.default', 'smtp');
|
config()->set('mail.default', 'smtp');
|
||||||
config()->set('mail.mailers.smtp', [
|
config()->set('mail.mailers.smtp', [
|
||||||
"transport" => "smtp",
|
"transport" => "smtp",
|
||||||
@ -155,10 +162,13 @@ function set_transanctional_email_settings(InstanceSettings | null $settings = n
|
|||||||
"port" => data_get($settings, 'smtp_port'),
|
"port" => data_get($settings, 'smtp_port'),
|
||||||
"encryption" => data_get($settings, 'smtp_encryption'),
|
"encryption" => data_get($settings, 'smtp_encryption'),
|
||||||
"username" => data_get($settings, 'smtp_username'),
|
"username" => data_get($settings, 'smtp_username'),
|
||||||
"password" => $password,
|
"password" => data_get($settings, 'smtp_password'),
|
||||||
"timeout" => data_get($settings, 'smtp_timeout'),
|
"timeout" => data_get($settings, 'smtp_timeout'),
|
||||||
"local_domain" => null,
|
"local_domain" => null,
|
||||||
]);
|
]);
|
||||||
|
return 'smtp';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function base_ip(): string
|
function base_ip(): string
|
||||||
@ -212,7 +222,7 @@ function isDev(): bool
|
|||||||
return config('app.env') === 'local';
|
return config('app.env') === 'local';
|
||||||
}
|
}
|
||||||
|
|
||||||
function is_cloud(): bool
|
function isCloud(): bool
|
||||||
{
|
{
|
||||||
return !config('coolify.self_hosted');
|
return !config('coolify.self_hosted');
|
||||||
}
|
}
|
||||||
@ -241,7 +251,10 @@ function send_internal_notification(string $message): void
|
|||||||
function send_user_an_email(MailMessage $mail, string $email): void
|
function send_user_an_email(MailMessage $mail, string $email): void
|
||||||
{
|
{
|
||||||
$settings = InstanceSettings::get();
|
$settings = InstanceSettings::get();
|
||||||
set_transanctional_email_settings($settings);
|
$type = set_transanctional_email_settings($settings);
|
||||||
|
if (!$type) {
|
||||||
|
throw new Exception('No email settings found.');
|
||||||
|
}
|
||||||
Mail::send(
|
Mail::send(
|
||||||
[],
|
[],
|
||||||
[],
|
[],
|
||||||
@ -254,4 +267,9 @@ function send_user_an_email(MailMessage $mail, string $email): void
|
|||||||
->subject($mail->subject)
|
->subject($mail->subject)
|
||||||
->html((string) $mail->render())
|
->html((string) $mail->render())
|
||||||
);
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
function isEmailEnabled($notifiable)
|
||||||
|
{
|
||||||
|
return data_get($notifiable, 'smtp_enabled') || data_get($notifiable, 'resend_enabled') || data_get($notifiable, 'use_instance_email_settings');
|
||||||
}
|
}
|
||||||
|
@ -56,21 +56,19 @@ function isSubscriptionActive()
|
|||||||
if (!$subscription) {
|
if (!$subscription) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (config('subscription.provider') === 'lemon') {
|
if (isLemon()) {
|
||||||
return $subscription->lemon_status === 'active';
|
return $subscription->lemon_status === 'active';
|
||||||
}
|
}
|
||||||
if (config('subscription.provider') === 'stripe') {
|
// if (isPaddle()) {
|
||||||
|
// return $subscription->paddle_status === 'active';
|
||||||
|
// }
|
||||||
|
if (isStripe()) {
|
||||||
return $subscription->stripe_invoice_paid === true && $subscription->stripe_cancel_at_period_end === false;
|
return $subscription->stripe_invoice_paid === true && $subscription->stripe_cancel_at_period_end === false;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
// if (config('subscription.provider') === 'paddle') {
|
|
||||||
// return $subscription->paddle_status === 'active';
|
|
||||||
// }
|
|
||||||
|
|
||||||
}
|
}
|
||||||
function isSubscriptionOnGracePeriod()
|
function isSubscriptionOnGracePeriod()
|
||||||
{
|
{
|
||||||
|
|
||||||
$team = currentTeam();
|
$team = currentTeam();
|
||||||
if (!$team) {
|
if (!$team) {
|
||||||
return false;
|
return false;
|
||||||
@ -79,12 +77,12 @@ function isSubscriptionOnGracePeriod()
|
|||||||
if (!$subscription) {
|
if (!$subscription) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (config('subscription.provider') === 'lemon') {
|
if (isLemon()) {
|
||||||
$is_still_grace_period = $subscription->lemon_ends_at &&
|
$is_still_grace_period = $subscription->lemon_ends_at &&
|
||||||
Carbon::parse($subscription->lemon_ends_at) > Carbon::now();
|
Carbon::parse($subscription->lemon_ends_at) > Carbon::now();
|
||||||
return $is_still_grace_period;
|
return $is_still_grace_period;
|
||||||
}
|
}
|
||||||
if (config('subscription.provider') === 'stripe') {
|
if (isStripe()) {
|
||||||
return $subscription->stripe_cancel_at_period_end;
|
return $subscription->stripe_cancel_at_period_end;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@ -93,10 +91,22 @@ function subscriptionProvider()
|
|||||||
{
|
{
|
||||||
return config('subscription.provider');
|
return config('subscription.provider');
|
||||||
}
|
}
|
||||||
|
function isLemon()
|
||||||
|
{
|
||||||
|
return config('subscription.provider') === 'lemon';
|
||||||
|
}
|
||||||
|
function isStripe()
|
||||||
|
{
|
||||||
|
return config('subscription.provider') === 'stripe';
|
||||||
|
}
|
||||||
|
function isPaddle()
|
||||||
|
{
|
||||||
|
return config('subscription.provider') === 'paddle';
|
||||||
|
}
|
||||||
function getStripeCustomerPortalSession(Team $team)
|
function getStripeCustomerPortalSession(Team $team)
|
||||||
{
|
{
|
||||||
Stripe::setApiKey(config('subscription.stripe_api_key'));
|
Stripe::setApiKey(config('subscription.stripe_api_key'));
|
||||||
$return_url = route('team.show');
|
$return_url = route('team.index');
|
||||||
$stripe_customer_id = $team->subscription->stripe_customer_id;
|
$stripe_customer_id = $team->subscription->stripe_customer_id;
|
||||||
$session = \Stripe\BillingPortal\Session::create([
|
$session = \Stripe\BillingPortal\Session::create([
|
||||||
'customer' => $stripe_customer_id,
|
'customer' => $stripe_customer_id,
|
||||||
@ -124,6 +134,6 @@ function allowedPathsForBoardingAccounts()
|
|||||||
return [
|
return [
|
||||||
...allowedPathsForUnsubscribedAccounts(),
|
...allowedPathsForUnsubscribedAccounts(),
|
||||||
'boarding',
|
'boarding',
|
||||||
'livewire/message/boarding',
|
'livewire/message/boarding.index',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -42,7 +42,7 @@
|
|||||||
"laravel/pint": "^v1.8.0",
|
"laravel/pint": "^v1.8.0",
|
||||||
"mockery/mockery": "^1.5.1",
|
"mockery/mockery": "^1.5.1",
|
||||||
"nunomaduro/collision": "^v7.4.0",
|
"nunomaduro/collision": "^v7.4.0",
|
||||||
"pestphp/pest": "^v2.4.0",
|
"pestphp/pest": "^2.16",
|
||||||
"phpstan/phpstan": "^1.10",
|
"phpstan/phpstan": "^1.10",
|
||||||
"phpunit/phpunit": "^10.0.19",
|
"phpunit/phpunit": "^10.0.19",
|
||||||
"serversideup/spin": "^v1.1.0",
|
"serversideup/spin": "^v1.1.0",
|
||||||
|
142
composer.lock
generated
142
composer.lock
generated
@ -4,7 +4,7 @@
|
|||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "da14dce99d76abcaaa6393166eda049a",
|
"content-hash": "dbb08df7a80c46ce2b9b9fa397ed71c1",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "aws/aws-crt-php",
|
"name": "aws/aws-crt-php",
|
||||||
@ -6654,16 +6654,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/console",
|
"name": "symfony/console",
|
||||||
"version": "v6.3.2",
|
"version": "v6.3.4",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/symfony/console.git",
|
"url": "https://github.com/symfony/console.git",
|
||||||
"reference": "aa5d64ad3f63f2e48964fc81ee45cb318a723898"
|
"reference": "eca495f2ee845130855ddf1cf18460c38966c8b6"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/symfony/console/zipball/aa5d64ad3f63f2e48964fc81ee45cb318a723898",
|
"url": "https://api.github.com/repos/symfony/console/zipball/eca495f2ee845130855ddf1cf18460c38966c8b6",
|
||||||
"reference": "aa5d64ad3f63f2e48964fc81ee45cb318a723898",
|
"reference": "eca495f2ee845130855ddf1cf18460c38966c8b6",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@ -6724,7 +6724,7 @@
|
|||||||
"terminal"
|
"terminal"
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"source": "https://github.com/symfony/console/tree/v6.3.2"
|
"source": "https://github.com/symfony/console/tree/v6.3.4"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@ -6740,7 +6740,7 @@
|
|||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2023-07-19T20:17:28+00:00"
|
"time": "2023-08-16T10:10:12+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/css-selector",
|
"name": "symfony/css-selector",
|
||||||
@ -7761,16 +7761,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/polyfill-ctype",
|
"name": "symfony/polyfill-ctype",
|
||||||
"version": "v1.27.0",
|
"version": "v1.28.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/symfony/polyfill-ctype.git",
|
"url": "https://github.com/symfony/polyfill-ctype.git",
|
||||||
"reference": "5bbc823adecdae860bb64756d639ecfec17b050a"
|
"reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/5bbc823adecdae860bb64756d639ecfec17b050a",
|
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb",
|
||||||
"reference": "5bbc823adecdae860bb64756d639ecfec17b050a",
|
"reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@ -7785,7 +7785,7 @@
|
|||||||
"type": "library",
|
"type": "library",
|
||||||
"extra": {
|
"extra": {
|
||||||
"branch-alias": {
|
"branch-alias": {
|
||||||
"dev-main": "1.27-dev"
|
"dev-main": "1.28-dev"
|
||||||
},
|
},
|
||||||
"thanks": {
|
"thanks": {
|
||||||
"name": "symfony/polyfill",
|
"name": "symfony/polyfill",
|
||||||
@ -7823,7 +7823,7 @@
|
|||||||
"portable"
|
"portable"
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.27.0"
|
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.28.0"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@ -7839,7 +7839,7 @@
|
|||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2022-11-03T14:55:06+00:00"
|
"time": "2023-01-26T09:26:14+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/polyfill-iconv",
|
"name": "symfony/polyfill-iconv",
|
||||||
@ -7926,16 +7926,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/polyfill-intl-grapheme",
|
"name": "symfony/polyfill-intl-grapheme",
|
||||||
"version": "v1.27.0",
|
"version": "v1.28.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/symfony/polyfill-intl-grapheme.git",
|
"url": "https://github.com/symfony/polyfill-intl-grapheme.git",
|
||||||
"reference": "511a08c03c1960e08a883f4cffcacd219b758354"
|
"reference": "875e90aeea2777b6f135677f618529449334a612"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/511a08c03c1960e08a883f4cffcacd219b758354",
|
"url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/875e90aeea2777b6f135677f618529449334a612",
|
||||||
"reference": "511a08c03c1960e08a883f4cffcacd219b758354",
|
"reference": "875e90aeea2777b6f135677f618529449334a612",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@ -7947,7 +7947,7 @@
|
|||||||
"type": "library",
|
"type": "library",
|
||||||
"extra": {
|
"extra": {
|
||||||
"branch-alias": {
|
"branch-alias": {
|
||||||
"dev-main": "1.27-dev"
|
"dev-main": "1.28-dev"
|
||||||
},
|
},
|
||||||
"thanks": {
|
"thanks": {
|
||||||
"name": "symfony/polyfill",
|
"name": "symfony/polyfill",
|
||||||
@ -7987,7 +7987,7 @@
|
|||||||
"shim"
|
"shim"
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.27.0"
|
"source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.28.0"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@ -8003,7 +8003,7 @@
|
|||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2022-11-03T14:55:06+00:00"
|
"time": "2023-01-26T09:26:14+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/polyfill-intl-idn",
|
"name": "symfony/polyfill-intl-idn",
|
||||||
@ -8094,16 +8094,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/polyfill-intl-normalizer",
|
"name": "symfony/polyfill-intl-normalizer",
|
||||||
"version": "v1.27.0",
|
"version": "v1.28.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/symfony/polyfill-intl-normalizer.git",
|
"url": "https://github.com/symfony/polyfill-intl-normalizer.git",
|
||||||
"reference": "19bd1e4fcd5b91116f14d8533c57831ed00571b6"
|
"reference": "8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/19bd1e4fcd5b91116f14d8533c57831ed00571b6",
|
"url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92",
|
||||||
"reference": "19bd1e4fcd5b91116f14d8533c57831ed00571b6",
|
"reference": "8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@ -8115,7 +8115,7 @@
|
|||||||
"type": "library",
|
"type": "library",
|
||||||
"extra": {
|
"extra": {
|
||||||
"branch-alias": {
|
"branch-alias": {
|
||||||
"dev-main": "1.27-dev"
|
"dev-main": "1.28-dev"
|
||||||
},
|
},
|
||||||
"thanks": {
|
"thanks": {
|
||||||
"name": "symfony/polyfill",
|
"name": "symfony/polyfill",
|
||||||
@ -8158,7 +8158,7 @@
|
|||||||
"shim"
|
"shim"
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.27.0"
|
"source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.28.0"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@ -8174,20 +8174,20 @@
|
|||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2022-11-03T14:55:06+00:00"
|
"time": "2023-01-26T09:26:14+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/polyfill-mbstring",
|
"name": "symfony/polyfill-mbstring",
|
||||||
"version": "v1.27.0",
|
"version": "v1.28.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/symfony/polyfill-mbstring.git",
|
"url": "https://github.com/symfony/polyfill-mbstring.git",
|
||||||
"reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534"
|
"reference": "42292d99c55abe617799667f454222c54c60e229"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/8ad114f6b39e2c98a8b0e3bd907732c207c2b534",
|
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/42292d99c55abe617799667f454222c54c60e229",
|
||||||
"reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534",
|
"reference": "42292d99c55abe617799667f454222c54c60e229",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@ -8202,7 +8202,7 @@
|
|||||||
"type": "library",
|
"type": "library",
|
||||||
"extra": {
|
"extra": {
|
||||||
"branch-alias": {
|
"branch-alias": {
|
||||||
"dev-main": "1.27-dev"
|
"dev-main": "1.28-dev"
|
||||||
},
|
},
|
||||||
"thanks": {
|
"thanks": {
|
||||||
"name": "symfony/polyfill",
|
"name": "symfony/polyfill",
|
||||||
@ -8241,7 +8241,7 @@
|
|||||||
"shim"
|
"shim"
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.27.0"
|
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.28.0"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@ -8257,7 +8257,7 @@
|
|||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2022-11-03T14:55:06+00:00"
|
"time": "2023-07-28T09:04:16+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/polyfill-php72",
|
"name": "symfony/polyfill-php72",
|
||||||
@ -8337,16 +8337,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/polyfill-php80",
|
"name": "symfony/polyfill-php80",
|
||||||
"version": "v1.27.0",
|
"version": "v1.28.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/symfony/polyfill-php80.git",
|
"url": "https://github.com/symfony/polyfill-php80.git",
|
||||||
"reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936"
|
"reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936",
|
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/6caa57379c4aec19c0a12a38b59b26487dcfe4b5",
|
||||||
"reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936",
|
"reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@ -8355,7 +8355,7 @@
|
|||||||
"type": "library",
|
"type": "library",
|
||||||
"extra": {
|
"extra": {
|
||||||
"branch-alias": {
|
"branch-alias": {
|
||||||
"dev-main": "1.27-dev"
|
"dev-main": "1.28-dev"
|
||||||
},
|
},
|
||||||
"thanks": {
|
"thanks": {
|
||||||
"name": "symfony/polyfill",
|
"name": "symfony/polyfill",
|
||||||
@ -8400,7 +8400,7 @@
|
|||||||
"shim"
|
"shim"
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"source": "https://github.com/symfony/polyfill-php80/tree/v1.27.0"
|
"source": "https://github.com/symfony/polyfill-php80/tree/v1.28.0"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@ -8416,7 +8416,7 @@
|
|||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2022-11-03T14:55:06+00:00"
|
"time": "2023-01-26T09:26:14+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/polyfill-php83",
|
"name": "symfony/polyfill-php83",
|
||||||
@ -8579,16 +8579,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/process",
|
"name": "symfony/process",
|
||||||
"version": "v6.3.2",
|
"version": "v6.3.4",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/symfony/process.git",
|
"url": "https://github.com/symfony/process.git",
|
||||||
"reference": "c5ce962db0d9b6e80247ca5eb9af6472bd4d7b5d"
|
"reference": "0b5c29118f2e980d455d2e34a5659f4579847c54"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/symfony/process/zipball/c5ce962db0d9b6e80247ca5eb9af6472bd4d7b5d",
|
"url": "https://api.github.com/repos/symfony/process/zipball/0b5c29118f2e980d455d2e34a5659f4579847c54",
|
||||||
"reference": "c5ce962db0d9b6e80247ca5eb9af6472bd4d7b5d",
|
"reference": "0b5c29118f2e980d455d2e34a5659f4579847c54",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@ -8620,7 +8620,7 @@
|
|||||||
"description": "Executes commands in sub-processes",
|
"description": "Executes commands in sub-processes",
|
||||||
"homepage": "https://symfony.com",
|
"homepage": "https://symfony.com",
|
||||||
"support": {
|
"support": {
|
||||||
"source": "https://github.com/symfony/process/tree/v6.3.2"
|
"source": "https://github.com/symfony/process/tree/v6.3.4"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@ -8636,7 +8636,7 @@
|
|||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2023-07-12T16:00:22+00:00"
|
"time": "2023-08-07T10:39:22+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/psr-http-message-bridge",
|
"name": "symfony/psr-http-message-bridge",
|
||||||
@ -9982,16 +9982,16 @@
|
|||||||
"packages-dev": [
|
"packages-dev": [
|
||||||
{
|
{
|
||||||
"name": "brianium/paratest",
|
"name": "brianium/paratest",
|
||||||
"version": "v7.2.5",
|
"version": "v7.2.6",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/paratestphp/paratest.git",
|
"url": "https://github.com/paratestphp/paratest.git",
|
||||||
"reference": "4d7ad5b6564f63baa1b948ecad05439f22880942"
|
"reference": "7f372b5bb59b4271adedc67d3129df29b84c4173"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/paratestphp/paratest/zipball/4d7ad5b6564f63baa1b948ecad05439f22880942",
|
"url": "https://api.github.com/repos/paratestphp/paratest/zipball/7f372b5bb59b4271adedc67d3129df29b84c4173",
|
||||||
"reference": "4d7ad5b6564f63baa1b948ecad05439f22880942",
|
"reference": "7f372b5bb59b4271adedc67d3129df29b84c4173",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@ -10005,19 +10005,19 @@
|
|||||||
"phpunit/php-code-coverage": "^10.1.3",
|
"phpunit/php-code-coverage": "^10.1.3",
|
||||||
"phpunit/php-file-iterator": "^4.0.2",
|
"phpunit/php-file-iterator": "^4.0.2",
|
||||||
"phpunit/php-timer": "^6.0",
|
"phpunit/php-timer": "^6.0",
|
||||||
"phpunit/phpunit": "^10.3.1",
|
"phpunit/phpunit": "^10.3.2",
|
||||||
"sebastian/environment": "^6.0.1",
|
"sebastian/environment": "^6.0.1",
|
||||||
"symfony/console": "^6.3.2",
|
"symfony/console": "^6.3.4",
|
||||||
"symfony/process": "^6.3.2"
|
"symfony/process": "^6.3.4"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"doctrine/coding-standard": "^12.0.0",
|
"doctrine/coding-standard": "^12.0.0",
|
||||||
"ext-pcov": "*",
|
"ext-pcov": "*",
|
||||||
"ext-posix": "*",
|
"ext-posix": "*",
|
||||||
"infection/infection": "^0.27.0",
|
"infection/infection": "^0.27.0",
|
||||||
"phpstan/phpstan": "^1.10.26",
|
"phpstan/phpstan": "^1.10.32",
|
||||||
"phpstan/phpstan-deprecation-rules": "^1.1.3",
|
"phpstan/phpstan-deprecation-rules": "^1.1.4",
|
||||||
"phpstan/phpstan-phpunit": "^1.3.13",
|
"phpstan/phpstan-phpunit": "^1.3.14",
|
||||||
"phpstan/phpstan-strict-rules": "^1.5.1",
|
"phpstan/phpstan-strict-rules": "^1.5.1",
|
||||||
"squizlabs/php_codesniffer": "^3.7.2",
|
"squizlabs/php_codesniffer": "^3.7.2",
|
||||||
"symfony/filesystem": "^6.3.1"
|
"symfony/filesystem": "^6.3.1"
|
||||||
@ -10061,7 +10061,7 @@
|
|||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/paratestphp/paratest/issues",
|
"issues": "https://github.com/paratestphp/paratest/issues",
|
||||||
"source": "https://github.com/paratestphp/paratest/tree/v7.2.5"
|
"source": "https://github.com/paratestphp/paratest/tree/v7.2.6"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@ -10073,7 +10073,7 @@
|
|||||||
"type": "paypal"
|
"type": "paypal"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2023-08-08T13:23:59+00:00"
|
"time": "2023-08-29T07:47:39+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "fakerphp/faker",
|
"name": "fakerphp/faker",
|
||||||
@ -10707,24 +10707,24 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "pestphp/pest",
|
"name": "pestphp/pest",
|
||||||
"version": "v2.16.0",
|
"version": "v2.16.1",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/pestphp/pest.git",
|
"url": "https://github.com/pestphp/pest.git",
|
||||||
"reference": "cbd6a650576714c673dbb0575989663f7f5c8b6d"
|
"reference": "55b92666482b7d4320b7869c4eea7333d35c5631"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/pestphp/pest/zipball/cbd6a650576714c673dbb0575989663f7f5c8b6d",
|
"url": "https://api.github.com/repos/pestphp/pest/zipball/55b92666482b7d4320b7869c4eea7333d35c5631",
|
||||||
"reference": "cbd6a650576714c673dbb0575989663f7f5c8b6d",
|
"reference": "55b92666482b7d4320b7869c4eea7333d35c5631",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"brianium/paratest": "^7.2.5",
|
"brianium/paratest": "^7.2.6",
|
||||||
"nunomaduro/collision": "^7.8.1",
|
"nunomaduro/collision": "^7.8.1",
|
||||||
"nunomaduro/termwind": "^1.15.1",
|
"nunomaduro/termwind": "^1.15.1",
|
||||||
"pestphp/pest-plugin": "^2.0.1",
|
"pestphp/pest-plugin": "^2.1.1",
|
||||||
"pestphp/pest-plugin-arch": "^2.3.1",
|
"pestphp/pest-plugin-arch": "^2.3.3",
|
||||||
"php": "^8.1.0",
|
"php": "^8.1.0",
|
||||||
"phpunit/phpunit": "^10.3.2"
|
"phpunit/phpunit": "^10.3.2"
|
||||||
},
|
},
|
||||||
@ -10734,8 +10734,8 @@
|
|||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"pestphp/pest-dev-tools": "^2.16.0",
|
"pestphp/pest-dev-tools": "^2.16.0",
|
||||||
"pestphp/pest-plugin-type-coverage": "^2.0.0",
|
"pestphp/pest-plugin-type-coverage": "^2.2.0",
|
||||||
"symfony/process": "^6.3.2"
|
"symfony/process": "^6.3.4"
|
||||||
},
|
},
|
||||||
"bin": [
|
"bin": [
|
||||||
"bin/pest"
|
"bin/pest"
|
||||||
@ -10793,7 +10793,7 @@
|
|||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/pestphp/pest/issues",
|
"issues": "https://github.com/pestphp/pest/issues",
|
||||||
"source": "https://github.com/pestphp/pest/tree/v2.16.0"
|
"source": "https://github.com/pestphp/pest/tree/v2.16.1"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@ -10805,7 +10805,7 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2023-08-21T08:42:07+00:00"
|
"time": "2023-08-29T09:30:36+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "pestphp/pest-plugin",
|
"name": "pestphp/pest-plugin",
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
return [
|
return [
|
||||||
'waitlist' => [
|
'waitlist' => [
|
||||||
'confirmation_valid_for_minutes' => 10,
|
'expiration' => 10,
|
||||||
],
|
],
|
||||||
'invitation' => [
|
'invitation' => [
|
||||||
'link' => [
|
'link' => [
|
||||||
@ -11,9 +11,18 @@
|
|||||||
],
|
],
|
||||||
'limits' => [
|
'limits' => [
|
||||||
'server' => [
|
'server' => [
|
||||||
|
'zero' => 0,
|
||||||
|
'self-hosted' => 999999999999,
|
||||||
'basic' => 1,
|
'basic' => 1,
|
||||||
'pro' => 3,
|
'pro' => 10,
|
||||||
'ultimate' => 9999999999999999999,
|
'ultimate' => 25,
|
||||||
|
],
|
||||||
|
'email' => [
|
||||||
|
'zero' => false,
|
||||||
|
'self-hosted' => true,
|
||||||
|
'basic' => false,
|
||||||
|
'pro' => true,
|
||||||
|
'ultimate' => true,
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
return [
|
return [
|
||||||
'self_hosted' => env('SELF_HOSTED', true),
|
'self_hosted' => env('SELF_HOSTED', true),
|
||||||
'waitlist' => env('WAITLIST', false),
|
'waitlist' => env('WAITLIST', false),
|
||||||
'license_url' => 'https://license.coolify.io',
|
'license_url' => 'https://licenses.coollabs.io',
|
||||||
'mux_enabled' => env('MUX_ENABLED', true),
|
'mux_enabled' => env('MUX_ENABLED', true),
|
||||||
'dev_webhook' => env('SERVEO_URL'),
|
'dev_webhook' => env('SERVEO_URL'),
|
||||||
'base_config_path' => env('BASE_CONFIG_PATH', '/data/coolify'),
|
'base_config_path' => env('BASE_CONFIG_PATH', '/data/coolify'),
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
// The release version of your application
|
// The release version of your application
|
||||||
// Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD'))
|
// Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD'))
|
||||||
'release' => trim(exec('jq -r .coolify.v4.version versions.json 2>/dev/null')) ?? 'unknown',
|
'release' => '4.0.0-beta.21',
|
||||||
'server_name' => env('APP_ID', 'coolify'),
|
'server_name' => env('APP_ID', 'coolify'),
|
||||||
// When left empty or `null` the Laravel environment will be used
|
// When left empty or `null` the Laravel environment will be used
|
||||||
'environment' => config('app.env'),
|
'environment' => config('app.env'),
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'driver' => env('SESSION_DRIVER', 'database'),
|
'driver' => env('SESSION_DRIVER', 'redis'),
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
return '4.0.0-beta.21';
|
return '4.0.0-beta.22';
|
||||||
|
@ -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('subscriptions', function (Blueprint $table) {
|
||||||
|
$table->string('stripe_plan_id')->nullable()->after('stripe_cancel_at_period_end');
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('subscriptions', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('stripe_plan_id');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
@ -0,0 +1,30 @@
|
|||||||
|
<?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('instance_settings', function (Blueprint $table) {
|
||||||
|
$table->boolean('resend_enabled')->default(false);
|
||||||
|
$table->text('resend_api_key')->nullable();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('instance_settings', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('resend_enabled');
|
||||||
|
$table->dropColumn('resend_api_key');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('teams', function (Blueprint $table) {
|
||||||
|
$table->boolean('resend_enabled')->default(false);
|
||||||
|
$table->text('resend_api_key')->nullable();
|
||||||
|
$table->boolean('use_instance_email_settings')->default(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('teams', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('resend_enabled');
|
||||||
|
$table->dropColumn('resend_api_key');
|
||||||
|
$table->dropColumn('use_instance_email_settings');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
@ -45,6 +45,7 @@ public function run(): void
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (config('app.name') !== 'coolify-cloud') {
|
||||||
// Save SSH Keys for the Coolify Host
|
// Save SSH Keys for the Coolify Host
|
||||||
$coolify_key_name = "id.root@host.docker.internal";
|
$coolify_key_name = "id.root@host.docker.internal";
|
||||||
$coolify_key = Storage::disk('ssh-keys')->get("{$coolify_key_name}");
|
$coolify_key = Storage::disk('ssh-keys')->get("{$coolify_key_name}");
|
||||||
@ -65,7 +66,6 @@ public function run(): void
|
|||||||
echo "Then try to install again.\n";
|
echo "Then try to install again.\n";
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add Coolify host (localhost) as Server if it doesn't exist
|
// Add Coolify host (localhost) as Server if it doesn't exist
|
||||||
if (Server::find(0) == null) {
|
if (Server::find(0) == null) {
|
||||||
$server_details = [
|
$server_details = [
|
||||||
@ -99,6 +99,8 @@ public function run(): void
|
|||||||
'server_id' => 0,
|
'server_id' => 0,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$settings = InstanceSettings::get();
|
$settings = InstanceSettings::get();
|
||||||
if (is_null($settings->public_ipv4)) {
|
if (is_null($settings->public_ipv4)) {
|
||||||
|
@ -15,10 +15,12 @@ public function run(): void
|
|||||||
'email' => 'test@example.com',
|
'email' => 'test@example.com',
|
||||||
]);
|
]);
|
||||||
User::factory()->create([
|
User::factory()->create([
|
||||||
|
'id' => 1,
|
||||||
'name' => 'Normal User (but in root team)',
|
'name' => 'Normal User (but in root team)',
|
||||||
'email' => 'test2@example.com',
|
'email' => 'test2@example.com',
|
||||||
]);
|
]);
|
||||||
User::factory()->create([
|
User::factory()->create([
|
||||||
|
'id' => 2,
|
||||||
'name' => 'Normal User (not in root team)',
|
'name' => 'Normal User (not in root team)',
|
||||||
'email' => 'test3@example.com',
|
'email' => 'test3@example.com',
|
||||||
]);
|
]);
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
version: '3.8'
|
version: '3.8'
|
||||||
services:
|
services:
|
||||||
coolify:
|
coolify:
|
||||||
image: "ghcr.io/coollabsio/coolify:${LATEST_IMAGE:-4.0.0-nightly.0}"
|
image: "ghcr.io/coollabsio/coolify:${LATEST_IMAGE:-4.0.0-beta.20}"
|
||||||
volumes:
|
volumes:
|
||||||
- type: bind
|
- type: bind
|
||||||
source: /data/coolify/source/.env
|
source: /data/coolify/source/.env
|
||||||
|
17
phpunit.xml
17
phpunit.xml
@ -1,9 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.3/phpunit.xsd" bootstrap="vendor/autoload.php" colors="true">
|
||||||
xsi:noNamespaceSchemaLocation="./vendor/phpunit/phpunit/phpunit.xsd"
|
|
||||||
bootstrap="vendor/autoload.php"
|
|
||||||
colors="true"
|
|
||||||
>
|
|
||||||
<testsuites>
|
<testsuites>
|
||||||
<testsuite name="Unit">
|
<testsuite name="Unit">
|
||||||
<directory suffix="Test.php">./tests/Unit</directory>
|
<directory suffix="Test.php">./tests/Unit</directory>
|
||||||
@ -12,11 +8,7 @@
|
|||||||
<directory suffix="Test.php">./tests/Feature</directory>
|
<directory suffix="Test.php">./tests/Feature</directory>
|
||||||
</testsuite>
|
</testsuite>
|
||||||
</testsuites>
|
</testsuites>
|
||||||
<coverage>
|
<coverage/>
|
||||||
<include>
|
|
||||||
<directory suffix=".php">./app</directory>
|
|
||||||
</include>
|
|
||||||
</coverage>
|
|
||||||
<php>
|
<php>
|
||||||
<env name="APP_ENV" value="testing"/>
|
<env name="APP_ENV" value="testing"/>
|
||||||
<env name="BCRYPT_ROUNDS" value="4"/>
|
<env name="BCRYPT_ROUNDS" value="4"/>
|
||||||
@ -28,4 +20,9 @@
|
|||||||
<env name="SESSION_DRIVER" value="array"/>
|
<env name="SESSION_DRIVER" value="array"/>
|
||||||
<env name="TELESCOPE_ENABLED" value="false"/>
|
<env name="TELESCOPE_ENABLED" value="false"/>
|
||||||
</php>
|
</php>
|
||||||
|
<source>
|
||||||
|
<include>
|
||||||
|
<directory suffix=".php">./app</directory>
|
||||||
|
</include>
|
||||||
|
</source>
|
||||||
</phpunit>
|
</phpunit>
|
||||||
|
@ -408,6 +408,12 @@ const magicActions = [{
|
|||||||
name: 'Goto: Switch Teams',
|
name: 'Goto: Switch Teams',
|
||||||
icon: 'goto',
|
icon: 'goto',
|
||||||
sequence: ['main', 'redirect']
|
sequence: ['main', 'redirect']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 23,
|
||||||
|
name: 'Goto: Boarding process',
|
||||||
|
icon: 'goto',
|
||||||
|
sequence: ['main', 'redirect']
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
const initialState = {
|
const initialState = {
|
||||||
@ -635,6 +641,9 @@ async function redirect() {
|
|||||||
case 22:
|
case 22:
|
||||||
targetUrl.pathname = `/team`
|
targetUrl.pathname = `/team`
|
||||||
break;
|
break;
|
||||||
|
case 23:
|
||||||
|
targetUrl.pathname = `/boarding`
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
window.location.href = targetUrl;
|
window.location.href = targetUrl;
|
||||||
}
|
}
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
<x-layout-simple>
|
|
||||||
<livewire:waitlist :waiting_in_line="$waiting_in_line" />
|
|
||||||
</x-layout-simple>
|
|
@ -1,13 +0,0 @@
|
|||||||
<x-layout-simple>
|
|
||||||
<livewire:boarding />
|
|
||||||
<x-modal modalId="installDocker">
|
|
||||||
<x-slot:modalBody>
|
|
||||||
<livewire:activity-monitor header="Installing Docker Logs" />
|
|
||||||
</x-slot:modalBody>
|
|
||||||
<x-slot:modalSubmit>
|
|
||||||
<x-forms.button onclick="installDocker.close()" type="submit">
|
|
||||||
Close
|
|
||||||
</x-forms.button>
|
|
||||||
</x-slot:modalSubmit>
|
|
||||||
</x-modal>
|
|
||||||
</x-layout-simple>
|
|
@ -12,7 +12,9 @@
|
|||||||
@if ($attributes->get('type') === 'submit')
|
@if ($attributes->get('type') === 'submit')
|
||||||
<span wire:target="submit" wire:loading.delay class="loading loading-xs text-warning loading-spinner"></span>
|
<span wire:target="submit" wire:loading.delay class="loading loading-xs text-warning loading-spinner"></span>
|
||||||
@else
|
@else
|
||||||
<span wire:target="{{ explode('(', $attributes->whereStartsWith('wire:click')->first())[0] }}" wire:loading.delay
|
@if ($attributes->has('wire:click'))
|
||||||
|
<span wire:target="{{ $attributes->get('wire:click') }}" wire:loading.delay
|
||||||
class="loading loading-xs loading-spinner"></span>
|
class="loading loading-xs loading-spinner"></span>
|
||||||
@endif
|
@endif
|
||||||
|
@endif
|
||||||
</button>
|
</button>
|
||||||
|
@ -1,68 +1 @@
|
|||||||
<!DOCTYPE html>
|
@extends('layouts.simple')
|
||||||
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<link rel="preconnect" href="https://api.fonts.coollabs.io" crossorigin>
|
|
||||||
<link href="https://api.fonts.coollabs.io/css2?family=Inter&display=swap" rel="stylesheet">
|
|
||||||
@env('local')
|
|
||||||
<title>Coolify - localhost</title>
|
|
||||||
<link rel="icon" href="{{ asset('favicon-dev.png') }}" type="image/x-icon" />
|
|
||||||
@else
|
|
||||||
<title>{{ $title ?? 'Coolify' }}</title>
|
|
||||||
<link rel="icon" href="{{ asset('coolify-transparent.png') }}" type="image/x-icon" />
|
|
||||||
@endenv
|
|
||||||
<meta name="csrf-token" content="{{ csrf_token() }}">
|
|
||||||
@vite(['resources/js/app.js', 'resources/css/app.css'])
|
|
||||||
<style>
|
|
||||||
[x-cloak] {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@livewireStyles
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
@livewireScripts
|
|
||||||
<x-toaster-hub />
|
|
||||||
<main>
|
|
||||||
{{ $slot }}
|
|
||||||
</main>
|
|
||||||
<x-version class="fixed left-2 bottom-1" />
|
|
||||||
<script>
|
|
||||||
Livewire.on('info', (message) => {
|
|
||||||
if (message) Toaster.info(message)
|
|
||||||
})
|
|
||||||
Livewire.on('error', (message) => {
|
|
||||||
if (message) Toaster.error(message)
|
|
||||||
})
|
|
||||||
Livewire.on('warning', (message) => {
|
|
||||||
if (message) Toaster.warning(message)
|
|
||||||
})
|
|
||||||
Livewire.on('success', (message) => {
|
|
||||||
if (message) Toaster.success(message)
|
|
||||||
})
|
|
||||||
|
|
||||||
function changePasswordFieldType(event) {
|
|
||||||
let element = event.target
|
|
||||||
for (let i = 0; i < 10; i++) {
|
|
||||||
if (element.className === "relative") {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
element = element.parentElement;
|
|
||||||
}
|
|
||||||
element = element.children[1];
|
|
||||||
if (element.nodeName === 'INPUT') {
|
|
||||||
if (element.type === 'password') {
|
|
||||||
element.type = 'text';
|
|
||||||
} else {
|
|
||||||
element.type = 'password';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
||||||
|
@ -1,86 +1 @@
|
|||||||
<!DOCTYPE html>
|
@extends('layouts.subscription')
|
||||||
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<link rel="preconnect" href="https://api.fonts.coollabs.io" crossorigin>
|
|
||||||
<link href="https://api.fonts.coollabs.io/css2?family=Inter&display=swap" rel="stylesheet">
|
|
||||||
@env('local')
|
|
||||||
<title>Coolify - localhost</title>
|
|
||||||
<link rel="icon" href="{{ asset('favicon-dev.png') }}" type="image/x-icon" />
|
|
||||||
@else
|
|
||||||
<title>{{ $title ?? 'Coolify' }}</title>
|
|
||||||
<link rel="icon" href="{{ asset('coolify-transparent.png') }}" type="image/x-icon" />
|
|
||||||
@endenv
|
|
||||||
<meta name="csrf-token" content="{{ csrf_token() }}">
|
|
||||||
@vite(['resources/js/app.js', 'resources/css/app.css'])
|
|
||||||
<style>
|
|
||||||
[x-cloak] {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@livewireStyles
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
@livewireScripts
|
|
||||||
<x-toaster-hub />
|
|
||||||
@if (isSubscriptionOnGracePeriod())
|
|
||||||
<div class="fixed top-3 left-4" id="vue">
|
|
||||||
<magic-bar></magic-bar>
|
|
||||||
</div>
|
|
||||||
<x-navbar />
|
|
||||||
@else
|
|
||||||
<x-navbar-subscription />
|
|
||||||
@endif
|
|
||||||
|
|
||||||
<main class="main max-w-screen-2xl">
|
|
||||||
{{ $slot }}
|
|
||||||
</main>
|
|
||||||
<x-version class="fixed left-2 bottom-1" />
|
|
||||||
<script>
|
|
||||||
function changePasswordFieldType(event) {
|
|
||||||
let element = event.target
|
|
||||||
for (let i = 0; i < 10; i++) {
|
|
||||||
if (element.className === "relative") {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
element = element.parentElement;
|
|
||||||
}
|
|
||||||
element = element.children[1];
|
|
||||||
if (element.nodeName === 'INPUT') {
|
|
||||||
if (element.type === 'password') {
|
|
||||||
element.type = 'text';
|
|
||||||
} else {
|
|
||||||
element.type = 'password';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Livewire.on('reloadWindow', (timeout) => {
|
|
||||||
if (timeout) {
|
|
||||||
setTimeout(() => {
|
|
||||||
window.location.reload();
|
|
||||||
}, timeout);
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
window.location.reload();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
Livewire.on('info', (message) => {
|
|
||||||
if (message) Toaster.info(message)
|
|
||||||
})
|
|
||||||
Livewire.on('error', (message) => {
|
|
||||||
if (message) Toaster.error(message)
|
|
||||||
})
|
|
||||||
Livewire.on('warning', (message) => {
|
|
||||||
if (message) Toaster.warning(message)
|
|
||||||
})
|
|
||||||
Livewire.on('success', (message) => {
|
|
||||||
if (message) Toaster.success(message)
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
||||||
|
@ -1,133 +1 @@
|
|||||||
<!DOCTYPE html>
|
@extends('layouts.app')
|
||||||
<html data-theme="coollabs" lang="{{ str_replace('_', '-', app()->getLocale()) }}">
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<link rel="preconnect" href="https://api.fonts.coollabs.io" crossorigin>
|
|
||||||
<link href="https://api.fonts.coollabs.io/css2?family=Inter&display=swap" rel="stylesheet">
|
|
||||||
@env('local')
|
|
||||||
<title>Coolify - localhost</title>
|
|
||||||
<link rel="icon" href="{{ asset('favicon-dev.png') }}" type="image/x-icon" />
|
|
||||||
@else
|
|
||||||
<title>{{ $title ?? 'Coolify' }}</title>
|
|
||||||
<link rel="icon" href="{{ asset('coolify-transparent.png') }}" type="image/x-icon" />
|
|
||||||
@endenv
|
|
||||||
<meta name="csrf-token" content="{{ csrf_token() }}">
|
|
||||||
@vite(['resources/js/app.js', 'resources/css/app.css'])
|
|
||||||
<style>
|
|
||||||
[x-cloak] {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@livewireStyles
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
@livewireScripts
|
|
||||||
@auth
|
|
||||||
<x-toaster-hub />
|
|
||||||
<x-navbar />
|
|
||||||
<div class="fixed top-3 left-4" id="vue">
|
|
||||||
<magic-bar></magic-bar>
|
|
||||||
</div>
|
|
||||||
<main class="main max-w-screen-2xl">
|
|
||||||
{{ $slot }}
|
|
||||||
</main>
|
|
||||||
<x-version class="fixed left-2 bottom-1" />
|
|
||||||
<script>
|
|
||||||
let checkHealthInterval = null;
|
|
||||||
let checkIfIamDeadInterval = null;
|
|
||||||
|
|
||||||
function changePasswordFieldType(event) {
|
|
||||||
let element = event.target
|
|
||||||
for (let i = 0; i < 10; i++) {
|
|
||||||
if (element.className === "relative") {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
element = element.parentElement;
|
|
||||||
}
|
|
||||||
element = element.children[1];
|
|
||||||
if (element.nodeName === 'INPUT') {
|
|
||||||
if (element.type === 'password') {
|
|
||||||
element.type = 'text';
|
|
||||||
} else {
|
|
||||||
element.type = 'password';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function revive() {
|
|
||||||
if (checkHealthInterval) return true;
|
|
||||||
console.log('Checking server\'s health...')
|
|
||||||
checkHealthInterval = setInterval(() => {
|
|
||||||
fetch('/api/health')
|
|
||||||
.then(response => {
|
|
||||||
if (response.ok) {
|
|
||||||
Toaster.success('Coolify is back online. Reloading...')
|
|
||||||
if (checkHealthInterval) clearInterval(checkHealthInterval);
|
|
||||||
setTimeout(() => {
|
|
||||||
window.location.reload();
|
|
||||||
}, 5000)
|
|
||||||
} else {
|
|
||||||
console.log('Waiting for server to come back from dead...');
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
}, 2000);
|
|
||||||
}
|
|
||||||
|
|
||||||
function upgrade() {
|
|
||||||
if (checkIfIamDeadInterval) return true;
|
|
||||||
console.log('Update initiated.')
|
|
||||||
checkIfIamDeadInterval = setInterval(() => {
|
|
||||||
fetch('/api/health')
|
|
||||||
.then(response => {
|
|
||||||
if (response.ok) {
|
|
||||||
console.log('It\'s alive. Waiting for server to be dead...');
|
|
||||||
} else {
|
|
||||||
Toaster.success('Update done, restarting Coolify!')
|
|
||||||
console.log('It\'s dead. Reviving... Standby... Bzz... Bzz...')
|
|
||||||
if (checkIfIamDeadInterval) clearInterval(checkIfIamDeadInterval);
|
|
||||||
revive();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
}, 2000);
|
|
||||||
}
|
|
||||||
|
|
||||||
function copyToClipboard(text) {
|
|
||||||
navigator.clipboard.writeText(text);
|
|
||||||
Livewire.emit('message', 'Copied to clipboard.');
|
|
||||||
}
|
|
||||||
|
|
||||||
Livewire.on('reloadWindow', (timeout) => {
|
|
||||||
if (timeout) {
|
|
||||||
setTimeout(() => {
|
|
||||||
window.location.reload();
|
|
||||||
}, timeout);
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
window.location.reload();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
Livewire.on('info', (message) => {
|
|
||||||
if (message) Toaster.info(message)
|
|
||||||
})
|
|
||||||
Livewire.on('error', (message) => {
|
|
||||||
if (message) Toaster.error(message)
|
|
||||||
})
|
|
||||||
Livewire.on('warning', (message) => {
|
|
||||||
if (message) Toaster.warning(message)
|
|
||||||
})
|
|
||||||
Livewire.on('success', (message) => {
|
|
||||||
if (message) Toaster.success(message)
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
@endauth
|
|
||||||
@guest
|
|
||||||
{{ $slot }}
|
|
||||||
@endguest
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<div class="flex flex-col items-center justify-center h-screen">
|
<div class="flex flex-col items-center justify-center h-screen">
|
||||||
<span class="text-xl font-bold text-white">You have reached the limit of {{ $name }} you can create.</span>
|
<span class="text-xl font-bold text-white">You have reached the limit of {{ $name }} you can create.</span>
|
||||||
<span>Please <a class="text-white underline "href="{{ route('team.show') }}">upgrade your
|
<span>Please <a class="text-white underline "href="{{ route('team.index') }}">upgrade your
|
||||||
subscription<a /> to create more
|
subscription<a /> to create more
|
||||||
{{ $name }}.</span>
|
{{ $name }}.</span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,6 +1,15 @@
|
|||||||
@auth
|
@auth
|
||||||
<nav class="fixed h-full overflow-hidden overflow-y-auto scrollbar">
|
<nav class="fixed h-full overflow-hidden overflow-y-auto scrollbar">
|
||||||
<ul class="flex flex-col h-full gap-4 menu flex-nowrap">
|
<ul class="flex flex-col h-full gap-4 menu flex-nowrap">
|
||||||
|
<li title="Dashboard">
|
||||||
|
<a class="hover:bg-transparent" @if (!request()->is('/')) href="/" @endif>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="{{ request()->is('/') ? 'text-warning icon' : 'icon' }}"
|
||||||
|
fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" />
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
<li class="pb-6" title="Logout">
|
<li class="pb-6" title="Logout">
|
||||||
<form action="/logout" method="POST" class=" hover:bg-transparent">
|
<form action="/logout" method="POST" class=" hover:bg-transparent">
|
||||||
@csrf
|
@csrf
|
||||||
|
@ -51,7 +51,7 @@ class="{{ request()->is('command-center') ? 'text-warning icon' : 'icon' }}" vie
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li title="Teams">
|
<li title="Teams">
|
||||||
<a class="hover:bg-transparent" href="{{ route('team.show') }}">
|
<a class="hover:bg-transparent" href="{{ route('team.index') }}">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 24 24" stroke-width="1.5"
|
<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">
|
stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
|
@ -3,9 +3,9 @@
|
|||||||
])
|
])
|
||||||
<div x-data="{ selected: 'yearly' }" class="w-full pb-20">
|
<div x-data="{ selected: 'yearly' }" class="w-full pb-20">
|
||||||
<div class="px-6 mx-auto lg:px-8">
|
<div class="px-6 mx-auto lg:px-8">
|
||||||
<div class="flex justify-center mt-5">
|
<div class="flex justify-center">
|
||||||
<fieldset
|
<fieldset
|
||||||
class="grid grid-cols-2 p-1 text-xs font-semibold leading-5 text-center rounded-full gap-x-1 ">
|
class="grid grid-cols-2 p-1 text-xs font-semibold leading-5 text-center text-white rounded gap-x-1 bg-white/5">
|
||||||
<legend class="sr-only">Payment frequency</legend>
|
<legend class="sr-only">Payment frequency</legend>
|
||||||
<label class="cursor-pointer rounded px-2.5 py-1"
|
<label class="cursor-pointer rounded px-2.5 py-1"
|
||||||
:class="selected === 'monthly' ? 'bg-coollabs-100 text-white' : ''">
|
:class="selected === 'monthly' ? 'bg-coollabs-100 text-white' : ''">
|
||||||
@ -17,7 +17,7 @@ class="sr-only">
|
|||||||
:class="selected === 'yearly' ? 'bg-coollabs-100 text-white' : ''">
|
:class="selected === 'yearly' ? 'bg-coollabs-100 text-white' : ''">
|
||||||
<input type="radio" x-on:click="selected = 'yearly'" name="frequency" value="annually"
|
<input type="radio" x-on:click="selected = 'yearly'" name="frequency" value="annually"
|
||||||
class="sr-only">
|
class="sr-only">
|
||||||
<span>Annually <span class="text-xs text-warning">(save ~1 month)<span></span>
|
<span>Annually</span>
|
||||||
</label>
|
</label>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</div>
|
</div>
|
||||||
@ -181,7 +181,7 @@ class="grid max-w-sm grid-cols-1 -mt-16 divide-y divide-coolgray-500 isolate gap
|
|||||||
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z"
|
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z"
|
||||||
clip-rule="evenodd" />
|
clip-rule="evenodd" />
|
||||||
</svg>
|
</svg>
|
||||||
5 servers <x-helper helper="Bring Your Own Server. All you need is n SSH connection." />
|
10 servers <x-helper helper="Bring Your Own Server. All you need is n SSH connection." />
|
||||||
</li>
|
</li>
|
||||||
<li class="flex gap-x-3">
|
<li class="flex gap-x-3">
|
||||||
<svg class="flex-none w-5 h-6 text-warning" viewBox="0 0 20 20" fill="currentColor"
|
<svg class="flex-none w-5 h-6 text-warning" viewBox="0 0 20 20" fill="currentColor"
|
||||||
@ -192,6 +192,15 @@ class="grid max-w-sm grid-cols-1 -mt-16 divide-y divide-coolgray-500 isolate gap
|
|||||||
</svg>
|
</svg>
|
||||||
Basic Support
|
Basic Support
|
||||||
</li>
|
</li>
|
||||||
|
<li class="flex gap-x-3">
|
||||||
|
<svg class="flex-none w-5 h-6 text-warning" viewBox="0 0 20 20" fill="currentColor"
|
||||||
|
aria-hidden="true">
|
||||||
|
<path fill-rule="evenodd"
|
||||||
|
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z"
|
||||||
|
clip-rule="evenodd" />
|
||||||
|
</svg>
|
||||||
|
Included Email System
|
||||||
|
</li>
|
||||||
<li class="flex font-bold text-white gap-x-3">
|
<li class="flex font-bold text-white gap-x-3">
|
||||||
<svg width="512" height="512" class="flex-none w-5 h-6 text-green-600"
|
<svg width="512" height="512" class="flex-none w-5 h-6 text-green-600"
|
||||||
viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
@ -229,7 +238,7 @@ class="grid max-w-sm grid-cols-1 -mt-16 divide-y divide-coolgray-500 isolate gap
|
|||||||
{{ $ultimate }}
|
{{ $ultimate }}
|
||||||
@endisset
|
@endisset
|
||||||
@endif
|
@endif
|
||||||
<p class="h-20 mt-10 text-sm leading-6 text-white">Deploy complex infrastuctures and
|
<p class="h-20 mt-10 text-sm leading-6 text-white">Deploy complex infrastructures and
|
||||||
manage them easily in one place.</p>
|
manage them easily in one place.</p>
|
||||||
<ul role="list" class="mt-6 space-y-3 text-sm leading-6 ">
|
<ul role="list" class="mt-6 space-y-3 text-sm leading-6 ">
|
||||||
<li class="flex gap-x-3">
|
<li class="flex gap-x-3">
|
||||||
@ -239,7 +248,7 @@ class="grid max-w-sm grid-cols-1 -mt-16 divide-y divide-coolgray-500 isolate gap
|
|||||||
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z"
|
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z"
|
||||||
clip-rule="evenodd" />
|
clip-rule="evenodd" />
|
||||||
</svg>
|
</svg>
|
||||||
15 servers <x-helper helper="Bring Your Own Server. All you need is n SSH connection." />
|
25 servers <x-helper helper="Bring Your Own Server. All you need is n SSH connection." />
|
||||||
</li>
|
</li>
|
||||||
<li class="flex font-bold text-white gap-x-3">
|
<li class="flex font-bold text-white gap-x-3">
|
||||||
<svg class="flex-none w-5 h-6 text-warning" viewBox="0 0 20 20" fill="currentColor"
|
<svg class="flex-none w-5 h-6 text-warning" viewBox="0 0 20 20" fill="currentColor"
|
||||||
@ -250,6 +259,15 @@ class="grid max-w-sm grid-cols-1 -mt-16 divide-y divide-coolgray-500 isolate gap
|
|||||||
</svg>
|
</svg>
|
||||||
Priority Support
|
Priority Support
|
||||||
</li>
|
</li>
|
||||||
|
<li class="flex gap-x-3">
|
||||||
|
<svg class="flex-none w-5 h-6 text-warning" viewBox="0 0 20 20" fill="currentColor"
|
||||||
|
aria-hidden="true">
|
||||||
|
<path fill-rule="evenodd"
|
||||||
|
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z"
|
||||||
|
clip-rule="evenodd" />
|
||||||
|
</svg>
|
||||||
|
Included Email System
|
||||||
|
</li>
|
||||||
<li class="flex font-bold text-white gap-x-3">
|
<li class="flex font-bold text-white gap-x-3">
|
||||||
<svg width="512" height="512" class="flex-none w-5 h-6 text-green-600"
|
<svg width="512" height="512" class="flex-none w-5 h-6 text-green-600"
|
||||||
viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
href="{{ route('settings.configuration') }}">
|
href="{{ route('settings.configuration') }}">
|
||||||
<button>Configuration</button>
|
<button>Configuration</button>
|
||||||
</a>
|
</a>
|
||||||
@if (is_cloud())
|
@if (isCloud())
|
||||||
<a class="{{ request()->routeIs('settings.license') ? 'text-white' : '' }}"
|
<a class="{{ request()->routeIs('settings.license') ? 'text-white' : '' }}"
|
||||||
href="{{ route('settings.license') }}">
|
href="{{ route('settings.license') }}">
|
||||||
<button>Resale License</button>
|
<button>Resale License</button>
|
||||||
|
@ -4,24 +4,14 @@
|
|||||||
<ol class="inline-flex items-center">
|
<ol class="inline-flex items-center">
|
||||||
<li>
|
<li>
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<span>Currently active team: {{ session('currentTeam.name') }}</span>
|
<span>Currently active team: <span
|
||||||
|
class="text-warning">{{ session('currentTeam.name') }}</span></span>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
@if (session('currentTeam.description'))
|
|
||||||
<li class="inline-flex items-center">
|
|
||||||
<svg aria-hidden="true" class="w-4 h-4 mx-1 font-bold text-warning" fill="currentColor"
|
|
||||||
viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path fill-rule="evenodd"
|
|
||||||
d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"
|
|
||||||
clip-rule="evenodd"></path>
|
|
||||||
</svg>
|
|
||||||
<span class="truncate">{{ Str::limit(session('currentTeam.description'), 52) }}</span>
|
|
||||||
</li>
|
|
||||||
@endif
|
|
||||||
</ol>
|
</ol>
|
||||||
</nav>
|
</nav>
|
||||||
<nav class="navbar-main">
|
<nav class="navbar-main">
|
||||||
<a class="{{ request()->routeIs('team.show') ? 'text-white' : '' }}" href="{{ route('team.show') }}">
|
<a class="{{ request()->routeIs('team.index') ? 'text-white' : '' }}" href="{{ route('team.index') }}">
|
||||||
<button>General</button>
|
<button>General</button>
|
||||||
</a>
|
</a>
|
||||||
<a class="{{ request()->routeIs('team.members') ? 'text-white' : '' }}" href="{{ route('team.members') }}">
|
<a class="{{ request()->routeIs('team.members') ? 'text-white' : '' }}" href="{{ route('team.members') }}">
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
Someone added this email to the Coolify Cloud's waitlist.
|
Someone added this email to the Coolify Cloud's waitlist.
|
||||||
<br>
|
<br>
|
||||||
<a href="{{ $confirmation_url }}">Click here to confirm</a>! The link will expire in {{config('constants.waitlist.confirmation_valid_for_minutes')}} minutes.<br><br>
|
<a href="{{ $confirmation_url }}">Click here to confirm</a>! The link will expire in {{config('constants.waitlist.expiration')}} minutes.<br><br>
|
||||||
You have no idea what <a href="https://coolify.io">Coolify Cloud</a> is or this waitlist? <a href="{{ $cancel_url }}">Click here to remove</a> you from the waitlist.
|
You have no idea what <a href="https://coolify.io">Coolify Cloud</a> is or this waitlist? <a href="{{ $cancel_url }}">Click here to remove</a> you from the waitlist.
|
||||||
|
@ -1,14 +1,13 @@
|
|||||||
Congratulations!<br>
|
|
||||||
Congratulations!<br>
|
|
||||||
<br>
|
|
||||||
You have been invited to join the Coolify Cloud. <a href="{{base_url()}}/login">Login here</a>
|
You have been invited to join the Coolify Cloud. <a href="{{base_url()}}/login">Login here</a>
|
||||||
<br>
|
<br>
|
||||||
<br>
|
<br>
|
||||||
Credentials:
|
Here is your initial login information.
|
||||||
<br>
|
|
||||||
Email: {{ $email }}
|
|
||||||
<br>
|
|
||||||
Password: {{ $password }}
|
|
||||||
<br>
|
<br>
|
||||||
|
Email: <br>
|
||||||
|
{{ $email }}
|
||||||
|
<br><br>
|
||||||
|
Password:<br>
|
||||||
|
{{ $password }}
|
||||||
|
<br><br>
|
||||||
(You will forced to change it on first login.)
|
(You will forced to change it on first login.)
|
||||||
|
|
||||||
|
11
resources/views/layouts/app.blade.php
Normal file
11
resources/views/layouts/app.blade.php
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
@extends('layouts.base')
|
||||||
|
@section('body')
|
||||||
|
@parent
|
||||||
|
<x-navbar />
|
||||||
|
<div class="fixed top-3 left-4" id="vue">
|
||||||
|
<magic-bar></magic-bar>
|
||||||
|
</div>
|
||||||
|
<main class="main max-w-screen-2xl">
|
||||||
|
{{ $slot }}
|
||||||
|
</main>
|
||||||
|
@endsection
|
83
resources/views/layouts/base.blade.php
Normal file
83
resources/views/layouts/base.blade.php
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html data-theme="coollabs" lang="{{ str_replace('_', '-', app()->getLocale()) }}">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<link rel="preconnect" href="https://api.fonts.coollabs.io" crossorigin>
|
||||||
|
<link href="https://api.fonts.coollabs.io/css2?family=Inter&display=swap" rel="stylesheet">
|
||||||
|
<title>Coolify</title>
|
||||||
|
@env('local')
|
||||||
|
<link rel="icon" href="{{ asset('favicon-dev.png') }}" type="image/x-icon" />
|
||||||
|
@else
|
||||||
|
<link rel="icon" href="{{ asset('coolify-transparent.png') }}" type="image/x-icon" />
|
||||||
|
@endenv
|
||||||
|
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||||
|
@vite(['resources/js/app.js', 'resources/css/app.css'])
|
||||||
|
<style>
|
||||||
|
[x-cloak] {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
@livewireStyles
|
||||||
|
</head>
|
||||||
|
@section('body')
|
||||||
|
|
||||||
|
<body>
|
||||||
|
@livewireScripts
|
||||||
|
<x-toaster-hub />
|
||||||
|
<x-version class="fixed left-2 bottom-1" />
|
||||||
|
<script>
|
||||||
|
let checkHealthInterval = null;
|
||||||
|
let checkIfIamDeadInterval = null;
|
||||||
|
|
||||||
|
function changePasswordFieldType(event) {
|
||||||
|
let element = event.target
|
||||||
|
for (let i = 0; i < 10; i++) {
|
||||||
|
if (element.className === "relative") {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
element = element.parentElement;
|
||||||
|
}
|
||||||
|
element = element.children[1];
|
||||||
|
if (element.nodeName === 'INPUT') {
|
||||||
|
if (element.type === 'password') {
|
||||||
|
element.type = 'text';
|
||||||
|
} else {
|
||||||
|
element.type = 'password';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function copyToClipboard(text) {
|
||||||
|
navigator.clipboard.writeText(text);
|
||||||
|
Livewire.emit('success', 'Copied to clipboard.');
|
||||||
|
}
|
||||||
|
|
||||||
|
Livewire.on('reloadWindow', (timeout) => {
|
||||||
|
if (timeout) {
|
||||||
|
setTimeout(() => {
|
||||||
|
window.location.reload();
|
||||||
|
}, timeout);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
Livewire.on('info', (message) => {
|
||||||
|
if (message) Toaster.info(message)
|
||||||
|
})
|
||||||
|
Livewire.on('error', (message) => {
|
||||||
|
if (message) Toaster.error(message)
|
||||||
|
})
|
||||||
|
Livewire.on('warning', (message) => {
|
||||||
|
if (message) Toaster.warning(message)
|
||||||
|
})
|
||||||
|
Livewire.on('success', (message) => {
|
||||||
|
if (message) Toaster.success(message)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
@show
|
||||||
|
|
||||||
|
</html>
|
9
resources/views/layouts/boarding.blade.php
Normal file
9
resources/views/layouts/boarding.blade.php
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
@extends('layouts.base')
|
||||||
|
@section('body')
|
||||||
|
<main class="min-h-screen hero">
|
||||||
|
<div class="hero-content">
|
||||||
|
{{ $slot }}
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
@parent
|
||||||
|
@endsection
|
7
resources/views/layouts/simple.blade.php
Normal file
7
resources/views/layouts/simple.blade.php
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
@extends('layouts.base')
|
||||||
|
@section('body')
|
||||||
|
@parent
|
||||||
|
<main>
|
||||||
|
{{ $slot }}
|
||||||
|
</main>
|
||||||
|
@endsection
|
16
resources/views/layouts/subscription.blade.php
Normal file
16
resources/views/layouts/subscription.blade.php
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
@extends('layouts.base')
|
||||||
|
@section('body')
|
||||||
|
@parent
|
||||||
|
@if (isSubscriptionOnGracePeriod())
|
||||||
|
<div class="fixed top-3 left-4" id="vue">
|
||||||
|
<magic-bar></magic-bar>
|
||||||
|
</div>
|
||||||
|
<x-navbar />
|
||||||
|
@else
|
||||||
|
<x-navbar-subscription />
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<main class="main max-w-screen-2xl">
|
||||||
|
{{ $slot }}
|
||||||
|
</main>
|
||||||
|
@endsection
|
@ -1,195 +0,0 @@
|
|||||||
@php use App\Enums\ProxyTypes; @endphp
|
|
||||||
<div class="min-h-screen hero">
|
|
||||||
<div class="hero-content">
|
|
||||||
<div>
|
|
||||||
@if ($currentState === 'welcome')
|
|
||||||
<h1 class="text-5xl font-bold">Welcome to Coolify</h1>
|
|
||||||
<p class="py-6 text-xl text-center">Let me help you to set the basics.</p>
|
|
||||||
<div class="flex justify-center ">
|
|
||||||
<div class="justify-center box" wire:click="$set('currentState', 'select-server')">Get Started
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@endif
|
|
||||||
@if ($currentState === 'select-server')
|
|
||||||
<x-boarding-step title="Server">
|
|
||||||
<x-slot:question>
|
|
||||||
Do you want to deploy your resources on your <x-highlighted text="Localhost" />
|
|
||||||
or on a <x-highlighted text="Remote Server" />?
|
|
||||||
</x-slot:question>
|
|
||||||
<x-slot:actions>
|
|
||||||
<div class="justify-center box" wire:click="setServer('localhost')">Localhost
|
|
||||||
</div>
|
|
||||||
<div class="justify-center box" wire:click="setServer('remote')">Remote Server
|
|
||||||
</div>
|
|
||||||
</x-slot:actions>
|
|
||||||
<x-slot:explanation>
|
|
||||||
<p>Servers are the main building blocks, as they will host your applications, databases,
|
|
||||||
services, called resources. Any CPU intensive process will use the server's CPU where you
|
|
||||||
are deploying your resources.</p>
|
|
||||||
<p>Localhost is the server where Coolify is running on. It is not recommended to use one server
|
|
||||||
for everyting.</p>
|
|
||||||
<p>Remote Server is a server reachable through SSH. It can be hosted at home, or from any cloud
|
|
||||||
provider.</p>
|
|
||||||
</x-slot:explanation>
|
|
||||||
</x-boarding-step>
|
|
||||||
@endif
|
|
||||||
@if ($currentState === 'private-key')
|
|
||||||
<x-boarding-step title="SSH Key">
|
|
||||||
<x-slot:question>
|
|
||||||
Do you have your own SSH Private Key?
|
|
||||||
</x-slot:question>
|
|
||||||
<x-slot:actions>
|
|
||||||
<div class="justify-center box" wire:click="setPrivateKey('own')">Yes
|
|
||||||
</div>
|
|
||||||
<div class="justify-center box" wire:click="setPrivateKey('create')">No (create one for me)
|
|
||||||
</div>
|
|
||||||
</x-slot:actions>
|
|
||||||
<x-slot:explanation>
|
|
||||||
<p>SSH Keys are used to connect to a remote server through a secure shell, called SSH.</p>
|
|
||||||
<p>You can use your own ssh private key, or you can let Coolify to create one for you.</p>
|
|
||||||
<p>In both ways, you need to add the public version of your ssh private key to the remote
|
|
||||||
server's
|
|
||||||
<code class="text-warning">~/.ssh/authorized_keys</code> file.
|
|
||||||
</p>
|
|
||||||
</x-slot:explanation>
|
|
||||||
</x-boarding-step>
|
|
||||||
@endif
|
|
||||||
@if ($currentState === 'create-private-key')
|
|
||||||
<x-boarding-step title="Create Private Key">
|
|
||||||
<x-slot:question>
|
|
||||||
Please let me know your key details.
|
|
||||||
</x-slot:question>
|
|
||||||
<x-slot:actions>
|
|
||||||
<form wire:submit.prevent='savePrivateKey' class="flex flex-col w-full gap-4 pr-10">
|
|
||||||
<x-forms.input required placeholder="Choose a name for your Private Key. Could be anything."
|
|
||||||
label="Name" id="privateKeyName" />
|
|
||||||
<x-forms.input placeholder="Description, so others will know more about this."
|
|
||||||
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" />
|
|
||||||
@endif
|
|
||||||
<x-forms.button type="submit">Save</x-forms.button>
|
|
||||||
</form>
|
|
||||||
</x-slot:actions>
|
|
||||||
<x-slot:explanation>
|
|
||||||
<p>Private Keys are used to connect to a remote server through a secure shell, called SSH.</p>
|
|
||||||
<p>You can use your own private key, or you can let Coolify to create one for you.</p>
|
|
||||||
<p>In both ways, you need to add the public version of your private key to the remote server's
|
|
||||||
<code>~/.ssh/authorized_keys</code> file.
|
|
||||||
</p>
|
|
||||||
</x-slot:explanation>
|
|
||||||
</x-boarding-step>
|
|
||||||
@endif
|
|
||||||
@if ($currentState === 'create-server')
|
|
||||||
<x-boarding-step title="Create Server">
|
|
||||||
<x-slot:question>
|
|
||||||
Please let me know your server details.
|
|
||||||
</x-slot:question>
|
|
||||||
<x-slot:actions>
|
|
||||||
<form wire:submit.prevent='saveServer' class="flex flex-col w-full gap-4 pr-10">
|
|
||||||
<div class="flex gap-2">
|
|
||||||
<x-forms.input required placeholder="Choose a name for your Server. Could be anything."
|
|
||||||
label="Name" id="remoteServerName" />
|
|
||||||
<x-forms.input placeholder="Description, so others will know more about this."
|
|
||||||
label="Description" id="remoteServerDescription" />
|
|
||||||
</div>
|
|
||||||
<div class="flex gap-2">
|
|
||||||
<x-forms.input required placeholder="Hostname or IP address"
|
|
||||||
label="Hostname or IP Address" id="remoteServerHost" />
|
|
||||||
<x-forms.input required placeholder="Port number of your server. Default is 22."
|
|
||||||
label="Port" id="remoteServerPort" />
|
|
||||||
<x-forms.input required readonly
|
|
||||||
placeholder="Username to connect to your server. Default is root." label="Username"
|
|
||||||
id="remoteServerUser" />
|
|
||||||
</div>
|
|
||||||
<x-forms.button type="submit">Save</x-forms.button>
|
|
||||||
</form>
|
|
||||||
</x-slot:actions>
|
|
||||||
<x-slot:explanation>
|
|
||||||
<p>Username should be <x-highlighted text="root" /> for now. We are working on to use
|
|
||||||
non-root users.</p>
|
|
||||||
</x-slot:explanation>
|
|
||||||
</x-boarding-step>
|
|
||||||
@endif
|
|
||||||
@if ($currentState === 'install-docker')
|
|
||||||
<x-boarding-step title="Install Docker">
|
|
||||||
<x-slot:question>
|
|
||||||
Could not find Docker Engine on your server. Do you want me to install it for you?
|
|
||||||
</x-slot:question>
|
|
||||||
<x-slot:actions>
|
|
||||||
<div class="justify-center box" wire:click="installDocker" onclick="installDocker.showModal()">
|
|
||||||
Let's do
|
|
||||||
it!</div>
|
|
||||||
</x-slot:actions>
|
|
||||||
<x-slot:explanation>
|
|
||||||
<p>This will install the latest Docker Engine on your server, configure a few things to be able
|
|
||||||
to run optimal.</p>
|
|
||||||
</x-slot:explanation>
|
|
||||||
</x-boarding-step>
|
|
||||||
@endif
|
|
||||||
@if ($currentState === 'select-proxy')
|
|
||||||
<x-boarding-step title="Select a Proxy">
|
|
||||||
<x-slot:question>
|
|
||||||
If you would like to attach any kind of domain to your resources, you need a proxy.
|
|
||||||
</x-slot:question>
|
|
||||||
<x-slot:actions>
|
|
||||||
<x-forms.button wire:click="selectProxy" class="w-64 box">
|
|
||||||
Decide later
|
|
||||||
</x-forms.button>
|
|
||||||
<x-forms.button class="w-32 box" wire:click="selectProxy('{{ ProxyTypes::TRAEFIK_V2 }}')">
|
|
||||||
Traefik
|
|
||||||
v2
|
|
||||||
</x-forms.button>
|
|
||||||
<x-forms.button disabled class="w-32 box">
|
|
||||||
Nginx
|
|
||||||
</x-forms.button>
|
|
||||||
<x-forms.button disabled class="w-32 box">
|
|
||||||
Caddy
|
|
||||||
</x-forms.button>
|
|
||||||
</x-slot:actions>
|
|
||||||
<x-slot:explanation>
|
|
||||||
<p>This will install the latest Docker Engine on your server, configure a few things to be able
|
|
||||||
to run optimal.</p>
|
|
||||||
</x-slot:explanation>
|
|
||||||
</x-boarding-step>
|
|
||||||
@endif
|
|
||||||
@if ($currentState === 'create-project')
|
|
||||||
<x-boarding-step title="Project">
|
|
||||||
<x-slot:question>
|
|
||||||
I will create an initial project for you. You can change all the details later on.
|
|
||||||
</x-slot:question>
|
|
||||||
<x-slot:actions>
|
|
||||||
<div class="justify-center box" wire:click="createNewProject">Let's do it!</div>
|
|
||||||
</x-slot:actions>
|
|
||||||
<x-slot:explanation>
|
|
||||||
<p>Projects are bound together several resources into one virtual group. There are no
|
|
||||||
limitations on the number of projects you could have.</p>
|
|
||||||
<p>Each project should have at least one environment. This helps you to create a production &
|
|
||||||
staging version of the same application, but grouped separately.</p>
|
|
||||||
</x-slot:explanation>
|
|
||||||
</x-boarding-step>
|
|
||||||
@endif
|
|
||||||
@if ($currentState === 'create-resource')
|
|
||||||
<x-boarding-step title="Resources">
|
|
||||||
<x-slot:question>
|
|
||||||
I will redirect you to the new resource page, where you can create your first resource.
|
|
||||||
</x-slot:question>
|
|
||||||
<x-slot:actions>
|
|
||||||
<div class="justify-center box" wire:click="showNewResource">Let's do
|
|
||||||
it!</div>
|
|
||||||
</x-slot:actions>
|
|
||||||
<x-slot:explanation>
|
|
||||||
<p>A resource could be an application, a database or a service (like WordPress).</p>
|
|
||||||
</x-slot:explanation>
|
|
||||||
</x-boarding-step>
|
|
||||||
@endif
|
|
||||||
<div class="flex justify-center gap-2 pt-4">
|
|
||||||
<a wire:click='skipBoarding'>Skip boarding process</a>
|
|
||||||
<a wire:click='restartBoarding'>Restart boarding process</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
281
resources/views/livewire/boarding/index.blade.php
Normal file
281
resources/views/livewire/boarding/index.blade.php
Normal file
@ -0,0 +1,281 @@
|
|||||||
|
@php use App\Enums\ProxyTypes; @endphp
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
@if ($currentState === 'welcome')
|
||||||
|
<h1 class="text-5xl font-bold">Welcome to Coolify</h1>
|
||||||
|
<p class="py-6 text-xl text-center">Let me help you to set the basics.</p>
|
||||||
|
<div class="flex justify-center ">
|
||||||
|
<x-forms.button class="justify-center box" wire:click="welcome">Get Started
|
||||||
|
</x-forms.button>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
@if ($currentState === 'select-server-type')
|
||||||
|
<x-boarding-step title="Server">
|
||||||
|
<x-slot:question>
|
||||||
|
Do you want to deploy your resources on your <x-highlighted text="Localhost" />
|
||||||
|
or on a <x-highlighted text="Remote Server" />?
|
||||||
|
</x-slot:question>
|
||||||
|
<x-slot:actions>
|
||||||
|
<x-forms.button class="justify-center box" wire:target="setServerType('localhost')" wire:click="setServerType('localhost')">Localhost
|
||||||
|
</x-forms.button>
|
||||||
|
<x-forms.button class="justify-center box" wire:target="setServerType('remote')" wire:click="setServerType('remote')">Remote Server
|
||||||
|
</x-forms.button>
|
||||||
|
</x-slot:actions>
|
||||||
|
<x-slot:explanation>
|
||||||
|
<p>Servers are the main building blocks, as they will host your applications, databases,
|
||||||
|
services, called resources. Any CPU intensive process will use the server's CPU where you
|
||||||
|
are deploying your resources.</p>
|
||||||
|
<p>Localhost is the server where Coolify is running on. It is not recommended to use one server
|
||||||
|
for everyting.</p>
|
||||||
|
<p>Remote Server is a server reachable through SSH. It can be hosted at home, or from any cloud
|
||||||
|
provider.</p>
|
||||||
|
</x-slot:explanation>
|
||||||
|
</x-boarding-step>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
@if ($currentState === 'private-key')
|
||||||
|
<x-boarding-step title="SSH Key">
|
||||||
|
<x-slot:question>
|
||||||
|
Do you have your own SSH Private Key?
|
||||||
|
</x-slot:question>
|
||||||
|
<x-slot:actions>
|
||||||
|
<x-forms.button class="justify-center box" wire:target="setPrivateKey('own')" wire:click="setPrivateKey('own')">Yes
|
||||||
|
</x-forms.button>
|
||||||
|
<x-forms.button class="justify-center box" wire:target="setPrivateKey('create')" wire:click="setPrivateKey('create')">No (create one for me)
|
||||||
|
</x-forms.button>
|
||||||
|
@if (count($privateKeys) > 0)
|
||||||
|
<form wire:submit.prevent='selectExistingPrivateKey' class="flex flex-col w-full gap-4 pr-10">
|
||||||
|
<x-forms.select label="Existing SSH Keys" id='selectedExistingPrivateKey'>
|
||||||
|
@foreach ($privateKeys as $privateKey)
|
||||||
|
<option wire:key="{{ $loop->index }}" value="{{ $privateKey->id }}">
|
||||||
|
{{ $privateKey->name }}</option>
|
||||||
|
@endforeach
|
||||||
|
</x-forms.select>
|
||||||
|
<x-forms.button type="submit">Use this SSH Key</x-forms.button>
|
||||||
|
</form>
|
||||||
|
@endif
|
||||||
|
</x-slot:actions>
|
||||||
|
<x-slot:explanation>
|
||||||
|
<p>SSH Keys are used to connect to a remote server through a secure shell, called SSH.</p>
|
||||||
|
<p>You can use your own ssh private key, or you can let Coolify to create one for you.</p>
|
||||||
|
<p>In both ways, you need to add the public version of your ssh private key to the remote
|
||||||
|
server's
|
||||||
|
<code class="text-warning">~/.ssh/authorized_keys</code> file.
|
||||||
|
</p>
|
||||||
|
</x-slot:explanation>
|
||||||
|
</x-boarding-step>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
@if ($currentState === 'select-existing-server')
|
||||||
|
<x-boarding-step title="Select a server">
|
||||||
|
<x-slot:question>
|
||||||
|
There are already servers available for your Team. Do you want to use one of them?
|
||||||
|
</x-slot:question>
|
||||||
|
<x-slot:actions>
|
||||||
|
<x-forms.button class="justify-center box" wire:click="createNewServer">No (create one for me)
|
||||||
|
</x-forms.button>
|
||||||
|
<div>
|
||||||
|
<form wire:submit.prevent='selectExistingServer' class="flex flex-col w-full gap-4 lg:w-96">
|
||||||
|
<x-forms.select label="Existing servers" class="w-96" id='selectedExistingServer'>
|
||||||
|
@foreach ($servers as $server)
|
||||||
|
<option wire:key="{{ $loop->index }}" value="{{ $server->id }}">
|
||||||
|
{{ $server->name }}</option>
|
||||||
|
@endforeach
|
||||||
|
</x-forms.select>
|
||||||
|
<x-forms.button type="submit">Use this Server</x-forms.button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</x-slot:actions>
|
||||||
|
<x-slot:explanation>
|
||||||
|
<p>Private Keys are used to connect to a remote server through a secure shell, called SSH.</p>
|
||||||
|
<p>You can use your own private key, or you can let Coolify to create one for you.</p>
|
||||||
|
<p>In both ways, you need to add the public version of your private key to the remote server's
|
||||||
|
<code>~/.ssh/authorized_keys</code> file.
|
||||||
|
</p>
|
||||||
|
</x-slot:explanation>
|
||||||
|
</x-boarding-step>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
@if ($currentState === 'create-private-key')
|
||||||
|
<x-boarding-step title="Create Private Key">
|
||||||
|
<x-slot:question>
|
||||||
|
Please let me know your key details.
|
||||||
|
</x-slot:question>
|
||||||
|
<x-slot:actions>
|
||||||
|
<form wire:submit.prevent='savePrivateKey' class="flex flex-col w-full gap-4 pr-10">
|
||||||
|
<x-forms.input required placeholder="Choose a name for your Private Key. Could be anything."
|
||||||
|
label="Name" id="privateKeyName" />
|
||||||
|
<x-forms.input placeholder="Description, so others will know more about this."
|
||||||
|
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" />
|
||||||
|
@endif
|
||||||
|
<x-forms.button type="submit">Save</x-forms.button>
|
||||||
|
</form>
|
||||||
|
</x-slot:actions>
|
||||||
|
<x-slot:explanation>
|
||||||
|
<p>Private Keys are used to connect to a remote server through a secure shell, called SSH.</p>
|
||||||
|
<p>You can use your own private key, or you can let Coolify to create one for you.</p>
|
||||||
|
<p>In both ways, you need to add the public version of your private key to the remote server's
|
||||||
|
<code>~/.ssh/authorized_keys</code> file.
|
||||||
|
</p>
|
||||||
|
</x-slot:explanation>
|
||||||
|
</x-boarding-step>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
@if ($currentState === 'create-server')
|
||||||
|
<x-boarding-step title="Create Server">
|
||||||
|
<x-slot:question>
|
||||||
|
Please let me know your server details.
|
||||||
|
</x-slot:question>
|
||||||
|
<x-slot:actions>
|
||||||
|
<form wire:submit.prevent='saveServer' class="flex flex-col w-full gap-4 pr-10">
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<x-forms.input required placeholder="Choose a name for your Server. Could be anything."
|
||||||
|
label="Name" id="remoteServerName" />
|
||||||
|
<x-forms.input placeholder="Description, so others will know more about this."
|
||||||
|
label="Description" id="remoteServerDescription" />
|
||||||
|
</div>
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<x-forms.input required placeholder="Hostname or IP address" label="Hostname or IP Address"
|
||||||
|
id="remoteServerHost" />
|
||||||
|
<x-forms.input required placeholder="Port number of your server. Default is 22."
|
||||||
|
label="Port" id="remoteServerPort" />
|
||||||
|
<x-forms.input required readonly
|
||||||
|
placeholder="Username to connect to your server. Default is root." label="Username"
|
||||||
|
id="remoteServerUser" />
|
||||||
|
</div>
|
||||||
|
<x-forms.button type="submit">Save</x-forms.button>
|
||||||
|
</form>
|
||||||
|
</x-slot:actions>
|
||||||
|
<x-slot:explanation>
|
||||||
|
<p>Username should be <x-highlighted text="root" /> for now. We are working on to use
|
||||||
|
non-root users.</p>
|
||||||
|
</x-slot:explanation>
|
||||||
|
</x-boarding-step>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
@if ($currentState === 'install-docker')
|
||||||
|
<x-modal modalId="installDocker">
|
||||||
|
<x-slot:modalBody>
|
||||||
|
<livewire:activity-monitor header="Docker Installation Logs" />
|
||||||
|
</x-slot:modalBody>
|
||||||
|
<x-slot:modalSubmit>
|
||||||
|
<x-forms.button onclick="installDocker.close()" type="submit">
|
||||||
|
Close
|
||||||
|
</x-forms.button>
|
||||||
|
</x-slot:modalSubmit>
|
||||||
|
</x-modal>
|
||||||
|
<x-boarding-step title="Install Docker">
|
||||||
|
<x-slot:question>
|
||||||
|
Could not find Docker Engine on your server. Do you want me to install it for you?
|
||||||
|
</x-slot:question>
|
||||||
|
<x-slot:actions>
|
||||||
|
<x-forms.button class="justify-center box" wire:click="installDocker" onclick="installDocker.showModal()">
|
||||||
|
Let's do
|
||||||
|
it!</x-forms.button>
|
||||||
|
</x-slot:actions>
|
||||||
|
<x-slot:explanation>
|
||||||
|
<p>This will install the latest Docker Engine on your server, configure a few things to be able
|
||||||
|
to run optimal.</p>
|
||||||
|
</x-slot:explanation>
|
||||||
|
</x-boarding-step>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
@if ($currentState === 'select-proxy')
|
||||||
|
<x-boarding-step title="Select a Proxy">
|
||||||
|
<x-slot:question>
|
||||||
|
If you would like to attach any kind of domain to your resources, you need a proxy.
|
||||||
|
</x-slot:question>
|
||||||
|
<x-slot:actions>
|
||||||
|
<x-forms.button wire:click="selectProxy" class="w-64 box">
|
||||||
|
Decide later
|
||||||
|
</x-forms.button>
|
||||||
|
<x-forms.button class="w-32 box" wire:click="selectProxy('{{ ProxyTypes::TRAEFIK_V2 }}')">
|
||||||
|
Traefik
|
||||||
|
v2
|
||||||
|
</x-forms.button>
|
||||||
|
<x-forms.button disabled class="w-32 box">
|
||||||
|
Nginx
|
||||||
|
</x-forms.button>
|
||||||
|
<x-forms.button disabled class="w-32 box">
|
||||||
|
Caddy
|
||||||
|
</x-forms.button>
|
||||||
|
</x-slot:actions>
|
||||||
|
<x-slot:explanation>
|
||||||
|
<p>This will install the latest Docker Engine on your server, configure a few things to be able
|
||||||
|
to run optimal.</p>
|
||||||
|
</x-slot:explanation>
|
||||||
|
</x-boarding-step>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
@if ($currentState === 'create-project')
|
||||||
|
<x-boarding-step title="Project">
|
||||||
|
<x-slot:question>
|
||||||
|
@if (count($projects) > 0)
|
||||||
|
You already have some projects. Do you want to use one of them or should I create a new one for
|
||||||
|
you?
|
||||||
|
@else
|
||||||
|
I will create an initial project for you. You can change all the details later on.
|
||||||
|
@endif
|
||||||
|
</x-slot:question>
|
||||||
|
<x-slot:actions>
|
||||||
|
<x-forms.button class="justify-center box" wire:click="createNewProject">Let's create a new one!</x-forms.button>
|
||||||
|
<div>
|
||||||
|
@if (count($projects) > 0)
|
||||||
|
<form wire:submit.prevent='selectExistingProject'
|
||||||
|
class="flex flex-col w-full gap-4 lg:w-96">
|
||||||
|
<x-forms.select label="Existing projects" class="w-96" id='selectedExistingProject'>
|
||||||
|
@foreach ($projects as $project)
|
||||||
|
<option wire:key="{{ $loop->index }}" value="{{ $project->id }}">
|
||||||
|
{{ $project->name }}</option>
|
||||||
|
@endforeach
|
||||||
|
</x-forms.select>
|
||||||
|
<x-forms.button type="submit">Use this Project</x-forms.button>
|
||||||
|
</form>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</x-slot:actions>
|
||||||
|
<x-slot:explanation>
|
||||||
|
<p>Projects are bound together several resources into one virtual group. There are no
|
||||||
|
limitations on the number of projects you could have.</p>
|
||||||
|
<p>Each project should have at least one environment. This helps you to create a production &
|
||||||
|
staging version of the same application, but grouped separately.</p>
|
||||||
|
</x-slot:explanation>
|
||||||
|
</x-boarding-step>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
@if ($currentState === 'create-resource')
|
||||||
|
<x-boarding-step title="Resources">
|
||||||
|
<x-slot:question>
|
||||||
|
I will redirect you to the new resource page, where you can create your first resource.
|
||||||
|
</x-slot:question>
|
||||||
|
<x-slot:actions>
|
||||||
|
<div class="justify-center box" wire:click="showNewResource">Let's do
|
||||||
|
it!</div>
|
||||||
|
</x-slot:actions>
|
||||||
|
<x-slot:explanation>
|
||||||
|
<p>A resource could be an application, a database or a service (like WordPress).</p>
|
||||||
|
</x-slot:explanation>
|
||||||
|
</x-boarding-step>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-center gap-2 pt-4">
|
||||||
|
<a wire:click='skipBoarding'>Skip boarding process</a>
|
||||||
|
<a wire:click='restartBoarding'>Restart boarding process</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -1,6 +1,19 @@
|
|||||||
<x-layout>
|
<div>
|
||||||
|
@if (session('error'))
|
||||||
|
<span x-data x-init="$wire.emit('error', '{{ session('error') }}')" />
|
||||||
|
@endif
|
||||||
<h1>Dashboard</h1>
|
<h1>Dashboard</h1>
|
||||||
<div class="subtitle">Something <x-highlighted text="(more)" /> useful will be here.</div>
|
<div class="subtitle">Something <x-highlighted text="(more)" /> useful will be here.</div>
|
||||||
|
@if (request()->query->get('success'))
|
||||||
|
<div class="rounded alert alert-success">
|
||||||
|
<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="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||||
|
</svg>
|
||||||
|
<span>Your subscription has been activated! Welcome onboard!</span>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
<div class="w-full rounded stats stats-vertical lg:stats-horizontal">
|
<div class="w-full rounded stats stats-vertical lg:stats-horizontal">
|
||||||
<div class="stat">
|
<div class="stat">
|
||||||
<div class="stat-title">Servers</div>
|
<div class="stat-title">Servers</div>
|
||||||
@ -19,10 +32,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="stat">
|
<div class="stat">
|
||||||
<div class="stat-title">S3 Storages</div>
|
<div class="stat-title">S3 Storages</div>
|
||||||
<div class="stat-value">{{ $s3s->count() }}</div>
|
<div class="stat-value">{{ $s3s }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@if (isDev())
|
|
||||||
{{-- <livewire:dev.s3-test /> --}}
|
</div>
|
||||||
@endif
|
|
||||||
</x-layout>
|
|
@ -16,59 +16,106 @@
|
|||||||
<x-forms.button type="submit">
|
<x-forms.button type="submit">
|
||||||
Save
|
Save
|
||||||
</x-forms.button>
|
</x-forms.button>
|
||||||
@if (isInstanceAdmin())
|
@if (isInstanceAdmin() && !$team->use_instance_email_settings)
|
||||||
<x-forms.button wire:click='copyFromInstanceSettings'>
|
<x-forms.button wire:click='copyFromInstanceSettings'>
|
||||||
Copy from Instance Settings
|
Copy from Instance Settings
|
||||||
</x-forms.button>
|
</x-forms.button>
|
||||||
@endif
|
@endif
|
||||||
@if ($model->smtp_enabled)
|
@if (isEmailEnabled($team) || data_get($team, 'use_instance_email_settings'))
|
||||||
<x-forms.button onclick="sendTestEmail.showModal()"
|
<x-forms.button onclick="sendTestEmail.showModal()"
|
||||||
class="text-white normal-case btn btn-xs no-animation btn-primary">
|
class="text-white normal-case btn btn-xs no-animation btn-primary">
|
||||||
Send Test Email
|
Send Test Email
|
||||||
</x-forms.button>
|
</x-forms.button>
|
||||||
@endif
|
@endif
|
||||||
</div>
|
|
||||||
<div class="w-48">
|
|
||||||
<x-forms.checkbox instantSave id="model.smtp_enabled" label="Notification Enabled" />
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col gap-4">
|
|
||||||
<div class="flex flex-col gap-2 xl:flex-row">
|
|
||||||
<x-forms.input id="model.smtp_recipients"
|
|
||||||
placeholder="If empty, all users will be notified in the team."
|
|
||||||
helper="Email list to send the all notifications to, separated by comma." label="Recipients" />
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col gap-2 xl:flex-row">
|
|
||||||
<x-forms.input required id="model.smtp_host" helper="SMTP Hostname" placeholder="smtp.mailgun.org"
|
|
||||||
label="Host" />
|
|
||||||
<x-forms.input required id="model.smtp_port" helper="SMTP Port" placeholder="587" label="Port" />
|
|
||||||
<x-forms.input helper="If SMTP through SSL, set it to 'tls'." placeholder="tls"
|
|
||||||
id="model.smtp_encryption" label="Encryption" />
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col gap-2 xl:flex-row">
|
|
||||||
<x-forms.input id="model.smtp_username" label="SMTP Username" />
|
|
||||||
<x-forms.input type="password" id="model.smtp_password" label="SMTP Password" />
|
|
||||||
<x-forms.input id="model.smtp_timeout" helper="Timeout value for sending emails." label="Timeout" />
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col gap-2 xl:flex-row">
|
|
||||||
<x-forms.input required id="model.smtp_from_name" helper="Name used in emails." label="From Name" />
|
|
||||||
<x-forms.input required id="model.smtp_from_address" helper="Email address used in emails."
|
|
||||||
label="From Address" />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
@if (data_get($model, 'smtp_enabled'))
|
@if ($this->sharedEmailEnabled)
|
||||||
<h4 class="mt-4">Subscribe to events</h4>
|
<div class="w-64 pb-4">
|
||||||
|
<x-forms.checkbox instantSave="instantSaveInstance" id="team.use_instance_email_settings"
|
||||||
|
label="Use hosted email service" />
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
@if (!$team->use_instance_email_settings)
|
||||||
|
<form class="flex flex-col items-end gap-2 pb-4 xl:flex-row" wire:submit.prevent='submitFromFields'>
|
||||||
|
<x-forms.input required id="team.smtp_from_name" helper="Name used in emails." label="From Name" />
|
||||||
|
<x-forms.input required id="team.smtp_from_address" helper="Email address used in emails."
|
||||||
|
label="From Address" />
|
||||||
|
<x-forms.button type="submit">
|
||||||
|
Save
|
||||||
|
</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>
|
||||||
|
</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" />
|
||||||
|
</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 type="password" id="team.resend_api_key" placeholder="API key"
|
||||||
|
label="Host" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-end gap-4 pt-6">
|
||||||
|
<x-forms.button type="submit">
|
||||||
|
Save
|
||||||
|
</x-forms.button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
@if (isEmailEnabled($team) || data_get($team, 'use_instance_email_settings'))
|
||||||
|
<h3 class="mt-4">Subscribe to events</h3>
|
||||||
<div class="w-64">
|
<div class="w-64">
|
||||||
@if (isDev())
|
@if (isDev())
|
||||||
<x-forms.checkbox instantSave="saveModel" id="model.smtp_notifications_test" label="Test" />
|
<x-forms.checkbox instantSave="saveModel" id="team.smtp_notifications_test" label="Test" />
|
||||||
@endif
|
@endif
|
||||||
<h4 class="mt-4">General</h4>
|
<h4 class="mt-4">General</h4>
|
||||||
<x-forms.checkbox instantSave="saveModel" id="model.smtp_notifications_status_changes"
|
<x-forms.checkbox instantSave="saveModel" id="team.smtp_notifications_status_changes"
|
||||||
label="Container Status Changes" />
|
label="Container Status Changes" />
|
||||||
<h4 class="mt-4">Applications</h4>
|
<h4 class="mt-4">Applications</h4>
|
||||||
<x-forms.checkbox instantSave="saveModel" id="model.smtp_notifications_deployments" label="Deployments" />
|
<x-forms.checkbox instantSave="saveModel" id="team.smtp_notifications_deployments" label="Deployments" />
|
||||||
<h4 class="mt-4">Databases</h4>
|
<h4 class="mt-4">Databases</h4>
|
||||||
<x-forms.checkbox instantSave="saveModel" id="model.smtp_notifications_database_backups"
|
<x-forms.checkbox instantSave="saveModel" id="team.smtp_notifications_database_backups"
|
||||||
label="Backup Statuses" />
|
label="Backup Statuses" />
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user