wip: boarding

This commit is contained in:
Andras Bacsai 2023-08-22 17:44:49 +02:00
parent 2414ddd360
commit b39ca51d41
71 changed files with 694 additions and 137 deletions

1
.ssh/known_hosts Normal file
View File

@ -0,0 +1 @@
|1|TRkudHmvsBVekjyAfpo3EWrkQSs=|XdMLpIt2l32hhdSyWnDwMMlVBSI= ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIK/Ln1b72Lc5JtHRDiZd4lYyW7F5aVuJH42HdHXuYudT

View File

@ -17,9 +17,6 @@ public function __invoke(bool $force)
$settings = InstanceSettings::get(); $settings = InstanceSettings::get();
ray('Running InstanceAutoUpdateJob'); ray('Running InstanceAutoUpdateJob');
$localhost_name = 'localhost'; $localhost_name = 'localhost';
if (is_dev()) {
$localhost_name = 'testing-local-docker-container';
}
$this->server = Server::where('name', $localhost_name)->firstOrFail(); $this->server = Server::where('name', $localhost_name)->firstOrFail();
$this->latest_version = get_latest_version_of_coolify(); $this->latest_version = get_latest_version_of_coolify();
$this->current_version = config('version'); $this->current_version = config('version');

View File

@ -12,7 +12,7 @@ class ApplicationController extends Controller
public function configuration() public function configuration()
{ {
$project = auth()->user()->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) {
return redirect()->route('dashboard'); return redirect()->route('dashboard');
} }
@ -29,7 +29,7 @@ public function configuration()
public function deployments() public function deployments()
{ {
$project = auth()->user()->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) {
return redirect()->route('dashboard'); return redirect()->route('dashboard');
} }
@ -49,7 +49,7 @@ public function deployment()
{ {
$deploymentUuid = request()->route('deployment_uuid'); $deploymentUuid = request()->route('deployment_uuid');
$project = auth()->user()->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) {
return redirect()->route('dashboard'); return redirect()->route('dashboard');
} }

View File

