This commit is contained in:
Andras Bacsai 2023-06-07 10:33:45 +02:00
parent 2c3682cc26
commit 50bac2c056
15 changed files with 108 additions and 137 deletions

View File

@ -47,6 +47,11 @@ function remote_process(
} }
function save_private_key_for_server(Server $server) function save_private_key_for_server(Server $server)
{ {
if (data_get($server, 'privateKey.private_key') === null) {
$server->settings->is_validated = false;
$server->settings->save();
throw new \Exception("Server {$server->name} does not have a private key");
}
$temp_file = "id.root@{$server->ip}"; $temp_file = "id.root@{$server->ip}";
Storage::disk('ssh-keys')->put($temp_file, $server->privateKey->private_key); Storage::disk('ssh-keys')->put($temp_file, $server->privateKey->private_key);
Storage::disk('ssh-mux')->makeDirectory('.'); Storage::disk('ssh-mux')->makeDirectory('.');
@ -91,7 +96,7 @@ function instant_remote_process(array $command, Server $server, $throwError = tr
ray('executing again'); ray('executing again');
return instant_remote_process($command, $server, $throwError, $repeat - 1); return instant_remote_process($command, $server, $throwError, $repeat - 1);
} }
ray('ERROR OCCURED: ' . $process->errorOutput()); // ray('ERROR OCCURED: ' . $process->errorOutput());
if (!$throwError) { if (!$throwError) {
return null; return null;
} }

View File

@ -4,7 +4,7 @@
"auth.confirm_password": "Confirm password", "auth.confirm_password": "Confirm password",
"auth.forgot_password": "Forgot password", "auth.forgot_password": "Forgot password",
"auth.forgot_password_send_email": "Send password reset link via email", "auth.forgot_password_send_email": "Send password reset link via email",
"auth.register_now": "Register now!", "auth.register_now": "Register a new account",
"auth.logout": "Logout", "auth.logout": "Logout",
"auth.register": "Register", "auth.register": "Register",
"auth.registration_disabled": "Registration is disabled. Please contact the administrator.", "auth.registration_disabled": "Registration is disabled. Please contact the administrator.",

View File

@ -72,6 +72,9 @@ a {
.main-navbar { .main-navbar {
@apply fixed h-full overflow-hidden pt-14; @apply fixed h-full overflow-hidden pt-14;
} }
.kbd-custom {
@apply px-2 text-xs border border-dashed rounded border-neutral-700 text-warning;
}
.icon { .icon {
@apply w-6 h-6; @apply w-6 h-6;
} }

View File

@ -9,7 +9,7 @@
<path d="M10 10m-7 0a7 7 0 1 0 14 0a7 7 0 1 0 -14 0" /> <path d="M10 10m-7 0a7 7 0 1 0 14 0a7 7 0 1 0 -14 0" />
<path d="M21 21l-6 -6" /> <path d="M21 21l-6 -6" />
</svg> </svg>
<span class="px-2 ml-2 text-xs border border-dashed rounded border-neutral-700 text-warning">/</span> <span class="ml-2 kbd-custom">/</span>
</div> </div>
<div class="relative" role="dialog" aria-modal="true" v-if="showCommandPalette" @keyup.esc="resetState"> <div class="relative" role="dialog" aria-modal="true" v-if="showCommandPalette" @keyup.esc="resetState">
<div class="fixed inset-0 transition-opacity bg-opacity-75 bg-coolgray-100" @click.self="resetState"> <div class="fixed inset-0 transition-opacity bg-opacity-75 bg-coolgray-100" @click.self="resetState">

View File

@ -1,6 +1,6 @@
<x-layout-simple> <x-layout-simple>
<div class="flex items-center justify-center h-screen"> <div class="flex items-center justify-center h-screen">
<div> <div class="w-1/2 min-w-fit">
<div class="flex flex-col items-center pb-8"> <div class="flex flex-col items-center pb-8">
<div class="text-5xl font-bold tracking-tight text-center text-white">Coolify</div> <div class="text-5xl font-bold tracking-tight text-center text-white">Coolify</div>
<x-version /> <x-version />
@ -8,13 +8,13 @@
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<h1>{{ __('auth.login') }}</h1> <h1>{{ __('auth.login') }}</h1>
@if ($is_registration_enabled) @if ($is_registration_enabled)
<a href="/register" class="flex justify-center pt-2 hover:no-underline"> <a href="/register"
<button class="text-xs text-center text-white normal-case bg-transparent border-none rounded no-animation hover:no-underline btn btn-sm bg-coollabs-gradient">
class="normal-case rounded-none btn btn-sm btn-primary bg-coollabs-gradient">{{ __('auth.register_now') }}</button> {{ __('auth.register_now') }}
</a> </a>
@endif @endif
</div> </div>
<div class="w-96"> <div>
<form action="/login" method="POST" class="flex flex-col gap-2"> <form action="/login" method="POST" class="flex flex-col gap-2">
@csrf @csrf
@env('local') @env('local')
@ -29,8 +29,13 @@
<x-forms.input required type="email" name="email" label="{{ __('input.email') }}" autofocus /> <x-forms.input required type="email" name="email" label="{{ __('input.email') }}" autofocus />
<x-forms.input required type="password" name="password" label="{{ __('input.password') }}" /> <x-forms.input required type="password" name="password" label="{{ __('input.password') }}" />
@endenv @endenv
<x-forms.button type="submit">{{ __('auth.login') }}</x-forms.button>
@if (!$is_registration_enabled)
<div class="text-sm text-center">{{ __('auth.registration_disabled') }}</div>
@endif
@if ($errors->any()) @if ($errors->any())
<div class="text-center text-error"> <div class="text-xs text-center text-error">
<span>{{ __('auth.failed') }}</span> <span>{{ __('auth.failed') }}</span>
</div> </div>
@endif @endif
@ -39,10 +44,6 @@
{{ session('status') }} {{ session('status') }}
</div> </div>
@endif @endif
<x-forms.button type="submit">{{ __('auth.login') }}</x-forms.button>
@if (!$is_registration_enabled)
<div class="text-sm text-center">{{ __('auth.registration_disabled') }}</div>
@endif
</form> </form>
</div> </div>
</div> </div>

View File

@ -1,15 +1,15 @@
<x-layout-simple> <x-layout-simple>
<div class="flex items-center justify-center min-h-screen"> <div class="flex items-center justify-center min-h-screen ">
<div> <div class="w-1/2">
<div class="flex flex-col items-center pb-8"> <div class="flex flex-col items-center pb-8">
<div class="text-5xl font-bold tracking-tight text-center text-white">Coolify</div> <div class="text-5xl font-bold tracking-tight text-center text-white">Coolify</div>
<x-version /> <x-version />
</div> </div>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<h1>{{ __('auth.register') }}</h1> <h1>{{ __('auth.register') }}</h1>
<a href="/login" class="flex justify-center pt-2 hover:no-underline"> <a href="/login"
<button class="text-xs text-center text-white normal-case bg-transparent border-none rounded no-animation hover:no-underline btn btn-sm bg-coollabs-gradient">
class="normal-case rounded-none btn btn-sm btn-primary bg-coollabs-gradient">{{ __('auth.already_registered') }}</button> {{ __('auth.already_registered') }}
</a> </a>
</div> </div>
<form action="/register" method="POST" class="flex flex-col gap-2"> <form action="/register" method="POST" class="flex flex-col gap-2">
@ -37,10 +37,8 @@
<x-forms.button type="submit">{{ __('auth.register') }}</x-forms.button> <x-forms.button type="submit">{{ __('auth.register') }}</x-forms.button>
</form> </form>
@if ($errors->any()) @if ($errors->any())
<div class="fixed top-0 text-xs alert alert-error"> <div class="text-xs text-center text-error">
<ul> <span>{{ __('auth.failed') }}</span>
<li>{{ __('auth.failed') }}</li>
</ul>
</div> </div>
@endif @endif
</div> </div>

View File

@ -1,3 +1,8 @@
<x-layout> <x-layout>
<h1 class="pb-2">Command Center</h1>
@if ($servers->count() > 0)
<livewire:run-command :servers="$servers" /> <livewire:run-command :servers="$servers" />
@else
<div>No validated servers found.</div>
@endif
</x-layout> </x-layout>

View File

@ -1,6 +1,24 @@
<x-layout> <x-layout>
<h1 class="pb-2">Dashboard</h1> <h1 class="pb-2">Dashboard</h1>
<div class="text-sm "> <div class="pb-10 text-sm">
Something useful will be here. Something (more) useful will be here.
</div>
<div class="w-full rounded shadow stats stats-vertical lg:stats-horizontal">
<div class="stat">
<div class="stat-title">Servers</div>
<div class="stat-value">{{ $servers }}</div>
</div>
<div class="stat">
<div class="stat-title">Projects</div>
<div class="stat-value">{{ $projects }}</div>
</div>
<div class="stat">
<div class="stat-title">Resources</div>
<div class="stat-value">{{ $resources }}</div>
<div class="stat-desc">Applications, databases, etc...</div>
</div>
</div> </div>
</x-layout> </x-layout>

View File

@ -1,5 +1,4 @@
<div> <div>
<h1 class="pb-2">Command Center</h1>
<div class="pb-4 text-sm">Outputs are not saved at the moment, only available until you refresh or navigate.</div> <div class="pb-4 text-sm">Outputs are not saved at the moment, only available until you refresh or navigate.</div>
<form class="flex items-end justify-center gap-2" wire:submit.prevent='runCommand'> <form class="flex items-end justify-center gap-2" wire:submit.prevent='runCommand'>
<x-forms.input placeholder="ls -l" autofocus noDirty id="command" label="Command" required /> <x-forms.input placeholder="ls -l" autofocus noDirty id="command" label="Command" required />

View File

@ -68,12 +68,22 @@
<div class="flex items-center gap-2 py-4"> <div class="flex items-center gap-2 py-4">
<h3>Private Key</h3> <h3>Private Key</h3>
<a href="{{ route('server.private-key', ['server_uuid' => $server->uuid]) }}"> <a href="{{ route('server.private-key', ['server_uuid' => $server->uuid]) }}">
<x-forms.button>Change</x-forms.button> <x-forms.button>
@if (data_get($server, 'privateKey.uuid'))
Change
@else
Add
@endif
</x-forms.button>
</a> </a>
</div> </div>
@if (data_get($server, 'privateKey.uuid'))
<a href="{{ route('private-key.show', ['private_key_uuid' => data_get($server, 'privateKey.uuid')]) }}"> <a href="{{ route('private-key.show', ['private_key_uuid' => data_get($server, 'privateKey.uuid')]) }}">
<button class="text-white btn-link">{{ data_get($server, 'privateKey.name') }}</button> <button class="text-white btn-link">{{ data_get($server, 'privateKey.name') }}</button>
</a> </a>
@else
<div class="text-sm">No private key attached.</div>
@endif
<div class="flex items-center gap-2 py-4"> <div class="flex items-center gap-2 py-4">
<h3>Destinations</h3> <h3>Destinations</h3>
<a href="{{ route('destination.new', ['server_id' => $server->id]) }}"> <a href="{{ route('destination.new', ['server_id' => $server->id]) }}">

View File

@ -1,7 +1,14 @@
<x-layout> <x-layout>
<h1>Profile</h1> <h1>Profile</h1>
<div class="pb-10 text-sm breadcrumbs">
<ul>
<li>
Your user profile settings.
</li>
</ul>
</div>
<livewire:profile.form :request="$request" /> <livewire:profile.form :request="$request" />
<h3>2FA</h3> <h3 class="py-4">Two-factor Authentication</h3>
@if (session('status') == 'two-factor-authentication-enabled') @if (session('status') == 'two-factor-authentication-enabled')
<div class="mb-4 text-sm font-medium"> <div class="mb-4 text-sm font-medium">
Please finish configuring two factor authentication below. Read the QR code or enter the secret key Please finish configuring two factor authentication below. Read the QR code or enter the secret key
@ -15,10 +22,10 @@
</form> </form>
<div> <div>
<div>{!! $request->user()->twoFactorQrCodeSvg() !!}</div> <div>{!! $request->user()->twoFactorQrCodeSvg() !!}</div>
<div x-data="{ showCode: false }"> <div x-data="{ showCode: false }" class="py-2">
<x-forms.button x-on:click="showCode = !showCode">Show secret key to manually enter</x-forms.button> <x-forms.button x-on:click="showCode = !showCode">Show secret key to manually enter</x-forms.button>
<template x-if="showCode"> <template x-if="showCode">
<div class="text-sm">{!! decrypt($request->user()->two_factor_secret) !!}</div> <div class="py-2 text-sm">{!! decrypt($request->user()->two_factor_secret) !!}</div>
</template> </template>
</div> </div>
</div> </div>
@ -63,7 +70,7 @@
</div> </div>
@endif @endif
@else @else
<div class="text-sm">Two factor authentication is <span class="text-helper">disabled</span>.</div> <div class="pb-2 text-sm">Two factor authentication is <span class="text-helper">disabled</span>.</div>
<form action="/user/two-factor-authentication" method="POST"> <form action="/user/two-factor-authentication" method="POST">
@csrf @csrf
<x-forms.button type="submit">Configure 2FA</x-forms.button> <x-forms.button type="submit">Configure 2FA</x-forms.button>

View File

@ -12,11 +12,15 @@
<a href="{{ route('project.show', ['project_uuid' => data_get($project, 'uuid')]) }}" <a href="{{ route('project.show', ['project_uuid' => data_get($project, 'uuid')]) }}"
class="box">{{ $project->name }}</a> class="box">{{ $project->name }}</a>
@empty @empty
<div x-data> <div>
No project found. Use the <button x-on:click="$dispatch('slash')" class='text-white underline'>magic No project found. Use the magic
bar</button> to create a new bar (press <span class="kbd-custom">/</span>) to create a new
project. project.
</div> </div>
<div>
If you do not have a project yet, just create a resource (application, database, etc.) first, it will
create a new project for you automatically.
</div>
@endforelse @endforelse
</div> </div>
</x-layout> </x-layout>

View File

@ -7,10 +7,17 @@
</li> </li>
</ul> </ul>
</div> </div>
<div class="flex flex-col gap-2"> <div class="grid grid-cols-2">
@forelse ($servers as $server) @forelse ($servers as $server)
<a href="{{ route('server.show', ['server_uuid' => data_get($server, 'uuid')]) }}" <a class="text-center hover:no-underline box group"
class="box">{{ $server->name }}</a> href="{{ route('server.show', ['server_uuid' => data_get($server, 'uuid')]) }}">
<div class="group-hover:text-white">
<div>{{ $server->name }}</div>
@if (!$server->settings->is_validated)
<div class="text-xs text-error">not validated</div>
@endif
</div>
</a>
@empty @empty
<div class="flex flex-col"> <div class="flex flex-col">
<div>Without a server, you won't be able to do much.</div> <div>Without a server, you won't be able to do much.</div>

View File

@ -18,11 +18,11 @@
</tbody> </tbody>
</table> </table>
</div> </div>
<div class="py-4"> {{-- <div class="py-4">
<h2>Invite a new member</h2> <h3>Invite a new member</h3>
<form class="flex items-center gap-2"> <form class="flex items-center gap-2">
<x-forms.input type="email" name="email" placeholder="Email" /> <x-forms.input type="email" name="email" placeholder="Email" />
<x-forms.button>Invite</x-forms.button> <x-forms.button>Invite</x-forms.button>
</form> </form>
</div> </div> --}}
</x-layout> </x-layout>

View File

@ -86,101 +86,18 @@ Route::prefix('magic')->middleware(['auth'])->group(function () {
}); });
}); });
Route::middleware(['auth'])->group(function () { Route::middleware(['auth'])->group(function () {
// Route::get('/magic', function () {
// try {
// $id = session('currentTeam')->id;
// $is_new_project = request()->query('project') === 'new';
// $is_new_environment = request()->query('environment') === 'new';
// // Get servers
// if (request()->query('servers') === 'true') {
// $servers = Server::where('team_id', $id)->get();
// return response()->json([
// 'servers' => $servers,
// ]);
// }
// // Get destinations
// if ((request()->query('server') && request()->query('destinations') === 'true') || request()->query('destinations') === 'true') {
// $destinations = Server::destinations(request()->query('server'));
// return response()->json([
// 'destinations' => $destinations->toArray(),
// ]);
// }
// // Get private Keys
// if (request()->query('privateKeys') === 'true') {
// $privateKeys = PrivateKey::where('team_id', $id)->get();
// return response()->json([
// 'privateKeys' => $privateKeys->toArray(),
// ]);
// }
// // Get sources
// if (request()->query('sources') === 'true') {
// $github_apps = GithubApp::private();
// $sources = $github_apps;
// return response()->json([
// 'sources' => $sources->toArray(),
// ]);
// }
// // Get projects
// if ((request()->query('server') && request()->query('destination') && request()->query('projects') === 'true') || request()->query('projects') === 'true') {
// $projects = Project::where('team_id', $id)->get()->sortBy('name');
// return response()->json([
// 'projects' => $projects->toArray(),
// ]);
// }
// // Get environments
// if (request()->query('server') && request()->query('destination') && request()->query('project') && request()->query('environments') === 'true') {
// $environments = Project::where('team_id', $id)->where('uuid', request()->query('project'))->first()->environments;
// return response()->json([
// 'environments' => $environments->toArray(),
// ]);
// }
// if ($is_new_project) {
// $project = Project::create([
// 'name' => request()->query('name') ?? generate_random_name(),
// 'team_id' => $id,
// ]);
// return response()->json([
// 'project_uuid' => $project->uuid
// ]);
// }
// if ($is_new_environment) {
// $environment = Project::where('uuid', request()->query('project'))->first()->environments->where('name', request()->query('name'))->first();
// if (!$environment) {
// $environment = Environment::create([
// 'name' => request()->query('name') ?? generate_random_name(),
// 'project_id' => Project::where('uuid', request()->query('project'))->first()->id,
// ]);
// }
// return response()->json([
// 'environment_name' => $environment->name
// ]);
// }
// return response()->json([
// 'magic' => true,
// ]);
// } catch (\Throwable $e) {
// return general_error_handler($e, isJson: true);
// }
// });
Route::get('/', function () { Route::get('/', function () {
$id = session('currentTeam')->id; $id = session('currentTeam')->id;
$projects = Project::where('team_id', $id)->get(); $projects = Project::where('team_id', $id)->get();
$servers = Server::where('team_id', $id)->get(); $servers = Server::where('team_id', $id)->get()->count();
$destinations = $servers->map(function ($server) { $resources = 0;
return $server->standaloneDockers->merge($server->swarmDockers); foreach ($projects as $project) {
})->flatten(); $resources += $project->applications->count();
$private_keys = PrivateKey::where('team_id', $id)->get(); }
$github_apps = GithubApp::private();
return view('dashboard', [ return view('dashboard', [
'servers' => $servers->sortBy('name'), 'servers' => $servers,
'projects' => $projects->sortBy('name'), 'projects' => $projects->count(),
'destinations' => $destinations->sortBy('name'), 'resources' => $resources,
'private_keys' => $private_keys->sortBy('name'),
'github_apps' => $github_apps->sortBy('name'),
]); ]);
})->name('dashboard'); })->name('dashboard');
@ -215,9 +132,6 @@ Route::middleware(['auth'])->group(function () {
Route::get('/command-center', function () { Route::get('/command-center', function () {
$servers = Server::validated(); $servers = Server::validated();
if ($servers->count() === 0) {
return redirect()->route('dashboard');
}
return view('command-center', [ return view('command-center', [
'servers' => $servers, 'servers' => $servers,
]); ]);