diff --git a/README.md b/README.md
index 3a211d4b1..db38eb142 100644
--- a/README.md
+++ b/README.md
@@ -28,12 +28,12 @@ https://coolify.io/sponsorships
Thank you so much!
-Special thanks to our biggest sponsors, [CCCareers](https://cccareers.org/) and [Appwrite](https://appwrite.io)!
+Special thanks to our biggest sponsor, [CCCareers](https://cccareers.org/)!
-
## Github Sponsors ($40+)
+
diff --git a/app/Http/Controllers/Webhook/Github.php b/app/Http/Controllers/Webhook/Github.php
index 02ba017ca..214843aab 100644
--- a/app/Http/Controllers/Webhook/Github.php
+++ b/app/Http/Controllers/Webhook/Github.php
@@ -22,7 +22,6 @@ class Github extends Controller
public function manual(Request $request)
{
try {
- ray($request);
$return_payloads = collect([]);
$x_github_delivery = request()->header('X-GitHub-Delivery');
if (app()->isDownForMaintenance()) {
@@ -68,6 +67,10 @@ class Github extends Controller
if (Str::isMatch('/refs\/heads\/*/', $branch)) {
$branch = Str::after($branch, 'refs/heads/');
}
+ $added_files = data_get($payload, 'commits.*.added');
+ $removed_files = data_get($payload, 'commits.*.removed');
+ $modified_files = data_get($payload, 'commits.*.modified');
+ $changed_files = collect($added_files)->concat($removed_files)->concat($modified_files)->unique()->flatten();
ray('Manual Webhook GitHub Push Event with branch: ' . $branch);
}
if ($x_github_event === 'pull_request') {
@@ -118,24 +121,41 @@ class Github extends Controller
}
if ($x_github_event === 'push') {
if ($application->isDeployable()) {
- ray('Deploying ' . $application->name . ' with branch ' . $branch);
- $deployment_uuid = new Cuid2(7);
- queue_application_deployment(
- application: $application,
- deployment_uuid: $deployment_uuid,
- force_rebuild: false,
- is_webhook: true,
- );
- $return_payloads->push([
- 'application' => $application->name,
- 'status' => 'success',
- 'message' => 'Deployment queued.',
- ]);
+ $is_watch_path_triggered = $application->isWatchPathsTriggered($changed_files);
+ if ($is_watch_path_triggered || is_null($application->watch_paths)) {
+ ray('Deploying ' . $application->name . ' with branch ' . $branch);
+ $deployment_uuid = new Cuid2(7);
+ queue_application_deployment(
+ application: $application,
+ deployment_uuid: $deployment_uuid,
+ force_rebuild: false,
+ is_webhook: true,
+ );
+ $return_payloads->push([
+ 'status' => 'success',
+ 'message' => 'Deployment queued.',
+ 'application_uuid' => $application->uuid,
+ 'application_name' => $application->name,
+ ]);
+ } else {
+ $paths = str($application->watch_paths)->explode("\n");
+ $return_payloads->push([
+ 'status' => 'failed',
+ 'message' => 'Changed files do not match watch paths. Ignoring deployment.',
+ 'application_uuid' => $application->uuid,
+ 'application_name' => $application->name,
+ 'details' => [
+ 'changed_files' => $changed_files,
+ 'watch_paths' => $paths,
+ ],
+ ]);
+ }
} else {
$return_payloads->push([
- 'application' => $application->name,
'status' => 'failed',
'message' => 'Deployments disabled.',
+ 'application_uuid' => $application->uuid,
+ 'application_name' => $application->name,
]);
}
}
@@ -266,6 +286,10 @@ class Github extends Controller
if (Str::isMatch('/refs\/heads\/*/', $branch)) {
$branch = Str::after($branch, 'refs/heads/');
}
+ $added_files = data_get($payload, 'commits.*.added');
+ $removed_files = data_get($payload, 'commits.*.removed');
+ $modified_files = data_get($payload, 'commits.*.modified');
+ $changed_files = collect($added_files)->concat($removed_files)->concat($modified_files)->unique()->flatten();
ray('Webhook GitHub Push Event: ' . $id . ' with branch: ' . $branch);
}
if ($x_github_event === 'pull_request') {
@@ -298,32 +322,50 @@ class Github extends Controller
$isFunctional = $application->destination->server->isFunctional();
if (!$isFunctional) {
$return_payloads->push([
- 'application' => $application->name,
'status' => 'failed',
'message' => 'Server is not functional.',
+ 'application_uuid' => $application->uuid,
+ 'application_name' => $application->name,
]);
continue;
}
if ($x_github_event === 'push') {
if ($application->isDeployable()) {
- ray('Deploying ' . $application->name . ' with branch ' . $branch);
- $deployment_uuid = new Cuid2(7);
- queue_application_deployment(
- application: $application,
- deployment_uuid: $deployment_uuid,
- force_rebuild: false,
- is_webhook: true
- );
- $return_payloads->push([
- 'application' => $application->name,
- 'status' => 'success',
- 'message' => 'Deployment queued.',
- ]);
+ $is_watch_path_triggered = $application->isWatchPathsTriggered($changed_files);
+ if ($is_watch_path_triggered || is_null($application->watch_paths)) {
+ ray('Deploying ' . $application->name . ' with branch ' . $branch);
+ $deployment_uuid = new Cuid2(7);
+ queue_application_deployment(
+ application: $application,
+ deployment_uuid: $deployment_uuid,
+ force_rebuild: false,
+ is_webhook: true,
+ );
+ $return_payloads->push([
+ 'status' => 'success',
+ 'message' => 'Deployment queued.',
+ 'application_uuid' => $application->uuid,
+ 'application_name' => $application->name,
+ ]);
+ } else {
+ $paths = str($application->watch_paths)->explode("\n");
+ $return_payloads->push([
+ 'status' => 'failed',
+ 'message' => 'Changed files do not match watch paths. Ignoring deployment.',
+ 'application_uuid' => $application->uuid,
+ 'application_name' => $application->name,
+ 'details' => [
+ 'changed_files' => $changed_files,
+ 'watch_paths' => $paths,
+ ],
+ ]);
+ }
} else {
$return_payloads->push([
- 'application' => $application->name,
'status' => 'failed',
'message' => 'Deployments disabled.',
+ 'application_uuid' => $application->uuid,
+ 'application_name' => $application->name,
]);
}
}
diff --git a/app/Http/Controllers/Webhook/Gitlab.php b/app/Http/Controllers/Webhook/Gitlab.php
index 5b2911e88..65ce9910b 100644
--- a/app/Http/Controllers/Webhook/Gitlab.php
+++ b/app/Http/Controllers/Webhook/Gitlab.php
@@ -51,6 +51,10 @@ class Gitlab extends Controller
]);
return response($return_payloads);
}
+ $added_files = data_get($payload, 'commits.*.added');
+ $removed_files = data_get($payload, 'commits.*.removed');
+ $modified_files = data_get($payload, 'commits.*.modified');
+ $changed_files = collect($added_files)->concat($removed_files)->concat($modified_files)->unique()->flatten();
ray('Manual Webhook GitLab Push Event with branch: ' . $branch);
}
if ($x_gitlab_event === 'merge_request') {
@@ -113,19 +117,41 @@ class Gitlab extends Controller
}
if ($x_gitlab_event === 'push') {
if ($application->isDeployable()) {
- ray('Deploying ' . $application->name . ' with branch ' . $branch);
- $deployment_uuid = new Cuid2(7);
- queue_application_deployment(
- application: $application,
- deployment_uuid: $deployment_uuid,
- force_rebuild: false,
- is_webhook: true
- );
+ $is_watch_path_triggered = $application->isWatchPathsTriggered($changed_files);
+ if ($is_watch_path_triggered || is_null($application->watch_paths)) {
+ ray('Deploying ' . $application->name . ' with branch ' . $branch);
+ $deployment_uuid = new Cuid2(7);
+ queue_application_deployment(
+ application: $application,
+ deployment_uuid: $deployment_uuid,
+ force_rebuild: false,
+ is_webhook: true,
+ );
+ $return_payloads->push([
+ 'status' => 'success',
+ 'message' => 'Deployment queued.',
+ 'application_uuid' => $application->uuid,
+ 'application_name' => $application->name,
+ ]);
+ } else {
+ $paths = str($application->watch_paths)->explode("\n");
+ $return_payloads->push([
+ 'status' => 'failed',
+ 'message' => 'Changed files do not match watch paths. Ignoring deployment.',
+ 'application_uuid' => $application->uuid,
+ 'application_name' => $application->name,
+ 'details' => [
+ 'changed_files' => $changed_files,
+ 'watch_paths' => $paths,
+ ],
+ ]);
+ }
} else {
$return_payloads->push([
- 'application' => $application->name,
'status' => 'failed',
'message' => 'Deployments disabled',
+ 'application_uuid' => $application->uuid,
+ 'application_name' => $application->name,
]);
ray('Deployments disabled for ' . $application->name);
}
diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php
index 0c6b6eecb..51c024af6 100644
--- a/app/Jobs/ApplicationDeploymentJob.php
+++ b/app/Jobs/ApplicationDeploymentJob.php
@@ -234,7 +234,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->build_server = $this->server;
$this->original_server = $this->server;
}
- if ($this->restart_only && $this->application->build_pack !== 'dockerimage') {
+ if ($this->restart_only && $this->application->build_pack !== 'dockerimage' && $this->application->build_pack !== 'dockerfile') {
$this->just_restart();
if ($this->server->isProxyShouldRun()) {
dispatch(new ContainerStatusJob($this->server));
@@ -326,17 +326,19 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
],
);
$this->generate_image_names();
- if (!$this->force_rebuild) {
- $this->check_image_locally_or_remotely();
- if (str($this->saved_outputs->get('local_image_found'))->isNotEmpty() && !$this->application->isConfigurationChanged()) {
- $this->create_workdir();
- $this->application_deployment_queue->addLogEntry("No configuration changed & image found ({$this->production_image_name}) with the same Git Commit SHA. Build step skipped.");
- $this->generate_compose_file();
- $this->push_to_docker_registry();
- $this->rolling_update();
- return;
- }
- }
+
+ // Always rebuild dockerfile based container.
+ // if (!$this->force_rebuild) {
+ // $this->check_image_locally_or_remotely();
+ // if (str($this->saved_outputs->get('local_image_found'))->isNotEmpty() && !$this->application->isConfigurationChanged()) {
+ // $this->create_workdir();
+ // $this->application_deployment_queue->addLogEntry("No configuration changed & image found ({$this->production_image_name}) with the same Git Commit SHA. Build step skipped.");
+ // $this->generate_compose_file();
+ // $this->push_to_docker_registry();
+ // $this->rolling_update();
+ // return;
+ // }
+ // }
$this->generate_compose_file();
$this->generate_build_env_variables();
$this->add_build_env_variables_to_dockerfile();
diff --git a/app/Livewire/Admin/Index.php b/app/Livewire/Admin/Index.php
index 60bd6f5ea..b72bc8e35 100644
--- a/app/Livewire/Admin/Index.php
+++ b/app/Livewire/Admin/Index.php
@@ -8,7 +8,31 @@ use Livewire\Component;
class Index extends Component
{
- public $users = [];
+ public $active_subscribers = [];
+ public $inactive_subscribers = [];
+ public $search = '';
+ public function submitSearch() {
+ if ($this->search !== "") {
+ $this->inactive_subscribers = User::whereDoesntHave('teams', function ($query) {
+ $query->whereRelation('subscription', 'stripe_subscription_id', '!=', null);
+ })->where(function ($query) {
+ $query->where('name', 'like', "%{$this->search}%")
+ ->orWhere('email', 'like', "%{$this->search}%");
+ })->get()->filter(function ($user) {
+ return $user->id !== 0;
+ });
+ $this->active_subscribers = User::whereHas('teams', function ($query) {
+ $query->whereRelation('subscription', 'stripe_subscription_id', '!=', null);
+ })->where(function ($query) {
+ $query->where('name', 'like', "%{$this->search}%")
+ ->orWhere('email', 'like', "%{$this->search}%");
+ })->get()->filter(function ($user) {
+ return $user->id !== 0;
+ });
+ } else {
+ $this->getSubscribers();
+ }
+ }
public function mount()
{
if (!isCloud()) {
@@ -17,7 +41,15 @@ class Index extends Component
if (auth()->user()->id !== 0) {
return redirect()->route('dashboard');
}
- $this->users = User::whereHas('teams', function ($query) {
+ $this->getSubscribers();
+ }
+ public function getSubscribers() {
+ $this->inactive_subscribers = User::whereDoesntHave('teams', function ($query) {
+ $query->whereRelation('subscription', 'stripe_subscription_id', '!=', null);
+ })->get()->filter(function ($user) {
+ return $user->id !== 0;
+ });
+ $this->active_subscribers = User::whereHas('teams', function ($query) {
$query->whereRelation('subscription', 'stripe_subscription_id', '!=', null);
})->get()->filter(function ($user) {
return $user->id !== 0;
diff --git a/app/Livewire/Project/Application/General.php b/app/Livewire/Project/Application/General.php
index 9485cdd03..c6b8f2e51 100644
--- a/app/Livewire/Project/Application/General.php
+++ b/app/Livewire/Project/Application/General.php
@@ -73,6 +73,7 @@ class General extends Component
'application.settings.is_static' => 'boolean|required',
'application.settings.is_raw_compose_deployment_enabled' => 'boolean|required',
'application.settings.is_build_server_enabled' => 'boolean|required',
+ 'application.watch_paths' => 'nullable',
];
protected $validationAttributes = [
'application.name' => 'name',
@@ -108,6 +109,7 @@ class General extends Component
'application.settings.is_static' => 'Is static',
'application.settings.is_raw_compose_deployment_enabled' => 'Is raw compose deployment enabled',
'application.settings.is_build_server_enabled' => 'Is build server enabled',
+ 'application.watch_paths' => 'Watch paths',
];
public function mount()
{
diff --git a/app/Livewire/Project/New/GithubPrivateRepository.php b/app/Livewire/Project/New/GithubPrivateRepository.php
index 7d59cfb04..322fd4a4e 100644
--- a/app/Livewire/Project/New/GithubPrivateRepository.php
+++ b/app/Livewire/Project/New/GithubPrivateRepository.php
@@ -7,14 +7,12 @@ use App\Models\GithubApp;
use App\Models\Project;
use App\Models\StandaloneDocker;
use App\Models\SwarmDocker;
-use App\Traits\SaveFromRedirect;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Route;
use Livewire\Component;
class GithubPrivateRepository extends Component
{
- use SaveFromRedirect;
public $current_step = 'github_apps';
public $github_apps;
public GithubApp $github_app;
diff --git a/app/Livewire/Project/New/Select.php b/app/Livewire/Project/New/Select.php
index b1842547e..2662cff09 100644
--- a/app/Livewire/Project/New/Select.php
+++ b/app/Livewire/Project/New/Select.php
@@ -70,6 +70,10 @@ class Select extends Component
// }
// }
+ public function updatedSearch()
+ {
+ $this->loadServices();
+ }
public function loadServices(bool $force = false)
{
try {
diff --git a/app/Livewire/Project/Service/ServiceApplicationView.php b/app/Livewire/Project/Service/ServiceApplicationView.php
index 0b3a4cef6..0986987f9 100644
--- a/app/Livewire/Project/Service/ServiceApplicationView.php
+++ b/app/Livewire/Project/Service/ServiceApplicationView.php
@@ -24,6 +24,16 @@ class ServiceApplicationView extends Component
{
return view('livewire.project.service.service-application-view');
}
+ public function updatedApplicationFqdn()
+ {
+ $this->application->fqdn = str($this->application->fqdn)->replaceEnd(',', '')->trim();
+ $this->application->fqdn = str($this->application->fqdn)->replaceStart(',', '')->trim();
+ $this->application->fqdn = str($this->application->fqdn)->trim()->explode(',')->map(function ($domain) {
+ return str($domain)->trim()->lower();
+ });
+ $this->application->fqdn = $this->application->fqdn->unique()->implode(',');
+ $this->application->save();
+ }
public function instantSave()
{
$this->submit();
@@ -59,7 +69,11 @@ class ServiceApplicationView extends Component
$this->validate();
$this->application->save();
updateCompose($this->application);
- $this->dispatch('success', 'Service saved.');
+ if (str($this->application->fqdn)->contains(',')) {
+ $this->dispatch('warning', 'Some services do not support multiple domains, which can lead to problems and is NOT RECOMMENDED.');
+ } else {
+ $this->dispatch('success', 'Service saved.');
+ }
} catch (\Throwable $e) {
return handleError($e, $this);
} finally {
diff --git a/app/Livewire/Security/PrivateKey/Create.php b/app/Livewire/Security/PrivateKey/Create.php
index cd1c06568..30449b220 100644
--- a/app/Livewire/Security/PrivateKey/Create.php
+++ b/app/Livewire/Security/PrivateKey/Create.php
@@ -26,7 +26,7 @@ class Create extends Component
'value' => 'private Key',
];
- public function generateNewKey()
+ public function generateNewRSAKey()
{
try {
$this->rateLimit(10);
@@ -37,6 +37,17 @@ class Create extends Component
return handleError($e, $this);
}
}
+ public function generateNewEDKey()
+ {
+ try {
+ $this->rateLimit(10);
+ $this->name = generate_random_name();
+ $this->description = 'Created by Coolify';
+ ['private' => $this->value, 'public' => $this->publicKey] = generateSSHKey('ed25519');
+ } catch(\Throwable $e) {
+ return handleError($e, $this);
+ }
+ }
public function updated($updateProperty)
{
if ($updateProperty === 'value') {
diff --git a/app/Livewire/Security/PrivateKey/Show.php b/app/Livewire/Security/PrivateKey/Show.php
index 0540b2e29..0a292731b 100644
--- a/app/Livewire/Security/PrivateKey/Show.php
+++ b/app/Livewire/Security/PrivateKey/Show.php
@@ -8,7 +8,7 @@ use Livewire\Component;
class Show extends Component
{
public PrivateKey $private_key;
- public $public_key;
+ public $public_key = "Loading...";
protected $rules = [
'private_key.name' => 'required|string',
'private_key.description' => 'nullable|string',
@@ -25,11 +25,13 @@ class Show extends Component
{
try {
$this->private_key = PrivateKey::ownedByCurrentTeam(['name', 'description', 'private_key', 'is_git_related'])->whereUuid(request()->private_key_uuid)->firstOrFail();
- $this->public_key = $this->private_key->publicKey();
}catch(\Throwable $e) {
return handleError($e, $this);
}
}
+ public function loadPublicKey() {
+ $this->public_key = $this->private_key->publicKey();
+ }
public function delete()
{
try {
diff --git a/app/Livewire/Server/Resources.php b/app/Livewire/Server/Resources.php
index f781418d9..1c8a8267e 100644
--- a/app/Livewire/Server/Resources.php
+++ b/app/Livewire/Server/Resources.php
@@ -42,7 +42,11 @@ class Resources extends Component
$this->dispatch('success', 'Resource statuses refreshed.');
}
public function loadUnmanagedContainers() {
- $this->unmanagedContainers = $this->server->loadUnmanagedContainers();
+ try {
+ $this->unmanagedContainers = $this->server->loadUnmanagedContainers();
+ } catch (\Throwable $e) {
+ return handleError($e, $this);
+ }
}
public function mount() {
$this->unmanagedContainers = collect();
diff --git a/app/Livewire/Subscription/Index.php b/app/Livewire/Subscription/Index.php
index c87f7d0b6..b367e6dcc 100644
--- a/app/Livewire/Subscription/Index.php
+++ b/app/Livewire/Subscription/Index.php
@@ -15,7 +15,10 @@ class Index extends Component
if (!isCloud()) {
return redirect(RouteServiceProvider::HOME);
}
- if (data_get(currentTeam(), 'subscription')) {
+ if (auth()->user()?->isMember()) {
+ return redirect()->route('dashboard');
+ }
+ if (data_get(currentTeam(), 'subscription') && isSubscriptionActive()) {
return redirect()->route('subscription.show');
}
$this->settings = InstanceSettings::get();
diff --git a/app/Livewire/Subscription/Show.php b/app/Livewire/Subscription/Show.php
index ad677ce53..2ae89806d 100644
--- a/app/Livewire/Subscription/Show.php
+++ b/app/Livewire/Subscription/Show.php
@@ -11,6 +11,9 @@ class Show extends Component
if (!isCloud()) {
return redirect()->route('dashboard');
}
+ if (auth()->user()?->isMember()) {
+ return redirect()->route('dashboard');
+ }
if (!data_get(currentTeam(), 'subscription')) {
return redirect()->route('subscription.index');
}
diff --git a/app/Models/Application.php b/app/Models/Application.php
index 3263d8d72..6a57e76e0 100644
--- a/app/Models/Application.php
+++ b/app/Models/Application.php
@@ -6,6 +6,7 @@ use App\Enums\ApplicationDeploymentStatus;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
+use Illuminate\Support\Collection;
use Spatie\Activitylog\Models\Activity;
use Illuminate\Support\Str;
use RuntimeException;
@@ -501,9 +502,9 @@ class Application extends BaseModel
{
$newConfigHash = $this->fqdn . $this->git_repository . $this->git_branch . $this->git_commit_sha . $this->build_pack . $this->static_image . $this->install_command . $this->build_command . $this->start_command . $this->port_exposes . $this->port_mappings . $this->base_directory . $this->publish_directory . $this->dockerfile . $this->dockerfile_location . $this->custom_labels;
if ($this->pull_request_id === 0 || $this->pull_request_id === null) {
- $newConfigHash .= json_encode($this->environment_variables());
+ $newConfigHash .= json_encode($this->environment_variables()->get('updated_at'));
} else {
- $newConfigHash .= json_encode($this->environment_variables_preview->all());
+ $newConfigHash .= json_encode($this->environment_variables_preview->get('updated_at'));
}
$newConfigHash = md5($newConfigHash);
$oldConfigHash = data_get($this, 'config_hash');
@@ -905,7 +906,6 @@ class Application extends BaseModel
: explode(',', $this->fqdn),
);
}
-
protected function buildGitCheckoutCommand($target): string {
$command = "git checkout $target";
@@ -914,5 +914,28 @@ class Application extends BaseModel
}
return $command;
+
+ public function watchPaths(): Attribute
+ {
+ return Attribute::make(
+ set: function ($value) {
+ if ($value) {
+ return trim($value);
+ }
+ }
+ );
+ }
+ public function isWatchPathsTriggered(Collection $modified_files): bool
+ {
+ if (is_null($this->watch_paths)) {
+ return false;
+ }
+ $watch_paths = collect(explode("\n", $this->watch_paths));
+ $matches = $modified_files->filter(function ($file) use ($watch_paths) {
+ return $watch_paths->contains(function ($glob) use ($file) {
+ return fnmatch($glob, $file);
+ });
+ });
+ return $matches->count() > 0;
}
}
diff --git a/app/Models/EnvironmentVariable.php b/app/Models/EnvironmentVariable.php
index 611543b2d..32277769e 100644
--- a/app/Models/EnvironmentVariable.php
+++ b/app/Models/EnvironmentVariable.php
@@ -25,19 +25,18 @@ class EnvironmentVariable extends Model
static::created(function (EnvironmentVariable $environment_variable) {
if ($environment_variable->application_id && !$environment_variable->is_preview) {
$found = ModelsEnvironmentVariable::where('key', $environment_variable->key)->where('application_id', $environment_variable->application_id)->where('is_preview', true)->first();
- $application = Application::find($environment_variable->application_id);
- if ($application->build_pack === 'dockerfile') {
- return;
- }
if (!$found) {
- ModelsEnvironmentVariable::create([
- 'key' => $environment_variable->key,
- 'value' => $environment_variable->value,
- 'is_build_time' => $environment_variable->is_build_time,
- 'is_multiline' => $environment_variable->is_multiline,
- 'application_id' => $environment_variable->application_id,
- 'is_preview' => true
- ]);
+ $application = Application::find($environment_variable->application_id);
+ if ($application->build_pack !== 'dockerfile') {
+ ModelsEnvironmentVariable::create([
+ 'key' => $environment_variable->key,
+ 'value' => $environment_variable->value,
+ 'is_build_time' => $environment_variable->is_build_time,
+ 'is_multiline' => $environment_variable->is_multiline,
+ 'application_id' => $environment_variable->application_id,
+ 'is_preview' => true
+ ]);
+ }
}
}
$environment_variable->update([
diff --git a/app/Models/Server.php b/app/Models/Server.php
index 08235a26d..cbe895936 100644
--- a/app/Models/Server.php
+++ b/app/Models/Server.php
@@ -550,21 +550,21 @@ $schema://$host {
}
public function loadUnmanagedContainers()
{
- if ($this->isFunctional()) {
- $containers = instant_remote_process(["docker ps -a --format '{{json .}}' "], $this);
- $containers = format_docker_command_output_to_json($containers);
- $containers = $containers->map(function ($container) {
- $labels = data_get($container, 'Labels');
- if (!str($labels)->contains("coolify.managed")) {
- return $container;
- }
- return null;
- });
- $containers = $containers->filter();
- return collect($containers);
- } else {
- return collect([]);
- }
+ if ($this->isFunctional()) {
+ $containers = instant_remote_process(["docker ps -a --format '{{json .}}' "], $this);
+ $containers = format_docker_command_output_to_json($containers);
+ $containers = $containers->map(function ($container) {
+ $labels = data_get($container, 'Labels');
+ if (!str($labels)->contains("coolify.managed")) {
+ return $container;
+ }
+ return null;
+ });
+ $containers = $containers->filter();
+ return collect($containers);
+ } else {
+ return collect([]);
+ }
}
public function hasDefinedResources()
{
diff --git a/app/Models/Team.php b/app/Models/Team.php
index a3dd4e473..29e434a5d 100644
--- a/app/Models/Team.php
+++ b/app/Models/Team.php
@@ -21,9 +21,11 @@ class Team extends Model implements SendsDiscord, SendsEmail
protected static function booted()
{
- // static::saved(function () {
- // refreshSession();
- // });
+ static::saving(function ($team) {
+ if (auth()->user()?->isMember()) {
+ throw new \Exception('You are not allowed to update this team.');
+ }
+ });
}
public function routeNotificationForDiscord()
diff --git a/app/Models/User.php b/app/Models/User.php
index e2ecae56a..0fa8ead2f 100644
--- a/app/Models/User.php
+++ b/app/Models/User.php
@@ -127,6 +127,10 @@ class User extends Authenticatable implements SendsEmail
{
return $this->role() === 'owner';
}
+ public function isMember()
+ {
+ return $this->role() === 'member';
+ }
public function isAdminFromSession()
{
if (auth()->user()->id === 0) {
diff --git a/bootstrap/helpers/applications.php b/bootstrap/helpers/applications.php
index bf3f15514..8d810da0f 100644
--- a/bootstrap/helpers/applications.php
+++ b/bootstrap/helpers/applications.php
@@ -6,6 +6,7 @@ use App\Models\Application;
use App\Models\ApplicationDeploymentQueue;
use App\Models\Server;
use App\Models\StandaloneDocker;
+use Illuminate\Support\Collection;
use Spatie\Url\Url;
function queue_application_deployment(Application $application, string $deployment_uuid, int | null $pull_request_id = 0, string $commit = 'HEAD', bool $force_rebuild = false, bool $is_webhook = false, bool $restart_only = false, ?string $git_type = null, bool $no_questions_asked = false, Server $server = null, StandaloneDocker $destination = null, bool $only_this_server = false)
diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php
index 023a65107..458dbae85 100644
--- a/bootstrap/helpers/shared.php
+++ b/bootstrap/helpers/shared.php
@@ -39,6 +39,7 @@ use Lcobucci\JWT\Encoding\JoseEncoder;
use Lcobucci\JWT\Signer\Key\InMemory;
use Lcobucci\JWT\Signer\Hmac\Sha256;
use Lcobucci\JWT\Token\Builder;
+use phpseclib3\Crypt\EC;
use Poliander\Cron\CronExpression;
use Visus\Cuid2\Cuid2;
use phpseclib3\Crypt\RSA;
@@ -165,13 +166,22 @@ function generate_random_name(?string $cuid = null): string
}
return Str::kebab("{$generator->getName()}-$cuid");
}
-function generateSSHKey()
+function generateSSHKey(string $type = 'rsa')
{
- $key = RSA::createKey();
- return [
- 'private' => $key->toString('PKCS1'),
- 'public' => $key->getPublicKey()->toString('OpenSSH', ['comment' => 'coolify-generated-ssh-key'])
- ];
+ if ($type === 'rsa') {
+ $key = RSA::createKey();
+ return [
+ 'private' => $key->toString('PKCS1'),
+ 'public' => $key->getPublicKey()->toString('OpenSSH', ['comment' => 'coolify-generated-ssh-key'])
+ ];
+ } else if ($type === 'ed25519') {
+ $key = EC::createKey('Ed25519');
+ return [
+ 'private' => $key->toString('OpenSSH'),
+ 'public' => $key->getPublicKey()->toString('OpenSSH', ['comment' => 'coolify-generated-ssh-key'])
+ ];
+ }
+ throw new Exception('Invalid key type');
}
function formatPrivateKey(string $privateKey)
{
@@ -282,7 +292,7 @@ function base_url(bool $withPort = true): string
function isSubscribed()
{
- return auth()->user()->currentTeam()->subscription()->exists() || auth()->user()->isInstanceAdmin();
+ return isSubscriptionActive() || auth()->user()->isInstanceAdmin();
}
function isDev(): bool
{
diff --git a/bootstrap/helpers/subscriptions.php b/bootstrap/helpers/subscriptions.php
index 928bbbcaf..5158c4e7e 100644
--- a/bootstrap/helpers/subscriptions.php
+++ b/bootstrap/helpers/subscriptions.php
@@ -66,7 +66,7 @@ function isSubscriptionActive()
// return $subscription->paddle_status === 'active';
// }
if (isStripe()) {
- return $subscription->stripe_invoice_paid === true && $subscription->stripe_cancel_at_period_end === false;
+ return $subscription->stripe_invoice_paid === true;
}
return false;
}
diff --git a/config/sentry.php b/config/sentry.php
index 89345d082..a3fbc0a3a 100644
--- a/config/sentry.php
+++ b/config/sentry.php
@@ -7,7 +7,7 @@ return [
// The release version of your application
// Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD'))
- 'release' => '4.0.0-beta.248',
+ 'release' => '4.0.0-beta.251',
// When left empty or `null` the Laravel environment will be used
'environment' => config('app.env'),
diff --git a/config/version.php b/config/version.php
index 642ee812d..d88e65a1b 100644
--- a/config/version.php
+++ b/config/version.php
@@ -1,3 +1,3 @@
longText('watch_paths')->nullable();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::table('applications', function (Blueprint $table) {
+ $table->dropColumn('watch_paths');
+ });
+ }
+};
diff --git a/resources/css/app.css b/resources/css/app.css
index b7965ebbc..371efd8ed 100644
--- a/resources/css/app.css
+++ b/resources/css/app.css
@@ -13,7 +13,7 @@ body {
.input,
.select {
- @apply text-black dark:bg-coolgray-100 dark:text-white ring-neutral-300 dark:ring-coolgray-300;
+ @apply text-black dark:bg-coolgray-100 dark:text-white ring-neutral-200 dark:ring-coolgray-300;
}
/* Readonly */
@@ -41,7 +41,7 @@ option {
}
.button {
- @apply flex items-center justify-center gap-2 px-3 py-1 text-sm text-white normal-case rounded cursor-pointer hover:bg-black/80 bg-coolgray-200 hover:bg-coolgray-500 hover:text-white disabled:bg-coolgray-100/10 disabled:cursor-not-allowed min-w-fit focus:outline-1 dark:disabled:text-neutral-600;
+ @apply flex items-center justify-center gap-2 px-2 py-1 text-sm text-black normal-case border rounded cursor-pointer bg-neutral-200/50 border-neutral-300 hover:bg-neutral-300 dark:bg-coolgray-200 dark:text-white dark:hover:text-white dark:hover:bg-coolgray-500 dark:border-black hover:text-black disabled:bg-coolgray-100/10 disabled:cursor-not-allowed min-w-fit focus:outline-1 dark:disabled:text-neutral-600;
}
button[isError]:not(:disabled) {
@@ -78,7 +78,7 @@ label {
}
table {
- @apply min-w-full divide-y dark:divide-coolgray-200 divide-neutral-300;
+ @apply min-w-full divide-y dark:divide-coolgray-200 divide-neutral-300 ;
}
thead {
@@ -90,7 +90,7 @@ tbody {
}
tr {
- @apply text-neutral-400;
+ @apply text-black dark:text-neutral-400 dark:hover:bg-black hover:bg-neutral-200;
}
tr th {
@@ -203,6 +203,9 @@ tr td:first-child {
.box-without-bg {
@apply flex p-2 transition-colors dark:hover:text-white hover:no-underline min-h-[4rem] border border-neutral-200 dark:border-black;
}
+.box-without-bg-without-border {
+ @apply flex p-2 transition-colors dark:hover:text-white hover:no-underline min-h-[4rem] ;
+}
.on-box {
@apply rounded hover:bg-neutral-300 dark:hover:bg-coolgray-500/20;
diff --git a/resources/js/app.js b/resources/js/app.js
index 47313863b..befec919e 100644
--- a/resources/js/app.js
+++ b/resources/js/app.js
@@ -1,6 +1,6 @@
-import { createApp } from "vue";
-import MagicBar from "./components/MagicBar.vue";
+// import { createApp } from "vue";
+// import MagicBar from "./components/MagicBar.vue";
-const app = createApp({});
-app.component("magic-bar", MagicBar);
-app.mount("#vue");
+// const app = createApp({});
+// app.component("magic-bar", MagicBar);
+// app.mount("#vue");
diff --git a/resources/views/auth/login.blade.php b/resources/views/auth/login.blade.php
index 82ea5062b..2c61581c6 100644
--- a/resources/views/auth/login.blade.php
+++ b/resources/views/auth/login.blade.php
@@ -4,51 +4,31 @@
Coolify
-
{{ $error }}
- @endforeach -{{ $error }}
+ @endforeach +