@ -58,7 +58,6 @@ public function dashboard()
$resources += $project->applications->count(); $resources += $project->applications->count();
$resources += $project->postgresqls->count(); $resources += $project->postgresqls->count();
} }
return view('dashboard', [ return view('dashboard', [
'servers' => $servers->count(), 'servers' => $servers->count(),
'projects' => $projects->count(), 'projects' => $projects->count(),
@ -66,10 +65,17 @@ public function dashboard()
's3s' => $s3s, 's3s' => $s3s,
]); ]);
} }
public function boarding() {
if (currentTeam()->boarding || is_dev()) {
return view('boarding');
} else {
return redirect()->route('dashboard');
}
}
public function settings() public function settings()
{ {
if (is_instance_admin()) { if (isInstanceAdmin()) {
$settings = InstanceSettings::get(); $settings = InstanceSettings::get();
$database = StandalonePostgresql::whereName('coolify-db')->first(); $database = StandalonePostgresql::whereName('coolify-db')->first();
if ($database) { if ($database) {
@ -89,7 +95,7 @@ public function team()
{ {
$invitations = []; $invitations = [];
if (auth()->user()->isAdminFromSession()) { if (auth()->user()->isAdminFromSession()) {
$invitations = TeamInvitation::whereTeamId(auth()->user()->currentTeam()->id)->get(); $invitations = TeamInvitation::whereTeamId(currentTeam()->id)->get();
} }
return view('team.show', [ return view('team.show', [
'invitations' => $invitations, 'invitations' => $invitations,
@ -116,7 +122,7 @@ public function members()
{ {
$invitations = []; $invitations = [];
if (auth()->user()->isAdminFromSession()) { if (auth()->user()->isAdminFromSession()) {
$invitations = TeamInvitation::whereTeamId(auth()->user()->currentTeam()->id)->get(); $invitations = TeamInvitation::whereTeamId(currentTeam()->id)->get();
} }
return view('team.members', [ return view('team.members', [
'invitations' => $invitations, 'invitations' => $invitations,

View File

@ -11,7 +11,7 @@ class DatabaseController extends Controller
public function configuration() public function configuration()
{ {
$project = auth()->user()->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) {
return redirect()->route('dashboard'); return redirect()->route('dashboard');
} }
@ -29,7 +29,7 @@ public function configuration()
public function executions() public function executions()
{ {
$backup_uuid = request()->route('backup_uuid'); $backup_uuid = request()->route('backup_uuid');
$project = auth()->user()->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) {
return redirect()->route('dashboard'); return redirect()->route('dashboard');
} }
@ -50,13 +50,13 @@ public function executions()
'database' => $database, 'database' => $database,
'backup' => $backup, 'backup' => $backup,
'executions' => $executions, 'executions' => $executions,
's3s' => auth()->user()->currentTeam()->s3s, 's3s' => currentTeam()->s3s,
]); ]);
} }
public function backups() public function backups()
{ {
$project = auth()->user()->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) {
return redirect()->route('dashboard'); return redirect()->route('dashboard');
} }
@ -70,7 +70,7 @@ public function backups()
} }
return view('project.database.backups.all', [ return view('project.database.backups.all', [
'database' => $database, 'database' => $database,
's3s' => auth()->user()->currentTeam()->s3s, 's3s' => currentTeam()->s3s,
]); ]);
} }
} }

View File

@ -41,7 +41,7 @@ public function newProject()
{ {
$project = Project::firstOrCreate( $project = Project::firstOrCreate(
['name' => request()->query('name') ?? generate_random_name()], ['name' => request()->query('name') ?? generate_random_name()],
['team_id' => auth()->user()->currentTeam()->id] ['team_id' => currentTeam()->id]
); );
return response()->json([ return response()->json([
'project_uuid' => $project->uuid 'project_uuid' => $project->uuid
@ -68,7 +68,7 @@ public function newTeam()
], ],
); );
auth()->user()->teams()->attach($team, ['role' => 'admin']); auth()->user()->teams()->attach($team, ['role' => 'admin']);
session(['currentTeam' => $team]); refreshSession();
return redirect(request()->header('Referer')); return redirect(request()->header('Referer'));
} }
} }

View File

@ -18,7 +18,7 @@ public function all()
public function edit() public function edit()
{ {
$projectUuid = request()->route('project_uuid'); $projectUuid = request()->route('project_uuid');
$teamId = auth()->user()->currentTeam()->id; $teamId = currentTeam()->id;
$project = Project::where('team_id', $teamId)->where('uuid', $projectUuid)->first(); $project = Project::where('team_id', $teamId)->where('uuid', $projectUuid)->first();
if (!$project) { if (!$project) {
return redirect()->route('dashboard'); return redirect()->route('dashboard');
@ -29,7 +29,7 @@ public function edit()
public function show() public function show()
{ {
$projectUuid = request()->route('project_uuid'); $projectUuid = request()->route('project_uuid');
$teamId = auth()->user()->currentTeam()->id; $teamId = currentTeam()->id;
$project = Project::where('team_id', $teamId)->where('uuid', $projectUuid)->first(); $project = Project::where('team_id', $teamId)->where('uuid', $projectUuid)->first();
if (!$project) { if (!$project) {
@ -44,7 +44,7 @@ public function new()
$type = request()->query('type'); $type = request()->query('type');
$destination_uuid = request()->query('destination'); $destination_uuid = request()->query('destination');
$project = auth()->user()->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) {
return redirect()->route('dashboard'); return redirect()->route('dashboard');
} }
@ -67,7 +67,7 @@ public function new()
public function resources() public function resources()
{ {
$project = auth()->user()->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) {
return redirect()->route('dashboard'); return redirect()->route('dashboard');
} }

View File

@ -12,14 +12,14 @@ class ServerController extends Controller
public function new_server() public function new_server()
{ {
if (!is_cloud() || is_instance_admin()) { if (!is_cloud() || isInstanceAdmin()) {
return view('server.create', [ return view('server.create', [
'limit_reached' => false, 'limit_reached' => false,
'private_keys' => PrivateKey::ownedByCurrentTeam()->get(), 'private_keys' => PrivateKey::ownedByCurrentTeam()->get(),
]); ]);
} }
$servers = auth()->user()->currentTeam()->servers->count(); $servers = currentTeam()->servers->count();
$subscription = auth()->user()->currentTeam()?->subscription->type(); $subscription = currentTeam()?->subscription->type();
$your_limit = config('constants.limits.server')[strtolower($subscription)]; $your_limit = config('constants.limits.server')[strtolower($subscription)];
$limit_reached = $servers >= $your_limit; $limit_reached = $servers >= $your_limit;

View File

@ -39,6 +39,7 @@ class Kernel extends HttpKernel
\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\SubscriptionValid::class,
\App\Http\Middleware\IsBoardingFlow::class,
], ],

View File

@ -0,0 +1,147 @@
<?php
namespace App\Http\Livewire;
use App\Models\PrivateKey;
use App\Models\Project;
use App\Models\Server;
use Livewire\Component;
class Boarding extends Component
{
public string $currentState = 'create-private-key';
// public ?string $serverType = null;
public ?string $privateKeyType = null;
public ?string $privateKey = null;
public ?string $privateKeyName = null;
public ?string $privateKeyDescription = null;
public ?PrivateKey $createdPrivateKey = null;
public ?string $remoteServerName = null;
public ?string $remoteServerDescription = null;
public ?string $remoteServerHost = null;
public ?int $remoteServerPort = 22;
public ?string $remoteServerUser = 'root';
public ?Server $createdServer = null;
public function mount()
{
$this->privateKeyName = generate_random_name();
$this->remoteServerName = generate_random_name();
if (is_dev()) {
$this->privateKey = '-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACBbhpqHhqv6aI67Mj9abM3DVbmcfYhZAhC7ca4d9UCevAAAAJi/QySHv0Mk
hwAAAAtzc2gtZWQyNTUxOQAAACBbhpqHhqv6aI67Mj9abM3DVbmcfYhZAhC7ca4d9UCevA
AAAECBQw4jg1WRT2IGHMncCiZhURCts2s24HoDS0thHnnRKVuGmoeGq/pojrsyP1pszcNV
uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
-----END OPENSSH PRIVATE KEY-----';
$this->privateKeyDescription = 'Created by Coolify';
$this->remoteServerDescription = 'Created by Coolify';
$this->remoteServerHost = 'coolify-testing-host';
}
}
public function restartBoarding()
{
if ($this->createdServer) {
$this->createdServer->delete();
}
if ($this->createdPrivateKey) {
$this->createdPrivateKey->delete();
}
return redirect()->route('boarding');
}
public function skipBoarding()
{
currentTeam()->update([
'show_boarding' => false
]);
refreshSession();
return redirect()->route('dashboard');
}
public function setServer(string $type)
{
if ($type === 'localhost') {
$this->currentState = 'create-project';
} elseif ($type === 'remote') {
$this->currentState = 'private-key';
}
}
public function setPrivateKey(string $type)
{
$this->privateKeyType = $type;
$this->currentState = 'create-private-key';
}
public function savePrivateKey()
{
$this->validate([
'privateKeyName' => 'required',
'privateKey' => 'required',
]);
$this->currentState = 'create-server';
}
public function saveServer()
{
$this->validate([
'remoteServerName' => 'required',
'remoteServerHost' => 'required',
'remoteServerPort' => 'required',
'remoteServerUser' => 'required',
]);
if ($this->privateKeyType === 'create') {
$this->createNewPrivateKey();
}
$this->privateKey = formatPrivateKey($this->privateKey);
$this->createdPrivateKey = PrivateKey::create([
'name' => $this->privateKeyName,
'description' => $this->privateKeyDescription,
'private_key' => $this->privateKey,
'team_id' => currentTeam()->id
]);
$this->createdServer = Server::create([
'name' => $this->remoteServerName,
'ip' => $this->remoteServerHost,
'port' => $this->remoteServerPort,
'user' => $this->remoteServerUser,
'description' => $this->remoteServerDescription,
'private_key_id' => $this->createdPrivateKey->id,
'team_id' => currentTeam()->id
]);
try {
['uptime' => $uptime, 'dockerVersion' => $dockerVersion] = validateServer($this->createdServer);
if (!$uptime) {
$this->createdServer->delete();
$this->createdPrivateKey->delete();
throw new \Exception('Server is not reachable.');
} else {
$this->createdServer->settings->update([
'is_reachable' => true,
]);
$this->emit('success', 'Server is reachable.');
}
if ($dockerVersion) {
$this->emit('error', 'Docker is not installed on the server.');
$this->currentState = 'install-docker';
return;
}
ray($uptime, $dockerVersion);
} catch (\Exception $e) {
return general_error_handler(customErrorMessage: "Server is not reachable. Reason: {$e->getMessage()}", that: $this);
}
}
private function createNewPrivateKey()
{
$this->privateKeyName = generate_random_name();
$this->privateKeyDescription = 'Created by Coolify';
$this->privateKey = generateSSHKey();
}
public function createNewProject()
{
Project::create([
'name' => generate_random_name(),
'team_id' => currentTeam()->id
]);
}
}

View File

@ -67,7 +67,7 @@ public function submit()
'name' => $this->name, 'name' => $this->name,
'network' => $this->network, 'network' => $this->network,
'server_id' => $this->server_id, 'server_id' => $this->server_id,
'team_id' => auth()->user()->currentTeam()->id 'team_id' => currentTeam()->id
]); ]);
} }
$this->createNetworkAndAttachToProxy(); $this->createNetworkAndAttachToProxy();

View File

@ -41,10 +41,9 @@ public function submit()
public function saveModel() public function saveModel()
{ {
ray($this->model);
$this->model->save(); $this->model->save();
if (is_a($this->model, Team::class)) { if (is_a($this->model, Team::class)) {
session(['currentTeam' => $this->model]); refreshSession();
} }
$this->emit('success', 'Settings saved.'); $this->emit('success', 'Settings saved.');
} }

View File

@ -59,7 +59,7 @@ public function copyFromInstanceSettings()
{ {
$settings = InstanceSettings::get(); $settings = InstanceSettings::get();
if ($settings->smtp_enabled) { if ($settings->smtp_enabled) {
$team = auth()->user()->currentTeam(); $team = currentTeam();
$team->update([ $team->update([
'smtp_enabled' => $settings->smtp_enabled, 'smtp_enabled' => $settings->smtp_enabled,
'smtp_from_address' => $settings->smtp_from_address, 'smtp_from_address' => $settings->smtp_from_address,
@ -74,7 +74,7 @@ public function copyFromInstanceSettings()
]); ]);
$this->decrypt(); $this->decrypt();
if (is_a($team, Team::class)) { if (is_a($team, Team::class)) {
session(['currentTeam' => $team]); refreshSession();
} }
$this->model = $team; $this->model = $team;
$this->emit('success', 'Settings saved.'); $this->emit('success', 'Settings saved.');
@ -119,7 +119,7 @@ public function saveModel()
$this->model->save(); $this->model->save();
$this->decrypt(); $this->decrypt();
if (is_a($this->model, Team::class)) { if (is_a($this->model, Team::class)) {
session(['currentTeam' => $this->model]); refreshSession();
} }
$this->emit('success', 'Settings saved.'); $this->emit('success', 'Settings saved.');
} }

View File

@ -26,7 +26,7 @@ public function delete()
try { try {
if ($this->private_key->isEmpty()) { if ($this->private_key->isEmpty()) {
$this->private_key->delete(); $this->private_key->delete();
auth()->user()->currentTeam()->privateKeys = PrivateKey::where('team_id', auth()->user()->currentTeam()->id)->get(); currentTeam()->privateKeys = PrivateKey::where('team_id', currentTeam()->id)->get();
return redirect()->route('private-key.all'); return redirect()->route('private-key.all');
} }
$this->emit('error', 'This private key is in use and cannot be deleted. Please delete all servers, applications, and GitHub/GitLab apps that use this private key before deleting it.'); $this->emit('error', 'This private key is in use and cannot be deleted. Please delete all servers, applications, and GitHub/GitLab apps that use this private key before deleting it.');
@ -38,10 +38,7 @@ public function delete()
public function changePrivateKey() public function changePrivateKey()
{ {
try { try {
$this->private_key->private_key = trim($this->private_key->private_key); $this->private_key->private_key = formatPrivateKey($this->private_key->private_key);
if (!str_ends_with($this->private_key->private_key, "\n")) {
$this->private_key->private_key .= "\n";
}
$this->private_key->save(); $this->private_key->save();
refresh_server_connection($this->private_key); refresh_server_connection($this->private_key);
} catch (\Exception $e) { } catch (\Exception $e) {

View File

@ -32,7 +32,7 @@ public function createPrivateKey()
'name' => $this->name, 'name' => $this->name,
'description' => $this->description, 'description' => $this->description,
'private_key' => $this->value, 'private_key' => $this->value,
'team_id' => auth()->user()->currentTeam()->id 'team_id' => currentTeam()->id
]); ]);
if ($this->from === 'server') { if ($this->from === 'server') {
return redirect()->route('server.create'); return redirect()->route('server.create');

View File

@ -25,7 +25,7 @@ public function submit()
$project = Project::create([ $project = Project::create([
'name' => $this->name, 'name' => $this->name,
'description' => $this->description, 'description' => $this->description,
'team_id' => auth()->user()->currentTeam()->id, 'team_id' => currentTeam()->id,
]); ]);
return redirect()->route('project.show', $project->uuid); return redirect()->route('project.show', $project->uuid);
} catch (\Exception $e) { } catch (\Exception $e) {

View File

@ -29,7 +29,7 @@ public function mount()
private function get_private_keys() private function get_private_keys()
{ {
$this->private_keys = PrivateKey::whereTeamId(auth()->user()->currentTeam()->id)->get()->reject(function ($key) { $this->private_keys = PrivateKey::whereTeamId(currentTeam()->id)->get()->reject(function ($key) {
return $key->id == $this->application->private_key_id; return $key->id == $this->application->private_key_id;
}); });
} }

View File

@ -39,7 +39,7 @@ public function submit(): void
's3_storage_id' => $this->s3_storage_id, 's3_storage_id' => $this->s3_storage_id,
'database_id' => $this->database->id, 'database_id' => $this->database->id,
'database_type' => $this->database->getMorphClass(), 'database_type' => $this->database->getMorphClass(),
'team_id' => auth()->user()->currentTeam()->id, 'team_id' => currentTeam()->id,
]); ]);
$this->emit('refreshScheduledBackups'); $this->emit('refreshScheduledBackups');
} catch (\Exception $e) { } catch (\Exception $e) {

View File

@ -11,7 +11,7 @@ public function createEmptyProject()
{ {
$project = Project::create([ $project = Project::create([
'name' => generate_random_name(), 'name' => generate_random_name(),
'team_id' => auth()->user()->currentTeam()->id, 'team_id' => currentTeam()->id,
]); ]);
return redirect()->route('project.show', ['project_uuid' => $project->uuid, 'environment_name' => 'production']); return redirect()->route('project.show', ['project_uuid' => $project->uuid, 'environment_name' => 'production']);
} }

View File

@ -55,7 +55,7 @@ public function mount()
} }
$this->parameters = get_route_parameters(); $this->parameters = get_route_parameters();
$this->query = request()->query(); $this->query = request()->query();
$this->private_keys = PrivateKey::where('team_id', auth()->user()->currentTeam()->id)->where('id', '!=', 0)->get(); $this->private_keys = PrivateKey::where('team_id', currentTeam()->id)->where('id', '!=', 0)->get();
} }
public function instantSave() public function instantSave()

View File

@ -42,7 +42,7 @@ public function mount()
public function installDocker() public function installDocker()
{ {
$activity = resolve(InstallDocker::class)($this->server, auth()->user()->currentTeam()); $activity = resolve(InstallDocker::class)($this->server, currentTeam());
$this->emit('newMonitorActivity', $activity->id); $this->emit('newMonitorActivity', $activity->id);
} }

View File

@ -65,7 +65,7 @@ public function submit()
'ip' => $this->ip, 'ip' => $this->ip,
'user' => $this->user, 'user' => $this->user,
'port' => $this->port, 'port' => $this->port,
'team_id' => auth()->user()->currentTeam()->id, 'team_id' => currentTeam()->id,
'private_key_id' => $this->private_key_id, 'private_key_id' => $this->private_key_id,
]); ]);
$server->settings->is_part_of_swarm = $this->is_part_of_swarm; $server->settings->is_part_of_swarm = $this->is_part_of_swarm;

