Merge branch 'feature' into feature/oauth

This commit is contained in:
Andras Bacsai
2024-03-20 13:58:31 +01:00
committed by GitHub
155 changed files with 2455 additions and 898 deletions

View File

@@ -4,7 +4,6 @@ namespace App\Livewire\Admin;
use App\Models\User;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Crypt;
use Livewire\Component;
class Index extends Component

View File

@@ -73,7 +73,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
public function restartBoarding()
{
return redirect()->route('boarding');
return redirect()->route('onboarding');
}
public function skipBoarding()
{
@@ -126,6 +126,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
}
public function getProxyType()
{
// Set Default Proxy Type
$this->selectProxy(ProxyTypes::TRAEFIK_V2->value);
// $proxyTypeSet = $this->createdServer->proxy->type;
// if (!$proxyTypeSet) {

View File

@@ -17,10 +17,14 @@ class LayoutPopups extends Component
{
$this->dispatch('success', 'Realtime events configured!');
}
public function disable()
public function disableSponsorship()
{
auth()->user()->update(['is_notification_sponsorship_enabled' => false]);
}
public function disableNotifications()
{
auth()->user()->update(['is_notification_notifications_enabled' => false]);
}
public function render()
{
return view('livewire.layout-popups');

View File

@@ -2,6 +2,7 @@
namespace App\Livewire\Profile;
use Illuminate\Support\Facades\Hash;
use Livewire\Attributes\Validate;
use Livewire\Component;
@@ -10,6 +11,13 @@ class Index extends Component
public int $userId;
public string $email;
#[Validate('required')]
public string $current_password;
#[Validate('required|min:8')]
public string $new_password;
#[Validate('required|min:8|same:new_password')]
public string $new_password_confirmation;
#[Validate('required')]
public string $name;
public function mount()
@@ -19,7 +27,6 @@ class Index extends Component
$this->email = auth()->user()->email;
}
public function submit()
{
try {
$this->validate();
@@ -27,7 +34,30 @@ class Index extends Component
'name' => $this->name,
]);
$this->dispatch('success', 'Profile updated');
$this->dispatch('success', 'Profile updated.');
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function resetPassword()
{
try {
$this->validate();
if (!Hash::check($this->current_password, auth()->user()->password)) {
$this->dispatch('error', 'Current password is incorrect.');
return;
}
if ($this->new_password !== $this->new_password_confirmation) {
$this->dispatch('error', 'The two new passwords does not match.');
return;
}
auth()->user()->update([
'password' => Hash::make($this->new_password),
]);
$this->dispatch('success', 'Password updated.');
$this->current_password = '';
$this->new_password = '';
$this->new_password_confirmation = '';
} catch (\Throwable $e) {
return handleError($e, $this);
}

View File

@@ -66,6 +66,10 @@ class General extends Component
'application.docker_compose_custom_build_command' => 'nullable',
'application.custom_labels' => 'nullable',
'application.custom_docker_run_options' => 'nullable',
'application.pre_deployment_command' => 'nullable',
'application.pre_deployment_command_container' => 'nullable',
'application.post_deployment_command' => 'nullable',
'application.post_deployment_command_container' => 'nullable',
'application.settings.is_static' => 'boolean|required',
'application.settings.is_raw_compose_deployment_enabled' => 'boolean|required',
'application.settings.is_build_server_enabled' => 'boolean|required',
@@ -112,6 +116,10 @@ class General extends Component
} catch (\Throwable $e) {
$this->dispatch('error', $e->getMessage());
}
if ($this->application->build_pack === 'dockercompose') {
$this->application->fqdn = null;
$this->application->settings->save();
}
$this->parsedServiceDomains = $this->application->docker_compose_domains ? json_decode($this->application->docker_compose_domains, true) : [];
$this->ports_exposes = $this->application->ports_exposes;
@@ -120,7 +128,7 @@ class General extends Component
}
$this->isConfigurationChanged = $this->application->isConfigurationChanged();
$this->customLabels = $this->application->parseContainerLabels();
if (!$this->customLabels && $this->application->destination->server->proxyType() === 'TRAEFIK_V2') {
if (!$this->customLabels && $this->application->destination->server->proxyType() !== 'NONE') {
$this->customLabels = str(implode("|", generateLabelsApplication($this->application)))->replace("|", "\n");
$this->application->custom_labels = base64_encode($this->customLabels);
$this->application->save();
@@ -163,7 +171,12 @@ class General extends Component
}
return $domain;
}
public function updatedApplicationBaseDirectory() {
raY('asdf');
if ($this->application->build_pack === 'dockercompose') {
$this->loadComposeFile();
}
}
public function updatedApplicationBuildPack()
{
if ($this->application->build_pack !== 'nixpacks') {
@@ -211,12 +224,11 @@ class General extends Component
$this->application->fqdn = $this->application->fqdn->unique()->implode(',');
$this->application->save();
$this->resetDefaultLabels(false);
// $this->dispatch('success', 'Labels reset to default!');
}
public function submit($showToaster = true)
{
try {
if (!$this->customLabels && $this->application->destination->server->proxyType() === 'TRAEFIK_V2') {
if (!$this->customLabels && $this->application->destination->server->proxyType() !== 'NONE') {
$this->customLabels = str(implode("|", generateLabelsApplication($this->application)))->replace("|", "\n");
$this->application->custom_labels = base64_encode($this->customLabels);
$this->application->save();

View File

@@ -10,7 +10,8 @@ class Execution extends Component
public $backup;
public $executions;
public $s3s;
public function mount() {
public function mount()
{
$backup_uuid = request()->route('backup_uuid');
$project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
if (!$project) {
@@ -34,6 +35,11 @@ class Execution extends Component
$this->executions = $executions;
$this->s3s = currentTeam()->s3s;
}
public function cleanupFailed()
{
$this->backup->executions()->where('status', 'failed')->delete();
$this->dispatch('refreshBackupExecutions');
}
public function render()
{
return view('livewire.project.database.backup.execution');

View File

@@ -34,7 +34,7 @@ class BackupExecutions extends Component
}
$execution->delete();
$this->dispatch('success', 'Backup deleted.');
$this->dispatch('refreshBackupExecutions');
$this->refreshBackupExecutions();
}
public function download($exeuctionId)
{
@@ -65,6 +65,6 @@ class BackupExecutions extends Component
}
public function refreshBackupExecutions(): void
{
$this->executions = data_get($this->backup, 'executions', []);
$this->executions = $this->backup->executions()->get()->sortByDesc('created_at');
}
}

View File

@@ -17,9 +17,14 @@ class Edit extends Component
public function saveKey($data)
{
try {
$found = $this->project->environment_variables()->where('key', $data['key'])->first();
if ($found) {
throw new \Exception('Variable already exists.');
}
$this->project->environment_variables()->create([
'key' => $data['key'],
'value' => $data['value'],
'is_multiline' => $data['is_multiline'],
'type' => 'project',
'team_id' => currentTeam()->id,
]);

View File

@@ -21,9 +21,14 @@ class EnvironmentEdit extends Component
public function saveKey($data)
{
try {
$found = $this->environment->environment_variables()->where('key', $data['key'])->first();
if ($found) {
throw new \Exception('Variable already exists.');
}
$this->environment->environment_variables()->create([
'key' => $data['key'],
'value' => $data['value'],
'is_multiline' => $data['is_multiline'],
'type' => 'environment',
'team_id' => currentTeam()->id,
]);

View File

@@ -17,7 +17,6 @@ class DockerCompose extends Component
public array $query;
public function mount()
{
$this->parameters = get_route_parameters();
$this->query = request()->query();
if (isDev()) {
@@ -40,12 +39,17 @@ class DockerCompose extends Component
}
public function submit()
{
$server_id = $this->query['server_id'];
try {
$this->validate([
'dockerComposeRaw' => 'required'
]);
$this->dockerComposeRaw = Yaml::dump(Yaml::parse($this->dockerComposeRaw), 10, 2, Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK);
$server_id = $this->query['server_id'];
$isValid = validateComposeFile($this->dockerComposeRaw, $server_id);
if ($isValid !== 'OK') {
return $this->dispatch('error', "Invalid docker-compose file.\n$isValid");
}
$project = Project::where('uuid', $this->parameters['project_uuid'])->first();
$environment = $project->load(['environments'])->environments->where('name', $this->parameters['environment_name'])->first();
@@ -74,7 +78,6 @@ class DockerCompose extends Component
'environment_name' => $environment->name,
'project_uuid' => $project->uuid,
]);
} catch (\Throwable $e) {
return handleError($e, $this);
}

View File

@@ -38,8 +38,12 @@ class Configuration extends Component
}
public function check_status()
{
dispatch_sync(new ContainerStatusJob($this->service->server));
$this->dispatch('refresh')->self();
$this->dispatch('serviceStatusChanged');
try {
dispatch_sync(new ContainerStatusJob($this->service->server));
$this->dispatch('refresh')->self();
$this->dispatch('serviceStatusChanged');
} catch (\Exception $e) {
return handleError($e, $this);
}
}
}

View File

@@ -41,7 +41,6 @@ class StackForm extends Component
}
public function saveCompose($raw)
{
$this->service->docker_compose_raw = $raw;
$this->submit();
}
@@ -55,6 +54,10 @@ class StackForm extends Component
{
try {
$this->validate();
$isValid = validateComposeFile($this->service->docker_compose_raw, $this->service->server->id);
if ($isValid !== 'OK') {
throw new \Exception("Invalid docker-compose file.\n$isValid");
}
$this->service->save();
$this->service->saveExtraFields($this->fields);
$this->service->parse();

View File

@@ -11,17 +11,20 @@ class Add extends Component
public string $key;
public ?string $value = null;
public bool $is_build_time = false;
public bool $is_multiline = false;
protected $listeners = ['clearAddEnv' => 'clear'];
protected $rules = [
'key' => 'required|string',
'value' => 'nullable',
'is_build_time' => 'required|boolean',
'is_multiline' => 'required|boolean',
];
protected $validationAttributes = [
'key' => 'key',
'value' => 'value',
'is_build_time' => 'build',
'is_multiline' => 'multiline',
];
public function mount()
@@ -43,6 +46,7 @@ class Add extends Component
'key' => $this->key,
'value' => $this->value,
'is_build_time' => $this->is_build_time,
'is_multiline' => $this->is_multiline,
'is_preview' => $this->is_preview,
]);
$this->clear();
@@ -53,5 +57,6 @@ class Add extends Component
$this->key = '';
$this->value = '';
$this->is_build_time = false;
$this->is_multiline = false;
}
}

View File

@@ -11,7 +11,7 @@ class All extends Component
{
public $resource;
public bool $showPreview = false;
public string|null $modalId = null;
public ?string $modalId = null;
public ?string $variables = null;
public ?string $variablesPreview = null;
public string $view = 'normal';
@@ -34,6 +34,9 @@ class All extends Component
if ($item->is_shown_once) {
return "$item->key=(locked secret)";
}
if ($item->is_multiline) {
return "$item->key=(multiline, edit in normal view)";
}
return "$item->key=$item->value";
})->sort()->join('
');
@@ -42,6 +45,9 @@ class All extends Component
if ($item->is_shown_once) {
return "$item->key=(locked secret)";
}
if ($item->is_multiline) {
return "$item->key=(multiline, edit in normal view)";
}
return "$item->key=$item->value";
})->sort()->join('
');
@@ -67,7 +73,7 @@ class All extends Component
$found = $this->resource->environment_variables()->where('key', $key)->first();
}
if ($found) {
if ($found->is_shown_once) {
if ($found->is_shown_once || $found->is_multiline) {
continue;
}
$found->value = $variable;
@@ -144,6 +150,7 @@ class All extends Component
$environment->key = $data['key'];
$environment->value = $data['value'];
$environment->is_build_time = $data['is_build_time'];
$environment->is_multiline = $data['is_multiline'];
$environment->is_preview = $data['is_preview'];
switch ($this->resource->type()) {

View File

@@ -21,6 +21,7 @@ class Show extends Component
'env.key' => 'required|string',
'env.value' => 'nullable',
'env.is_build_time' => 'required|boolean',
'env.is_multiline' => 'required|boolean',
'env.is_shown_once' => 'required|boolean',
'env.real_value' => 'nullable',
];
@@ -28,6 +29,7 @@ class Show extends Component
'env.key' => 'Key',
'env.value' => 'Value',
'env.is_build_time' => 'Build Time',
'env.is_multiline' => 'Multiline',
'env.is_shown_once' => 'Shown Once',
];

View File

@@ -71,6 +71,9 @@ class GetLogs extends Component
}
public function getLogs($refresh = false)
{
if (!$this->server->isFunctional()) {
return;
}
if ($this->resource?->getMorphClass() === 'App\Models\Application') {
if (str($this->container)->contains('-pr-')) {
$this->pull_request = "Pull Request: " . str($this->container)->afterLast('-pr-')->beforeLast('_')->value();
@@ -79,6 +82,9 @@ class GetLogs extends Component
}
}
if (!$refresh && ($this->resource?->getMorphClass() === 'App\Models\Service' || str($this->container)->contains('-pr-'))) return;
if (!$this->numberOfLines) {
$this->numberOfLines = 1000;
}
if ($this->container) {
if ($this->showTimeStamps) {
if ($this->server->isSwarm()) {

View File

@@ -29,6 +29,9 @@ class Logs extends Component
{
try {
$server = $this->servers->firstWhere('id', $server_id);
if (!$server->isFunctional()) {
return;
}
if ($server->isSwarm()) {
$containers = collect([
[
@@ -96,7 +99,6 @@ class Logs extends Component
$this->resource->databases()->get()->each(function ($database) {
$this->containers->push(data_get($database, 'name') . '-' . data_get($this->resource, 'uuid'));
});
if ($this->resource->server->isFunctional()) {
$this->servers = $this->servers->push($this->resource->server);
}

View File

@@ -35,7 +35,7 @@ class ResourceLimits extends Component
if (!$this->resource->limits_memory_swap) {
$this->resource->limits_memory_swap = "0";
}
if (!$this->resource->limits_memory_swappiness) {
if (is_null($this->resource->limits_memory_swappiness)) {
$this->resource->limits_memory_swappiness = "60";
}
if (!$this->resource->limits_memory_reservation) {
@@ -47,7 +47,7 @@ class ResourceLimits extends Component
if ($this->resource->limits_cpuset === "") {
$this->resource->limits_cpuset = null;
}
if (!$this->resource->limits_cpu_shares) {
if (is_null($this->resource->limits_cpu_shares)) {
$this->resource->limits_cpu_shares = 1024;
}
$this->validate();

View File

@@ -45,7 +45,7 @@ class ResourceOperations extends Component
'destination_id' => $new_destination->id,
]);
$new_resource->save();
if ($new_resource->destination->server->proxyType() === 'TRAEFIK_V2') {
if ($new_resource->destination->server->proxyType() !== 'NONE') {
$customLabels = str(implode("|", generateLabelsApplication($new_resource)))->replace("|", "\n");
$new_resource->custom_labels = base64_encode($customLabels);
$new_resource->save();

View File

@@ -89,6 +89,7 @@ class ByIp extends Component
'team_id' => currentTeam()->id,
'private_key_id' => $this->private_key_id,
'proxy' => [
// set default proxy type to traefik v2
"type" => ProxyTypes::TRAEFIK_V2->value,
"status" => ProxyStatus::EXITED->value,
],

View File

@@ -21,7 +21,7 @@ class Proxy extends Component
public function mount()
{
$this->selectedProxy = data_get($this->server, 'proxy.type');
$this->selectedProxy = $this->server->proxyType();
$this->redirect_url = data_get($this->server, 'proxy.redirect_url');
}
@@ -54,8 +54,7 @@ class Proxy extends Component
SaveConfiguration::run($this->server, $this->proxy_settings);
$this->server->proxy->redirect_url = $this->redirect_url;
$this->server->save();
setup_default_redirect_404(redirect_url: $this->server->proxy->redirect_url, server: $this->server);
$this->server->setupDefault404Redirect();
$this->dispatch('success', 'Proxy configuration saved.');
} catch (\Throwable $e) {
return handleError($e, $this);
@@ -66,6 +65,9 @@ class Proxy extends Component
{
try {
$this->proxy_settings = CheckConfiguration::run($this->server, true);
SaveConfiguration::run($this->server, $this->proxy_settings);
$this->server->save();
$this->dispatch('success', 'Proxy configuration saved.');
} catch (\Throwable $e) {
return handleError($e, $this);
}

View File

@@ -14,9 +14,17 @@ class DynamicConfigurationNavbar extends Component
public function delete(string $fileName)
{
$server = Server::ownedByCurrentTeam()->whereId($this->server_id)->first();
$proxy_path = get_proxy_path();
$proxy_path = $server->proxyPath();
$proxy_type = $server->proxyType();
$file = str_replace('|', '.', $fileName);
if ($proxy_type === 'CADDY' && $file === "Caddyfile") {
$this->dispatch('error', 'Cannot delete Caddyfile.');
return;
}
instant_remote_process(["rm -f {$proxy_path}/dynamic/{$file}"], $server);
if ($proxy_type === 'CADDY') {
$server->reloadCaddy();
}
$this->dispatch('success', 'File deleted.');
$this->dispatch('loadDynamicConfigurations');
$this->dispatch('refresh');

View File

@@ -11,26 +11,32 @@ class DynamicConfigurations extends Component
public ?Server $server = null;
public $parameters = [];
public Collection $contents;
protected $listeners = ['loadDynamicConfigurations', 'refresh' => '$refresh'];
public function getListeners()
{
$teamId = auth()->user()->currentTeam()->id;
return [
"echo-private:team.{$teamId},ProxyStatusChanged" => 'loadDynamicConfigurations',
'loadDynamicConfigurations',
'refresh' => '$refresh'
];
}
protected $rules = [
'contents.*' => 'nullable|string',
];
public function loadDynamicConfigurations()
{
$proxy_path = get_proxy_path();
$proxy_path = $this->server->proxyPath();
$files = instant_remote_process(["mkdir -p $proxy_path/dynamic && ls -1 {$proxy_path}/dynamic"], $this->server);
$files = collect(explode("\n", $files))->filter(fn ($file) => !empty($file));
$files = $files->map(fn ($file) => trim($file));
$files = $files->sort();
if ($files->contains('coolify.yaml')) {
$files = $files->filter(fn ($file) => $file !== 'coolify.yaml')->prepend('coolify.yaml');
}
$contents = collect([]);
foreach ($files as $file) {
$without_extension = str_replace('.', '|', $file);
$contents[$without_extension] = instant_remote_process(["cat {$proxy_path}/dynamic/{$file}"], $this->server);
}
$this->contents = $contents;
$this->dispatch('refresh');
}
public function mount()
{

View File

@@ -29,7 +29,6 @@ class NewDynamicConfiguration extends Component
'fileName' => 'required',
'value' => 'required',
]);
if (data_get($this->parameters, 'server_uuid')) {
$this->server = Server::ownedByCurrentTeam()->whereUuid(data_get($this->parameters, 'server_uuid'))->first();
}
@@ -39,14 +38,21 @@ class NewDynamicConfiguration extends Component
if (is_null($this->server)) {
return redirect()->route('server.index');
}
if (!str($this->fileName)->endsWith('.yaml') && !str($this->fileName)->endsWith('.yml')) {
$this->fileName = "{$this->fileName}.yaml";
$proxy_type = $this->server->proxyType();
if ($proxy_type === 'TRAEFIK_V2') {
if (!str($this->fileName)->endsWith('.yaml') && !str($this->fileName)->endsWith('.yml')) {
$this->fileName = "{$this->fileName}.yaml";
}
if ($this->fileName === 'coolify.yaml') {
$this->dispatch('error', 'File name is reserved.');
return;
}
} else if ($proxy_type === 'CADDY') {
if (!str($this->fileName)->endsWith('.caddy')) {
$this->fileName = "{$this->fileName}.caddy";
}
}
if ($this->fileName === 'coolify.yaml') {
$this->dispatch('error', 'File name is reserved.');
return;
}
$proxy_path = get_proxy_path();
$proxy_path = $this->server->proxyPath();
$file = "{$proxy_path}/dynamic/{$this->fileName}";
if ($this->newFile) {
$exists = instant_remote_process(["test -f $file && echo 1 || echo 0"], $this->server);
@@ -55,11 +61,18 @@ class NewDynamicConfiguration extends Component
return;
}
}
$yaml = Yaml::parse($this->value);
$yaml = Yaml::dump($yaml, 10, 2);
$this->value = $yaml;
if ($proxy_type === 'TRAEFIK_V2') {
$yaml = Yaml::parse($this->value);
$yaml = Yaml::dump($yaml, 10, 2);
$this->value = $yaml;
}
$base64_value = base64_encode($this->value);
instant_remote_process(["echo '{$base64_value}' | base64 -d > {$file}"], $this->server);
instant_remote_process([
"echo '{$base64_value}' | base64 -d > {$file}",
], $this->server);
if ($proxy_type === 'CADDY') {
$this->server->reloadCaddy();
}
$this->dispatch('loadDynamicConfigurations');
$this->dispatch('dynamic-configuration-added');
$this->dispatch('success', 'Dynamic configuration saved.');

View File

@@ -2,12 +2,9 @@
namespace App\Livewire\Settings;
use App\Jobs\ContainerStatusJob;
use App\Models\InstanceSettings as ModelsInstanceSettings;
use App\Models\Server;
use Livewire\Component;
use Spatie\Url\Url;
use Symfony\Component\Yaml\Yaml;
class Configuration extends Component
{
@@ -78,13 +75,7 @@ class Configuration extends Component
$this->settings->save();
$this->server = Server::findOrFail(0);
$this->setup_instance_fqdn();
$this->server->setupDynamicProxyConfiguration();
$this->dispatch('success', 'Instance settings updated successfully!');
}
private function setup_instance_fqdn()
{
setup_dynamic_configuration();
}
}

View File

@@ -24,6 +24,8 @@ class Change extends Component
public string $name;
public bool $is_system_wide;
public $applications;
protected $rules = [
'github_app.name' => 'required|string',
'github_app.organization' => 'nullable|string',
@@ -90,6 +92,7 @@ class Change extends Component
if (!$this->github_app) {
return redirect()->route('source.all');
}
$this->applications = $this->github_app->applications;
$settings = InstanceSettings::get();
$this->github_app->makeVisible('client_secret')->makeVisible('webhook_secret');
@@ -170,6 +173,11 @@ class Change extends Component
public function delete()
{
try {
if ($this->github_app->applications->isNotEmpty()) {
$this->dispatch('error', 'This source is being used by an application. Please delete all applications first.');
$this->github_app->makeVisible('client_secret')->makeVisible('webhook_secret');
return;
}
$this->github_app->delete();
return redirect()->route('source.all');
} catch (\Throwable $e) {

View File

@@ -13,9 +13,14 @@ class TeamSharedVariablesIndex extends Component
public function saveKey($data)
{
try {
$found = $this->team->environment_variables()->where('key', $data['key'])->first();
if ($found) {
throw new \Exception('Variable already exists.');
}
$this->team->environment_variables()->create([
'key' => $data['key'],
'value' => $data['value'],
'is_multiline' => $data['is_multiline'],
'type' => 'team',
'team_id' => currentTeam()->id,
]);