View File

@ -65,7 +65,7 @@ public function add_coolify_database()
'frequency' => '0 0 * * *', 'frequency' => '0 0 * * *',
'database_id' => $this->database->id, 'database_id' => $this->database->id,
'database_type' => 'App\Models\StandalonePostgresql', 'database_type' => 'App\Models\StandalonePostgresql',
'team_id' => auth()->user()->currentTeam()->id, 'team_id' => currentTeam()->id,
]); ]);
$this->database->refresh(); $this->database->refresh();
$this->backup->refresh(); $this->backup->refresh();

View File

@ -40,7 +40,7 @@ public function createGitHubApp()
'custom_user' => $this->custom_user, 'custom_user' => $this->custom_user,
'custom_port' => $this->custom_port, 'custom_port' => $this->custom_port,
'is_system_wide' => $this->is_system_wide, 'is_system_wide' => $this->is_system_wide,
'team_id' => auth()->user()->currentTeam()->id, 'team_id' => currentTeam()->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) {

View File

@ -10,7 +10,7 @@ class Actions extends Component
public function cancel() public function cancel()
{ {
try { try {
$subscription_id = auth()->user()->currentTeam()->subscription->lemon_subscription_id; $subscription_id = currentTeam()->subscription->lemon_subscription_id;
if (!$subscription_id) { if (!$subscription_id) {
throw new \Exception('No subscription found'); throw new \Exception('No subscription found');
} }
@ -37,7 +37,7 @@ public function cancel()
public function resume() public function resume()
{ {
try { try {
$subscription_id = auth()->user()->currentTeam()->subscription->lemon_subscription_id; $subscription_id = currentTeam()->subscription->lemon_subscription_id;
if (!$subscription_id) { if (!$subscription_id) {
throw new \Exception('No subscription found'); throw new \Exception('No subscription found');
} }

View File

@ -29,7 +29,7 @@ public function submit()
'personal_team' => false, 'personal_team' => false,
]); ]);
auth()->user()->teams()->attach($team, ['role' => 'admin']); auth()->user()->teams()->attach($team, ['role' => 'admin']);
session(['currentTeam' => $team]); refreshSession();
return redirect()->route('team.show'); return redirect()->route('team.show');
} catch (\Throwable $th) { } catch (\Throwable $th) {
return general_error_handler($th, $this); return general_error_handler($th, $this);

View File

@ -9,7 +9,7 @@ class Delete extends Component
{ {
public function delete() public function delete()
{ {
$currentTeam = auth()->user()->currentTeam(); $currentTeam = currentTeam();
$currentTeam->delete(); $currentTeam->delete();
$team = auth()->user()->teams()->first(); $team = auth()->user()->teams()->first();
@ -24,7 +24,7 @@ public function delete()
} }
}); });
session(['currentTeam' => $team]); refreshSession();
return redirect()->route('team.show'); return redirect()->route('team.show');
} }
} }

View File

@ -19,7 +19,7 @@ class Form extends Component
public function mount() public function mount()
{ {
$this->team = auth()->user()->currentTeam(); $this->team = currentTeam();
} }
public function submit() public function submit()
@ -27,7 +27,7 @@ public function submit()
$this->validate(); $this->validate();
try { try {
$this->team->save(); $this->team->save();
session(['currentTeam' => $this->team]); refreshSession();
$this->emit('reloadWindow'); $this->emit('reloadWindow');
} catch (\Throwable $th) { } catch (\Throwable $th) {
return general_error_handler($th, $this); return general_error_handler($th, $this);

View File

@ -18,6 +18,6 @@ public function deleteInvitation(int $invitation_id)
public function refreshInvitations() public function refreshInvitations()
{ {
$this->invitations = TeamInvitation::whereTeamId(auth()->user()->currentTeam()->id)->get(); $this->invitations = TeamInvitation::whereTeamId(currentTeam()->id)->get();
} }
} }

View File

@ -35,9 +35,9 @@ private function generate_invite_link(bool $isEmail = false)
return general_error_handler(that: $this, customErrorMessage: "$this->email must be registered first (or activate transactional emails to invite via email)."); return general_error_handler(that: $this, customErrorMessage: "$this->email must be registered first (or activate transactional emails to invite via email).");
} }
$member_emails = auth()->user()->currentTeam()->members()->get()->pluck('email'); $member_emails = currentTeam()->members()->get()->pluck('email');
if ($member_emails->contains($this->email)) { if ($member_emails->contains($this->email)) {
return general_error_handler(that: $this, customErrorMessage: "$this->email is already a member of " . auth()->user()->currentTeam()->name . "."); return general_error_handler(that: $this, customErrorMessage: "$this->email is already a member of " . currentTeam()->name . ".");
} }
$invitation = TeamInvitation::whereEmail($this->email); $invitation = TeamInvitation::whereEmail($this->email);
@ -53,7 +53,7 @@ private function generate_invite_link(bool $isEmail = false)
} }
TeamInvitation::firstOrCreate([ TeamInvitation::firstOrCreate([
'team_id' => auth()->user()->currentTeam()->id, 'team_id' => currentTeam()->id,
'uuid' => $uuid, 'uuid' => $uuid,
'email' => $this->email, 'email' => $this->email,
'role' => $this->role, 'role' => $this->role,

View File

@ -11,19 +11,19 @@ class Member extends Component
public function makeAdmin() public function makeAdmin()
{ {
$this->member->teams()->updateExistingPivot(auth()->user()->currentTeam()->id, ['role' => 'admin']); $this->member->teams()->updateExistingPivot(currentTeam()->id, ['role' => 'admin']);
$this->emit('reloadWindow'); $this->emit('reloadWindow');
} }
public function makeReadonly() public function makeReadonly()
{ {
$this->member->teams()->updateExistingPivot(auth()->user()->currentTeam()->id, ['role' => 'member']); $this->member->teams()->updateExistingPivot(currentTeam()->id, ['role' => 'member']);
$this->emit('reloadWindow'); $this->emit('reloadWindow');
} }
public function remove() public function remove()
{ {
$this->member->teams()->detach(auth()->user()->currentTeam()); $this->member->teams()->detach(currentTeam());
$this->emit('reloadWindow'); $this->emit('reloadWindow');
} }
} }

View File

@ -62,7 +62,7 @@ public function submit()
} else { } else {
$this->storage->endpoint = $this->endpoint; $this->storage->endpoint = $this->endpoint;
} }
$this->storage->team_id = auth()->user()->currentTeam()->id; $this->storage->team_id = currentTeam()->id;
$this->storage->testConnection(); $this->storage->testConnection();
$this->emit('success', 'Connection is working. Tested with "ListObjectsV2" action.'); $this->emit('success', 'Connection is working. Tested with "ListObjectsV2" action.');
$this->storage->save(); $this->storage->save();

View File

@ -0,0 +1,28 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class IsBoardingFlow
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
$allowed_paths = [
'subscription',
'boarding',
'livewire/message/boarding'
];
if (showBoarding() && !in_array($request->path(), $allowed_paths)) {
return redirect('boarding');
}
return $next($request);
}
}

View File

@ -17,8 +17,7 @@ public function handle(Request $request, Closure $next): Response
return $next($request); return $next($request);
} }
} }
$is_instance_admin = is_instance_admin(); if (isInstanceAdmin()) {
if ($is_instance_admin) {
return $next($request); return $next($request);
} }

View File

@ -41,7 +41,7 @@ protected function value(): Attribute
private function get_environment_variables(string $environment_variable): string|null private function get_environment_variables(string $environment_variable): string|null
{ {
// $team_id = auth()->user()->currentTeam()->id; // $team_id = currentTeam()->id;
if (str_contains(trim($environment_variable), '{{') && str_contains(trim($environment_variable), '}}')) { if (str_contains(trim($environment_variable), '{{') && str_contains(trim($environment_variable), '}}')) {
$environment_variable = preg_replace('/\s+/', '', $environment_variable); $environment_variable = preg_replace('/\s+/', '', $environment_variable);
$environment_variable = str_replace('{{', '', $environment_variable); $environment_variable = str_replace('{{', '', $environment_variable);

View File

@ -19,12 +19,12 @@ class GithubApp extends BaseModel
static public function public() static public function public()
{ {
return GithubApp::whereTeamId(auth()->user()->currentTeam()->id)->whereisPublic(true)->whereNotNull('app_id')->get(); return GithubApp::whereTeamId(currentTeam()->id)->whereisPublic(true)->whereNotNull('app_id')->get();
} }
static public function private() static public function private()
{ {
return GithubApp::whereTeamId(auth()->user()->currentTeam()->id)->whereisPublic(false)->whereNotNull('app_id')->get(); return GithubApp::whereTeamId(currentTeam()->id)->whereisPublic(false)->whereNotNull('app_id')->get();
} }
protected static function booted(): void protected static function booted(): void

View File

@ -16,7 +16,7 @@ class PrivateKey extends BaseModel
static public function ownedByCurrentTeam(array $select = ['*']) static public function ownedByCurrentTeam(array $select = ['*'])
{ {
$selectArray = collect($select)->concat(['id']); $selectArray = collect($select)->concat(['id']);
return PrivateKey::whereTeamId(auth()->user()->currentTeam()->id)->select($selectArray->all()); return PrivateKey::whereTeamId(currentTeam()->id)->select($selectArray->all());
} }
public function isEmpty() public function isEmpty()

View File

@ -4,16 +4,11 @@
class Project extends BaseModel class Project extends BaseModel
{ {
protected $fillable = [ protected $guarded = [];
'name',
'description',
'team_id',
'project_id'
];
static public function ownedByCurrentTeam() static public function ownedByCurrentTeam()
{ {
return Project::whereTeamId(auth()->user()->currentTeam()->id)->orderBy('name'); return Project::whereTeamId(currentTeam()->id)->orderBy('name');
} }
protected static function booted() protected static function booted()

View File

@ -17,7 +17,7 @@ class S3Storage extends BaseModel
static public function ownedByCurrentTeam(array $select = ['*']) static public function ownedByCurrentTeam(array $select = ['*'])
{ {
$selectArray = collect($select)->concat(['id']); $selectArray = collect($select)->concat(['id']);
return S3Storage::whereTeamId(auth()->user()->currentTeam()->id)->select($selectArray->all())->orderBy('name'); return S3Storage::whereTeamId(currentTeam()->id)->select($selectArray->all())->orderBy('name');
} }
public function awsUrl() public function awsUrl()

View File

@ -33,7 +33,7 @@ static public function isReachable()
static public function ownedByCurrentTeam(array $select = ['*']) static public function ownedByCurrentTeam(array $select = ['*'])
{ {
$teamId = auth()->user()->currentTeam()->id; $teamId = currentTeam()->id;
$selectArray = collect($select)->concat(['id']); $selectArray = collect($select)->concat(['id']);
return Server::whereTeamId($teamId)->with('settings')->select($selectArray->all())->orderBy('name'); return Server::whereTeamId($teamId)->with('settings')->select($selectArray->all())->orderBy('name');
} }

View File

@ -22,6 +22,7 @@ class User extends Authenticatable implements SendsEmail
protected $casts = [ protected $casts = [
'email_verified_at' => 'datetime', 'email_verified_at' => 'datetime',
'force_password_reset' => 'boolean', 'force_password_reset' => 'boolean',
'show_boarding' => 'boolean',
]; ];
protected static function boot() protected static function boot()
@ -31,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;
@ -102,7 +104,7 @@ public function currentTeam()
public function otherTeams() public function otherTeams()
{ {
$team_id = auth()->user()->currentTeam()->id; $team_id = currentTeam()->id;
return auth()->user()->teams->filter(function ($team) use ($team_id) { return auth()->user()->teams->filter(function ($team) use ($team_id) {
return $team->id != $team_id; return $team->id != $team_id;
}); });
@ -113,13 +115,6 @@ public function role()
if ($this->teams()->where('team_id', 0)->first()) { if ($this->teams()->where('team_id', 0)->first()) {
return 'admin'; return 'admin';
} }
return $this->teams()->where('team_id', auth()->user()->currentTeam()->id)->first()->pivot->role; return $this->teams()->where('team_id', currentTeam()->id)->first()->pivot->role;
}
public function resources()
{
$team_id = auth()->user()->currentTeam()->id;
$data = Application::where('team_id', $team_id)->get();
return $data;
} }
} }

View File

@ -66,8 +66,6 @@ function get_private_key_for_server(Server $server)
function save_private_key_for_server(Server $server) function save_private_key_for_server(Server $server)
{ {
if (data_get($server, 'privateKey.private_key') === null) { if (data_get($server, 'privateKey.private_key') === null) {
$server->settings->is_reachable = false;
$server->settings->save();
throw new \Exception("Server {$server->name} does not have a private key"); throw new \Exception("Server {$server->name} does not have a private key");
} }
$temp_file = "id.root@{$server->ip}"; $temp_file = "id.root@{$server->ip}";
@ -159,8 +157,8 @@ function refresh_server_connection(PrivateKey $private_key)
// Delete the old ssh mux file to force a new one to be created // Delete the old ssh mux file to force a new one to be created
Storage::disk('ssh-mux')->delete($server->muxFilename()); Storage::disk('ssh-mux')->delete($server->muxFilename());
// check if user is authenticated // check if user is authenticated
if (auth()?->user()?->currentTeam()->id) { if (currentTeam()->id) {
auth()->user()->currentTeam()->privateKeys = PrivateKey::where('team_id', auth()->user()->currentTeam()->id)->get(); currentTeam()->privateKeys = PrivateKey::where('team_id', currentTeam()->id)->get();
} }
} }
} }

View File

@ -14,6 +14,7 @@
use Nubs\RandomNameGenerator\All; use Nubs\RandomNameGenerator\All;
use Poliander\Cron\CronExpression; use Poliander\Cron\CronExpression;
use Visus\Cuid2\Cuid2; use Visus\Cuid2\Cuid2;
use phpseclib3\Crypt\RSA;
function application_configuration_dir(): string function application_configuration_dir(): string
{ {
@ -35,11 +36,25 @@ function generate_readme_file(string $name, string $updated_at): string
return "Resource name: $name\nLatest Deployment Date: $updated_at"; return "Resource name: $name\nLatest Deployment Date: $updated_at";
} }
function is_instance_admin() function isInstanceAdmin()
{ {
return auth()->user()?->isInstanceAdmin(); return auth()?->user()?->isInstanceAdmin() ?? false;
} }
function currentTeam()
{
return auth()?->user()?->currentTeam() ?? null;
}
function showBoarding(): bool
{
return currentTeam()->show_boarding ?? false;
}
function refreshSession(): void
{
$team = currentTeam();
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 {
@ -97,7 +112,21 @@ function generate_random_name(): string
$cuid = new Cuid2(7); $cuid = new Cuid2(7);
return Str::kebab("{$generator->getName()}-$cuid"); return Str::kebab("{$generator->getName()}-$cuid");
} }
function generateSSHKey()
{
$key = RSA::createKey();
return [
'private' => $key->toString('PKCS1'),
'public' => $key->getPublicKey()->toString('OpenSSH',['comment' => 'coolify-generated-ssh-key'])
];
}
function formatPrivateKey(string $privateKey) {
$privateKey = trim($privateKey);
if (!str_ends_with($privateKey, "\n")) {
$privateKey .= "\n";
}
return $privateKey;
}
function generate_application_name(string $git_repository, string $git_branch): string function generate_application_name(string $git_repository, string $git_branch): string
{ {
$cuid = new Cuid2(7); $cuid = new Cuid2(7);

View File

@ -9,7 +9,7 @@ function getSubscriptionLink($type)
return null; return null;
} }
$user_id = auth()->user()->id; $user_id = auth()->user()->id;
$team_id = auth()->user()->currentTeam()->id ?? null; $team_id = currentTeam()->id ?? null;
$email = auth()->user()->email ?? null; $email = auth()->user()->email ?? null;
$name = auth()->user()->name ?? null; $name = auth()->user()->name ?? null;
$url = "https://store.coollabs.io/checkout/buy/$checkout_id?"; $url = "https://store.coollabs.io/checkout/buy/$checkout_id?";
@ -30,27 +30,27 @@ function getSubscriptionLink($type)
function getPaymentLink() function getPaymentLink()
{ {
return auth()->user()->currentTeam()->subscription->lemon_update_payment_menthod_url; return currentTeam()->subscription->lemon_update_payment_menthod_url;
} }
function getRenewDate() function getRenewDate()
{ {
return Carbon::parse(auth()->user()->currentTeam()->subscription->lemon_renews_at)->format('Y-M-d H:i:s'); return Carbon::parse(currentTeam()->subscription->lemon_renews_at)->format('Y-M-d H:i:s');
} }
function getEndDate() function getEndDate()
{ {
return Carbon::parse(auth()->user()->currentTeam()->subscription->lemon_renews_at)->format('Y-M-d H:i:s'); return Carbon::parse(currentTeam()->subscription->lemon_renews_at)->format('Y-M-d H:i:s');
} }
function is_subscription_active() function is_subscription_active()
{ {
$team = auth()->user()?->currentTeam(); $team = currentTeam();
if (!$team) { if (!$team) {
return false; return false;
} }
if (is_instance_admin()) { if (isInstanceAdmin()) {
return true; return true;
} }
$subscription = $team?->subscription; $subscription = $team?->subscription;
@ -64,11 +64,11 @@ function is_subscription_active()
} }
function is_subscription_in_grace_period() function is_subscription_in_grace_period()
{ {
$team = auth()->user()?->currentTeam(); $team = currentTeam();
if (!$team) { if (!$team) {
return false; return false;
} }
if (is_instance_admin()) { if (isInstanceAdmin()) {
return true; return true;
} }
$subscription = $team?->subscription; $subscription = $team?->subscription;

View File

@ -23,6 +23,7 @@
"livewire/livewire": "^v2.12.3", "livewire/livewire": "^v2.12.3",
"masmerise/livewire-toaster": "^1.2", "masmerise/livewire-toaster": "^1.2",
"nubs/random-name-generator": "^2.2", "nubs/random-name-generator": "^2.2",
"phpseclib/phpseclib": "~3.0",
"poliander/cron": "^3.0", "poliander/cron": "^3.0",
"resend/resend-laravel": "^0.5.0", "resend/resend-laravel": "^0.5.0",
"sentry/sentry-laravel": "^3.4", "sentry/sentry-laravel": "^3.4",

162
composer.lock generated
View File

@ -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": "15eb89e93c667bb63f48ef548ed38bed", "content-hash": "a4143cdb58c02a0490f9aa03d05b8e9a",
"packages": [ "packages": [
{ {
"name": "aws/aws-crt-php", "name": "aws/aws-crt-php",
@ -3875,6 +3875,56 @@
}, },
"time": "2022-06-14T06:56:20+00:00" "time": "2022-06-14T06:56:20+00:00"
}, },
{
"name": "paragonie/random_compat",
"version": "v9.99.100",
"source": {
"type": "git",
"url": "https://github.com/paragonie/random_compat.git",
"reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/paragonie/random_compat/zipball/996434e5492cb4c3edcb9168db6fbb1359ef965a",
"reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a",
"shasum": ""
},
"require": {
"php": ">= 7"
},
"require-dev": {
"phpunit/phpunit": "4.*|5.*",
"vimeo/psalm": "^1"
},
"suggest": {
"ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes."
},
"type": "library",
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Paragon Initiative Enterprises",
"email": "security@paragonie.com",
"homepage": "https://paragonie.com"
}
],
"description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7",
"keywords": [
"csprng",
"polyfill",
"pseudorandom",
"random"
],
"support": {
"email": "info@paragonie.com",
"issues": "https://github.com/paragonie/random_compat/issues",
"source": "https://github.com/paragonie/random_compat"
},
"time": "2020-10-15T08:29:30+00:00"
},
{ {
"name": "php-http/client-common", "name": "php-http/client-common",
"version": "2.7.0", "version": "2.7.0",
@ -4446,6 +4496,116 @@
], ],
"time": "2023-02-25T19:38:58+00:00" "time": "2023-02-25T19:38:58+00:00"
}, },
{
"name": "phpseclib/phpseclib",
"version": "3.0.21",
"source": {
"type": "git",
"url": "https://github.com/phpseclib/phpseclib.git",
"reference": "4580645d3fc05c189024eb3b834c6c1e4f0f30a1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/4580645d3fc05c189024eb3b834c6c1e4f0f30a1",
"reference": "4580645d3fc05c189024eb3b834c6c1e4f0f30a1",
"shasum": ""
},
"require": {
"paragonie/constant_time_encoding": "^1|^2",
"paragonie/random_compat": "^1.4|^2.0|^9.99.99",
"php": ">=5.6.1"
},
"require-dev": {
"phpunit/phpunit": "*"
},
"suggest": {
"ext-dom": "Install the DOM extension to load XML formatted public keys.",
"ext-gmp": "Install the GMP (GNU Multiple Precision) extension in order to speed up arbitrary precision integer arithmetic operations.",
"ext-libsodium": "SSH2/SFTP can make use of some algorithms provided by the libsodium-php extension.",
"ext-mcrypt": "Install the Mcrypt extension in order to speed up a few other cryptographic operations.",
"ext-openssl": "Install the OpenSSL extension in order to speed up a wide variety of cryptographic operations."
},
"type": "library",
"autoload": {
"files": [
"phpseclib/bootstrap.php"
],
"psr-4": {
"phpseclib3\\": "phpseclib/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Jim Wigginton",
"email": "terrafrost@php.net",
"role": "Lead Developer"
},
{
"name": "Patrick Monnerat",
"email": "pm@datasphere.ch",
"role": "Developer"
},
{
"name": "Andreas Fischer",
"email": "bantu@phpbb.com",
"role": "Developer"
},
{
"name": "Hans-Jürgen Petrich",
"email": "petrich@tronic-media.com",
"role": "Developer"
},
{
"name": "Graham Campbell",
"email": "graham@alt-three.com",
"role": "Developer"
}
],
"description": "PHP Secure Communications Library - Pure-PHP implementations of RSA, AES, SSH2, SFTP, X.509 etc.",
"homepage": "http://phpseclib.sourceforge.net",
"keywords": [
"BigInteger",
"aes",
"asn.1",
"asn1",
"blowfish",
"crypto",
"cryptography",
"encryption",
"rsa",
"security",
"sftp",
"signature",
"signing",
"ssh",
"twofish",
"x.509",
"x509"
],
"support": {
"issues": "https://github.com/phpseclib/phpseclib/issues",
"source": "https://github.com/phpseclib/phpseclib/tree/3.0.21"
},
"funding": [
{
"url": "https://github.com/terrafrost",
"type": "github"
},
{
"url": "https://www.patreon.com/phpseclib",
"type": "patreon"
},
{
"url": "https://tidelift.com/funding/github/packagist/phpseclib/phpseclib",
"type": "tidelift"
}
],
"time": "2023-07-09T15:24:48+00:00"
},
{ {
"name": "phpstan/phpdoc-parser", "name": "phpstan/phpdoc-parser",
"version": "1.23.1", "version": "1.23.1",

View File

@ -0,0 +1,28 @@
<?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('show_boarding')->default(false);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('teams', function (Blueprint $table) {
$table->dropColumn('show_boarding');
});
}
};

View File

@ -11,8 +11,8 @@ public function run(): void
{ {
Server::create([ Server::create([
'id' => 0, 'id' => 0,
'name' => "testing-local-docker-container", 'name' => "localhost",
'description' => "This is a test docker container", 'description' => "This is a test docker container in development mode",
'ip' => "coolify-testing-host", 'ip' => "coolify-testing-host",
'team_id' => 0, 'team_id' => 0,
'private_key_id' => 0, 'private_key_id' => 0,

View File

@ -1,5 +1,6 @@
{ {
"private": true, "private": true,
"type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "vite build" "build": "vite build"
@ -20,4 +21,4 @@
"daisyui": "3.2.1", "daisyui": "3.2.1",
"tailwindcss-scrollbar": "0.1.0" "tailwindcss-scrollbar": "0.1.0"
} }
} }

View File

@ -53,7 +53,7 @@ .icon:hover {
@apply text-white; @apply text-white;
} }
.box { .box {
@apply flex items-center justify-center p-2 transition-colors rounded min-h-12 bg-coolgray-200 hover:bg-coollabs-100 hover:text-white hover:no-underline; @apply flex items-center justify-center p-2 transition-colors rounded cursor-pointer min-h-12 bg-coolgray-200 hover:bg-coollabs-100 hover:text-white hover:no-underline;
} }
.lds-heart { .lds-heart {

View File

@ -53,12 +53,12 @@
<span v-if="search"><span class="capitalize ">{{ <span v-if="search"><span class="capitalize ">{{
sequenceState.sequence[sequenceState.currentActionIndex] }}</span> name sequenceState.sequence[sequenceState.currentActionIndex] }}</span> name
will be: will be:
<span class="text-warning">{{ search <x-highlighted text="{{ search }}" />
}}</span></span> </span>
<span v-else><span class="capitalize ">{{ <span v-else><span class="capitalize ">{{
sequenceState.sequence[sequenceState.currentActionIndex] }}</span> name sequenceState.sequence[sequenceState.currentActionIndex] }}</span> name
will be: will be:
<span class="text-warning">randomly generated (type to change)</span> <x-highlighted text="randomly generated (type to change)" />
</span> </span>
</span> </span>
</li> </li>

View File

@ -0,0 +1,3 @@
<x-layout-simple>
<livewire:boarding />
</x-layout-simple>

View File

@ -0,0 +1,25 @@
<div class="grid grid-cols-1 gap-4 md:grid-cols-3">
<div class="box-border col-span-2 min-w-[24rem]">
<h1 class="text-5xl font-bold">{{$title}}</h1>
<div class="py-6 ">
@isset($question)
<p class="text-base">
{{$question}}
</p>
@endisset
</div>
@if($actions)
<div class="flex flex-col gap-4 md:flex-row">
{{$actions}}
</div>
@endif
</div>
@if($explanation)
<div class="col-span-1">
<h1 class="pb-8 font-bold">Explanation</h1>
<div class="space-y-4">
{{$explanation}}
</div>
</div>
@endif
</div>

View File

@ -2,7 +2,7 @@
@if ($label) @if ($label)
<label for="small-input" class="flex items-center gap-1 mb-1 text-sm font-medium">{{ $label }} <label for="small-input" class="flex items-center gap-1 mb-1 text-sm font-medium">{{ $label }}
@if ($required) @if ($required)
<span class="text-warning">*</span> <x-highlighted text="*" />
@endif @endif
@if ($helper) @if ($helper)
<x-helper :helper="$helper" /> <x-helper :helper="$helper" />

View File

@ -2,7 +2,7 @@
@if ($label) @if ($label)
<label for="small-input" class="flex items-center gap-1 mb-1 text-sm font-medium">{{ $label }} <label for="small-input" class="flex items-center gap-1 mb-1 text-sm font-medium">{{ $label }}
@if ($required) @if ($required)
<span class="text-warning">*</span> <x-highlighted text="*" />
@endif @endif
@if ($helper) @if ($helper)
<x-helper :helper="$helper" /> <x-helper :helper="$helper" />

View File

@ -1,14 +1,14 @@
<div class="form-control"> <div class="form-control">
@if ($label) @if ($label)
<label class="label"> <label class="flex items-center gap-1 mb-1 text-sm font-medium">
<span class="label-text"> <span>
@if ($label) @if ($label)
{{ $label }} {{ $label }}
@else @else
{{ $id }} {{ $id }}
@endif @endif
@if ($required) @if ($required)
<span class="text-warning">*</span> <x-highlighted text="*" />
@endif @endif
@if ($helper) @if ($helper)
<div class="group"> <div class="group">

View File

@ -0,0 +1 @@
<span class="inline-block text-warning">{{$text}}</span>

View File

@ -26,7 +26,7 @@
<body> <body>
@livewireScripts @livewireScripts
<x-toaster-hub /> <x-toaster-hub />
<main class="main max-w-screen-2xl"> <main>
{{ $slot }} {{ $slot }}
</main> </main>
<x-version class="fixed left-2 bottom-1" /> <x-version class="fixed left-2 bottom-1" />

View File

@ -26,7 +26,7 @@
<body> <body>
@livewireScripts @livewireScripts
<x-toaster-hub /> <x-toaster-hub />
@if (is_instance_admin() || is_subscription_in_grace_period()) @if (isInstanceAdmin() || is_subscription_in_grace_period())
<div class="fixed top-3 left-4" id="vue"> <div class="fixed top-3 left-4" id="vue">
<magic-bar></magic-bar> <magic-bar></magic-bar>
</div> </div>

View File

@ -65,7 +65,7 @@ class="{{ request()->is('command-center') ? 'text-warning icon' : 'icon' }}" vie
</a> </a>
</li> </li>
<div class="flex-1"></div> <div class="flex-1"></div>
@if (is_instance_admin()) @if (isInstanceAdmin())
<livewire:upgrade /> <livewire:upgrade />
@endif @endif
<li title="Profile"> <li title="Profile">
@ -80,7 +80,7 @@ class="{{ request()->is('command-center') ? 'text-warning icon' : 'icon' }}" vie
</a> </a>
</li> </li>
@if (is_instance_admin()) @if (isInstanceAdmin())
<li title="Settings" class="mt-auto"> <li title="Settings" class="mt-auto">
<a class="hover:bg-transparent" @if (!request()->is('settings')) href="/settings" @endif> <a class="hover:bg-transparent" @if (!request()->is('settings')) href="/settings" @endif>
<svg xmlns="http://www.w3.org/2000/svg" <svg xmlns="http://www.w3.org/2000/svg"

View File

@ -1,6 +1,6 @@
<x-layout> <x-layout>
<h1>Dashboard</h1> <h1>Dashboard</h1>
<div class="subtitle">Something <span class="text-warning">(more)</span> useful will be here.</div> <div class="subtitle">Something <x-highlighted text="(more)"/> useful will be here.</div>
<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>

View File

@ -0,0 +1,146 @@
<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="w-72 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="md:w-72 box" wire:click="setServer('localhost')">Localhost
</div>
<div class="md:w-72 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="Private Key">
<x-slot:question>
Do you have your own Private Key?
</x-slot:question>
<x-slot:actions>
<div class="md:w-72 box" wire:click="setPrivateKey('own')">Yes
</div>
<div class="md:w-72 box" wire:click="setPrivateKey('create')">No (create one for me)
</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
@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" />
<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="w-72 box" wire:click="installDocker">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 === '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="w-72 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
<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>

View File

@ -25,7 +25,7 @@
@foreach ($networks as $network) @foreach ($networks as $network)
<a <a
href="{{ route('destination.new', ['server_id' => $server->id, 'network_name' => data_get($network, 'Name')]) }}"> href="{{ route('destination.new', ['server_id' => $server->id, 'network_name' => data_get($network, 'Name')]) }}">
<x-forms.button>Add<span class="text-warning">{{ data_get($network, 'Name') }}</span> <x-forms.button>+<x-highlighted text="{{ data_get($network, 'Name') }}" />
</x-forms.button> </x-forms.button>
</a> </a>
@endforeach @endforeach

View File

@ -16,7 +16,7 @@
<x-forms.button type="submit"> <x-forms.button type="submit">
Save Save
</x-forms.button> </x-forms.button>
@if (is_instance_admin()) @if (isInstanceAdmin())
<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>

View File

@ -9,9 +9,8 @@
</x-modal> </x-modal>
<x-modal yesOrNo modalId="startProxy" modalTitle="Start Proxy" action="start_proxy"> <x-modal yesOrNo modalId="startProxy" modalTitle="Start Proxy" action="start_proxy">
<x-slot:modalBody> <x-slot:modalBody>
<p>This will start the proxy on this server and <span class="text-warning">stop any running process that is <p>This will start the proxy on this server and
using port 80 and <x-highlighted text="stop any running process that is using port 80 and 443" />.
443</span>.
<br>Please think <br>Please think
again. again.
</p> </p>

View File

@ -1,7 +1,7 @@
<div> <div>
<div>Status: {{ auth()->user()->currentTeam()->subscription->lemon_status }}</div> <div>Status: {{ currentTeam()->subscription->lemon_status }}</div>
<div>Type: {{ auth()->user()->currentTeam()->subscription->lemon_variant_name }}</div> <div>Type: {{ currentTeam()->subscription->lemon_variant_name }}</div>
@if (auth()->user()->currentTeam()->subscription->lemon_status === 'cancelled') @if (currentTeam()->subscription->lemon_status === 'cancelled')
<div class="pb-4">Subscriptions ends at: {{ getRenewDate() }}</div> <div class="pb-4">Subscriptions ends at: {{ getRenewDate() }}</div>
<div class="py-4">If you would like to change the subscription to a lower/higher plan, <a <div class="py-4">If you would like to change the subscription to a lower/higher plan, <a
class="text-white underline" href="https://docs.coollabs.io/contact" target="_blank">please class="text-white underline" href="https://docs.coollabs.io/contact" target="_blank">please
@ -12,7 +12,7 @@ class="text-white underline" href="https://docs.coollabs.io/contact" target="_bl
@endif @endif
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
<div class="flex gap-2"> <div class="flex gap-2">
@if (auth()->user()->currentTeam()->subscription->lemon_status === 'cancelled') @if (currentTeam()->subscription->lemon_status === 'cancelled')
<x-forms.button class="bg-coollabs-gradient" wire:click='resume'>Resume Subscription <x-forms.button class="bg-coollabs-gradient" wire:click='resume'>Resume Subscription
</x-forms.button> </x-forms.button>
@else @else

View File

@ -11,11 +11,11 @@
<div>This is the default team. You can't delete it.</div> <div>This is the default team. You can't delete it.</div>
@elseif(auth()->user()->teams()->get()->count() === 1) @elseif(auth()->user()->teams()->get()->count() === 1)
<div>You can't delete your last team.</div> <div>You can't delete your last team.</div>
@elseif(auth()->user()->currentTeam()->subscription && @elseif(currentTeam()->subscription &&
auth()->user()->currentTeam()->subscription?->lemon_status !== 'cancelled') currentTeam()->subscription?->lemon_status !== 'cancelled')
<div>Please cancel your subscription before delete this team (Manage My Subscription button).</div> <div>Please cancel your subscription before delete this team (Manage My Subscription button).</div>
@else @else
@if (auth()->user()->currentTeam()->isEmpty()) @if (currentTeam()->isEmpty())
<div class="pb-4">This will delete your team. Beware! There is no coming back!</div> <div class="pb-4">This will delete your team. Beware! There is no coming back!</div>
<x-forms.button isError isModal modalId="deleteTeam"> <x-forms.button isError isModal modalId="deleteTeam">
Delete Delete
@ -25,25 +25,25 @@
<div class="pb-4">You need to delete the following resources to be able to delete the team:</div> <div class="pb-4">You need to delete the following resources to be able to delete the team:</div>
<h4 class="pb-4">Projects:</h4> <h4 class="pb-4">Projects:</h4>
<ul class="pl-8 list-disc"> <ul class="pl-8 list-disc">
@foreach (auth()->user()->currentTeam()->projects as $resource) @foreach (currentTeam()->projects as $resource)
<li>{{ $resource->name }}</li> <li>{{ $resource->name }}</li>
@endforeach @endforeach
</ul> </ul>
<h4 class="py-4">Servers:</h4> <h4 class="py-4">Servers:</h4>
<ul class="pl-8 list-disc"> <ul class="pl-8 list-disc">
@foreach (auth()->user()->currentTeam()->servers as $resource) @foreach (currentTeam()->servers as $resource)
<li>{{ $resource->name }}</li> <li>{{ $resource->name }}</li>
@endforeach @endforeach
</ul> </ul>
<h4 class="py-4">Private Keys:</h4> <h4 class="py-4">Private Keys:</h4>
<ul class="pl-8 list-disc"> <ul class="pl-8 list-disc">
@foreach (auth()->user()->currentTeam()->privateKeys as $resource) @foreach (currentTeam()->privateKeys as $resource)
<li>{{ $resource->name }}</li> <li>{{ $resource->name }}</li>
@endforeach @endforeach
</ul> </ul>
<h4 class="py-4">Sources:</h4> <h4 class="py-4">Sources:</h4>
<ul class="pl-8 list-disc"> <ul class="pl-8 list-disc">
@foreach (auth()->user()->currentTeam()->sources() as $resource) @foreach (currentTeam()->sources() as $resource)
<li>{{ $resource->name }}</li> <li>{{ $resource->name }}</li>
@endforeach @endforeach
</ul> </ul>

View File

@ -14,7 +14,7 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@foreach (auth()->user()->currentTeam()->members->sortBy('name') as $member) @foreach (currentTeam()->members->sortBy('name') as $member)
<livewire:team.member :member="$member" :wire:key="$member->id" /> <livewire:team.member :member="$member" :wire:key="$member->id" />
@endforeach @endforeach
</tbody> </tbody>
@ -26,7 +26,7 @@
<h3 class="pb-4">Invite a new member</h3> <h3 class="pb-4">Invite a new member</h3>
@else @else
<h3>Invite a new member</h3> <h3>Invite a new member</h3>
@if (is_instance_admin()) @if (isInstanceAdmin())
<div class="pb-4 text-xs text-warning">You need to configure <a href="/settings/emails" <div class="pb-4 text-xs text-warning">You need to configure <a href="/settings/emails"
class="underline text-warning">Transactional class="underline text-warning">Transactional
Emails</a> Emails</a>

View File

@ -6,7 +6,7 @@
@if (is_cloud()) @if (is_cloud())
<div class="pb-8"> <div class="pb-8">
<h2>Subscription</h2> <h2>Subscription</h2>
@if (data_get(auth()->user()->currentTeam(), @if (data_get(currentTeam(),
'subscription')) 'subscription'))
<livewire:subscription.actions /> <livewire:subscription.actions />
@else @else

View File

@ -93,6 +93,7 @@
Route::middleware(['auth'])->group(function () { Route::middleware(['auth'])->group(function () {
Route::get('/', [Controller::class, 'dashboard'])->name('dashboard'); Route::get('/', [Controller::class, 'dashboard'])->name('dashboard');
Route::get('/boarding', [Controller::class, 'boarding'])->name('boarding');
Route::middleware(['throttle:force-password-reset'])->group(function() { Route::middleware(['throttle:force-password-reset'])->group(function() {
Route::get('/force-password-reset', [Controller::class, 'force_passoword_reset'])->name('auth.force-password-reset'); Route::get('/force-password-reset', [Controller::class, 'force_passoword_reset'])->name('auth.force-password-reset');
}); });
@ -126,7 +127,7 @@
Route::middleware(['auth'])->group(function () { Route::middleware(['auth'])->group(function () {
Route::get('/source/new', fn () => view('source.new'))->name('source.new'); Route::get('/source/new', fn () => view('source.new'))->name('source.new');
Route::get('/sources', function () { Route::get('/sources', function () {
$sources = auth()->user()->currentTeam()->sources(); $sources = currentTeam()->sources();
return view('source.all', [ return view('source.all', [
'sources' => $sources, 'sources' => $sources,
]); ]);