Compare commits

...

32 Commits

Author SHA1 Message Date
Andras Bacsai
af5b9fced1 Merge pull request #1277 from coollabsio/next
v4.0.0-beta.57
2023-10-02 14:19:24 +02:00
Andras Bacsai
ab5202515e fix: service status 2023-10-02 14:12:19 +02:00
Andras Bacsai
f863db7ea5 only 100 2023-10-02 14:01:54 +02:00
Andras Bacsai
3adc0bdd6e fix: only show last 1000 lines 2023-10-02 14:01:16 +02:00
Andras Bacsai
97027875bf feat: container logs 2023-10-02 13:38:16 +02:00
Andras Bacsai
fd9c13009f fix: always pull helper image in dev 2023-10-02 09:37:55 +02:00
Andras Bacsai
46a72fac47 Merge pull request #1276 from alexjustesen/patch-1
wee little missing spacing
2023-10-02 09:32:33 +02:00
Andras Bacsai
5d6ee04991 fix: new volumes for services should have - instead of _ 2023-10-02 09:18:32 +02:00
Andras Bacsai
d523becb29 fix: add uuid to volume names 2023-10-02 09:08:41 +02:00
Andras Bacsai
41672f75d0 fix: only parse expose in dockerfiles if ports_exposes is empty 2023-10-02 09:08:33 +02:00
Alex Justesen
acd8541e68 wee little spacing
noticed some missing spacing during the onboarding process.
2023-10-01 19:16:37 -04:00
Andras Bacsai
ed6af777a4 fix: show real volume names 2023-10-01 20:46:49 +02:00
Andras Bacsai
05f162f4e8 version++ 2023-10-01 20:29:35 +02:00
Andras Bacsai
5e0adc3777 Merge pull request #1275 from coollabsio/next
v4.0.0-beta.56
2023-10-01 18:16:24 +02:00
Andras Bacsai
7d06fc4403 fix: do not show subscription cancelled noti 2023-10-01 18:14:24 +02:00
Andras Bacsai
0e1bcceb8e feat: able to disable container healthchecks
fix: dockerfile based deployments have hc off by default
2023-10-01 18:14:13 +02:00
Andras Bacsai
a922f2fedf version++ 2023-10-01 18:13:34 +02:00
Andras Bacsai
1e0226c8ed Merge pull request #1274 from coollabsio/next
v4.0.0-beta.55
2023-10-01 17:40:31 +02:00
Andras Bacsai
5c45908087 fix: if app settings is not saved to db 2023-10-01 17:39:57 +02:00
Andras Bacsai
aefdc76805 fix: dockerfile expose is not overwritten 2023-10-01 17:27:12 +02:00
Andras Bacsai
b3c8c881b7 Revert "fix: services should have destination as well"
This reverts commit 9ab5a1f7bd.
2023-10-01 15:31:25 +02:00
Andras Bacsai
9ab5a1f7bd fix: services should have destination as well 2023-10-01 13:59:22 +02:00
Andras Bacsai
23968e7886 version++ 2023-10-01 13:28:33 +02:00
Andras Bacsai
390d24b6d7 Merge pull request #1273 from coollabsio/next
v4.0.0-beta.54
2023-10-01 12:36:10 +02:00
Andras Bacsai
e4296345b3 fix: public repo branch selection
fix: commit sha selection in source tabs
2023-10-01 12:29:50 +02:00
Andras Bacsai
bcffbe418b fix: preview deployments name, status etc 2023-10-01 12:02:44 +02:00
Andras Bacsai
bfbee4e78f version+ 2023-10-01 11:33:15 +02:00
Andras Bacsai
2bdb44dac3 Merge pull request #1272 from coollabsio/next
v4.0.0-beta.52
2023-09-30 20:55:24 +02:00
Andras Bacsai
4daa1b8c16 fix: coolify db backup 2023-09-30 20:54:05 +02:00
Andras Bacsai
febc399568 fix: not found base_branch in git webhooks 2023-09-30 20:47:07 +02:00
Andras Bacsai
9b9d4f9941 Merge pull request #1271 from coollabsio/next
v4.0.0-beta.52
2023-09-30 20:27:12 +02:00
Andras Bacsai
f49b87870c fix: backups are now working again 2023-09-30 20:26:42 +02:00
35 changed files with 387 additions and 126 deletions

View File

@@ -104,13 +104,15 @@ class General extends Component
}
public function mount()
{
$this->is_static = $this->application->settings->is_static;
$this->is_git_submodules_enabled = $this->application->settings->is_git_submodules_enabled;
$this->is_git_lfs_enabled = $this->application->settings->is_git_lfs_enabled;
$this->is_debug_enabled = $this->application->settings->is_debug_enabled;
$this->is_preview_deployments_enabled = $this->application->settings->is_preview_deployments_enabled;
$this->is_auto_deploy_enabled = $this->application->settings->is_auto_deploy_enabled;
$this->is_force_https_enabled = $this->application->settings->is_force_https_enabled;
if (data_get($this->application,'settings')) {
$this->is_static = $this->application->settings->is_static;
$this->is_git_submodules_enabled = $this->application->settings->is_git_submodules_enabled;
$this->is_git_lfs_enabled = $this->application->settings->is_git_lfs_enabled;
$this->is_debug_enabled = $this->application->settings->is_debug_enabled;
$this->is_preview_deployments_enabled = $this->application->settings->is_preview_deployments_enabled;
$this->is_auto_deploy_enabled = $this->application->settings->is_auto_deploy_enabled;
$this->is_force_https_enabled = $this->application->settings->is_force_https_enabled;
}
}
public function submit()
@@ -125,7 +127,7 @@ class General extends Component
}
if (data_get($this->application, 'dockerfile')) {
$port = get_port_from_dockerfile($this->application->dockerfile);
if ($port) {
if ($port && !$this->application->ports_exposes) {
$this->application->ports_exposes = $port;
}
}

View File

@@ -72,8 +72,7 @@ class Previews extends Component
public function stop(int $pull_request_id)
{
try {
$container_name = generateApplicationContainerName($this->application);
ray('Stopping container: ' . $container_name);
$container_name = generateApplicationContainerName($this->application, $pull_request_id);
instant_remote_process(["docker rm -f $container_name"], $this->application->destination->server, throwError: false);
ApplicationPreview::where('application_id', $this->application->id)->where('pull_request_id', $pull_request_id)->delete();

View File

@@ -10,7 +10,7 @@ class CreateScheduledBackup extends Component
public $database;
public $frequency;
public bool $enabled = true;
public bool $save_s3 = true;
public bool $save_s3 = false;
public $s3_storage_id;
public $s3s;

View File

@@ -26,6 +26,10 @@ class PublicGitRepository extends Component
public string $git_branch = 'main';
public int $rate_limit_remaining = 0;
public $rate_limit_reset = 0;
private object $repository_url_parsed;
public GithubApp|GitlabApp|null $git_source = null;
public string $git_host;
public string $git_repository;
protected $rules = [
'repository_url' => 'required|url',
'port' => 'required|numeric',
@@ -38,10 +42,6 @@ class PublicGitRepository extends Component
'is_static' => 'static',
'publish_directory' => 'publish directory',
];
private object $repository_url_parsed;
private GithubApp|GitlabApp|null $git_source = null;
private string $git_host;
private string $git_repository;
public function mount()
{
@@ -76,6 +76,7 @@ class PublicGitRepository extends Component
$this->get_branch();
$this->selected_branch = $this->git_branch;
} catch (\Throwable $e) {
ray($e->getMessage());
if (!$this->branch_found && $this->git_branch == 'main') {
try {
$this->git_branch = 'master';
@@ -123,9 +124,6 @@ class PublicGitRepository extends Component
$project_uuid = $this->parameters['project_uuid'];
$environment_name = $this->parameters['environment_name'];
$this->get_git_source();
$this->git_branch = $this->selected_branch ?? $this->git_branch;
$destination = StandaloneDocker::where('uuid', $destination_uuid)->first();
if (!$destination) {
$destination = SwarmDocker::where('uuid', $destination_uuid)->first();

View File

@@ -45,6 +45,9 @@ CMD ["nginx", "-g", "daemon off;"]
$environment = $project->load(['environments'])->environments->where('name', $this->parameters['environment_name'])->first();
$port = get_port_from_dockerfile($this->dockerfile);
if (!$port) {
$port = 80;
}
$application = Application::create([
'name' => 'dockerfile-' . new Cuid2(7),
'repository_project_id' => 0,
@@ -56,6 +59,7 @@ CMD ["nginx", "-g", "daemon off;"]
'environment_id' => $environment->id,
'destination_id' => $destination->id,
'destination_type' => $destination_class,
'health_check_enabled' => false,
'source_id' => 0,
'source_type' => GithubApp::class
]);

View File

@@ -21,7 +21,6 @@ class Navbar extends Component
}
public function serviceStatusUpdated()
{
ray('serviceStatusUpdated');
$this->check_status();
}
public function check_status()

View File

@@ -0,0 +1,40 @@
<?php
namespace App\Http\Livewire\Project\Shared;
use App\Models\Server;
use Illuminate\Support\Facades\Process;
use Livewire\Component;
class GetLogs extends Component
{
public string $outputs = '';
public string $errors = '';
public Server $server;
public ?string $container = null;
public ?bool $streamLogs = false;
public int $numberOfLines = 100;
public function doSomethingWithThisChunkOfOutput($output)
{
$this->outputs .= $output;
}
public function instantSave()
{
}
public function getLogs($refresh = false)
{
if ($this->container) {
$sshCommand = generateSshCommand($this->server, "docker logs -n {$this->numberOfLines} -t {$this->container}");
if ($refresh) {
$this->outputs = '';
}
Process::run($sshCommand, function (string $type, string $output) {
$this->doSomethingWithThisChunkOfOutput($output);
});
}
}
public function render()
{
return view('livewire.project.shared.get-logs');
}
}

View File

@@ -9,6 +9,7 @@ class HealthChecks extends Component
public $resource;
protected $rules = [
'resource.health_check_enabled' => 'boolean',
'resource.health_check_path' => 'string',
'resource.health_check_port' => 'nullable|string',
'resource.health_check_host' => 'string',
@@ -22,12 +23,19 @@ class HealthChecks extends Component
'resource.health_check_start_period' => 'integer',
];
public function instantSave()
{
$this->resource->save();
$this->emit('success', 'Health check updated.');
}
public function submit()
{
try {
$this->validate();
$this->resource->save();
$this->emit('saved');
$this->emit('success', 'Health check updated.');
} catch (\Throwable $e) {
return handleError($e, $this);
}

View File

@@ -0,0 +1,53 @@
<?php
namespace App\Http\Livewire\Project\Shared;
use App\Models\Application;
use App\Models\Server;
use App\Models\Service;
use App\Models\StandalonePostgresql;
use Livewire\Component;
class Logs extends Component
{
public ?string $type = null;
public Application|StandalonePostgresql|Service $resource;
public Server $server;
public ?string $container = null;
public $parameters;
public $query;
public $status;
public function mount()
{
$this->parameters = get_route_parameters();
$this->query = request()->query();
if (data_get($this->parameters, 'application_uuid')) {
$this->type = 'application';
$this->resource = Application::where('uuid', $this->parameters['application_uuid'])->firstOrFail();
$this->status = $this->resource->status;
$this->server = $this->resource->destination->server;
$containers = getCurrentApplicationContainerStatus($this->server, $this->resource->id);
if ($containers->count() > 0) {
$this->container = data_get($containers[0], 'Names');
}
} else if (data_get($this->parameters, 'database_uuid')) {
$this->type = 'database';
$this->resource = StandalonePostgresql::where('uuid', $this->parameters['database_uuid'])->firstOrFail();
$this->status = $this->resource->status;
$this->server = $this->resource->destination->server;
$this->container = $this->resource->uuid;
} else if (data_get($this->parameters, 'service_uuid')) {
$this->type = 'service';
$this->resource = Service::where('uuid', $this->parameters['service_uuid'])->firstOrFail();
$this->status = $this->resource->status;
$this->server = $this->resource->server;
$this->container = data_get($this->parameters, 'service_name') . '-' . $this->resource->uuid;
}
}
public function render()
{
return view('livewire.project.shared.logs');
}
}

View File

@@ -6,6 +6,7 @@ use Livewire\Component;
class Add extends Component
{
public $uuid;
public $parameters;
public string $name;
public string $mount_path;
@@ -31,8 +32,9 @@ class Add extends Component
public function submit()
{
$this->validate();
$name = $this->uuid . '-' . $this->name;
$this->emitUp('submit', [
'name' => $this->name,
'name' => $name,
'mount_path' => $this->mount_path,
'host_path' => $this->host_path,
]);

View File

@@ -11,7 +11,6 @@ class Show extends Component
public LocalPersistentVolume $storage;
public bool $isReadOnly = false;
public ?string $modalId = null;
public ?string $realName = null;
protected $rules = [
'storage.name' => 'required|string',
@@ -26,11 +25,6 @@ class Show extends Component
public function mount()
{
if ($this->storage->resource_type === 'App\Models\ServiceApplication' || $this->storage->resource_type === 'App\Models\ServiceDatabase') {
$this->realName = "{$this->storage->service->service->uuid}_{$this->storage->name}";
} else {
$this->realName = $this->storage->name;
}
$this->modalId = new Cuid2(7);
}

View File

@@ -89,7 +89,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->build_workdir = "{$this->workdir}" . rtrim($this->application->base_directory, '/');
$this->is_debug_enabled = $this->application->settings->is_debug_enabled;
$this->container_name = generateApplicationContainerName($this->application);
$this->container_name = generateApplicationContainerName($this->application, $this->pull_request_id);
savePrivateKeyToFs($this->server);
$this->saved_outputs = collect();
@@ -97,7 +97,9 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
if ($this->pull_request_id !== 0) {
$this->preview = ApplicationPreview::findPreviewByApplicationAndPullId($this->application->id, $this->pull_request_id);
if ($this->application->fqdn) {
$preview_fqdn = getFqdnWithoutPort(data_get($this->preview, 'fqdn'));
if (data_get($this->preview, 'fqdn')) {
$preview_fqdn = getFqdnWithoutPort(data_get($this->preview, 'fqdn'));
}
$template = $this->application->preview_url_template;
$url = Url::fromString($this->application->fqdn);
$host = $url->getHost();
@@ -284,7 +286,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
private function rolling_update()
{
if (count($this->application->ports_mappings_array) > 0){
if (count($this->application->ports_mappings_array) > 0) {
$this->execute_remote_command(
["echo -n 'Application has ports mapped to the host system, rolling update is not supported. Stopping current container.'"],
);
@@ -301,6 +303,10 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
}
private function health_check()
{
if ($this->application->isHealthcheckDisabled()) {
$this->newVersionIsHealthy = true;
return;
}
ray('New container name: ', $this->container_name);
if ($this->container_name) {
$counter = 0;
@@ -373,9 +379,6 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
private function prepare_builder_image()
{
$pull = "--pull=always";
if (isDev()) {
$pull = "--pull=never";
}
$helperImage = config('coolify.helper_image');
$runCommand = "docker run {$pull} -d --network {$this->destination->network} -v /:/host --name {$this->deployment_uuid} --rm -v /var/run/docker.sock:/var/run/docker.sock {$helperImage}";
@@ -399,7 +402,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
{
$this->execute_remote_command(
[
"echo -n 'Importing {$this->application->git_repository}:{$this->application->git_branch} to {$this->workdir}. '"
"echo -n 'Importing {$this->application->git_repository}:{$this->application->git_branch} (commit sha {$this->application->git_commit_sha}) to {$this->workdir}. '"
],
[
$this->importing_git_repository()
@@ -571,6 +574,9 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
]
]
];
if ($this->application->isHealthcheckDisabled()) {
data_forget($docker_compose, 'services.' . $this->container_name . '.healthcheck');
}
if (count($this->application->ports_mappings_array) > 0 && $this->pull_request_id === 0) {
$docker_compose['services'][$this->container_name]['ports'] = $this->application->ports_mappings_array;
}

View File

@@ -108,9 +108,9 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
$labelId = data_get($labels, 'coolify.applicationId');
if ($labelId) {
if (str_contains($labelId, '-pr-')) {
$previewId = (int) Str::after($labelId, '-pr-');
$pullRequestId = data_get($labels, 'coolify.pullRequestId');
$applicationId = (int) Str::before($labelId, '-pr-');
$preview = ApplicationPreview::where('application_id', $applicationId)->where('pull_request_id', $previewId)->first();
$preview = ApplicationPreview::where('application_id', $applicationId)->where('pull_request_id', $pullRequestId)->first();
if ($preview) {
$foundApplicationPreviews[] = $preview->id;
$statusFromDb = $preview->status;

View File

@@ -61,7 +61,8 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
public function handle(): void
{
try {
if (data_get($this->database, 'status') !== 'running') {
$status = Str::of(data_get($this->database, 'status'));
if (!$status->startsWith('running') && $this->database->id !== 0) {
ray('database not running');
return;
}
@@ -89,7 +90,6 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
$this->upload_to_s3();
}
$this->save_backup_logs();
// TODO: Notify user
} catch (\Throwable $e) {
ray($e->getMessage());
send_internal_notification('DatabaseBackupJob failed with: ' . $e->getMessage());

View File

@@ -231,4 +231,12 @@ class Application extends BaseModel
}
return true;
}
public function isHealthcheckDisabled(): bool
{
if (data_get($this, 'dockerfile') || data_get($this, 'build_pack') === 'dockerfile' || data_get($this,'health_check_enabled') === false) {
ray('dockerfile');
return true;
}
return false;
}
}

View File

@@ -248,7 +248,7 @@ class Service extends BaseModel
// Collect/create/update volumes
if ($serviceVolumes->count() > 0) {
foreach ($serviceVolumes as $volume) {
$serviceVolumes = $serviceVolumes->map(function ($volume) use ($savedService, $topLevelVolumes, $isNew) {
$type = null;
$source = null;
$target = null;
@@ -276,10 +276,10 @@ class Service extends BaseModel
}
if ($type->value() === 'bind') {
if ($source->value() === "/var/run/docker.sock") {
continue;
return $volume;
}
if ($source->value() === '/tmp' || $source->value() === '/tmp/') {
continue;
return $volume;
}
LocalFileVolume::updateOrCreate(
[
@@ -297,7 +297,21 @@ class Service extends BaseModel
]
);
} else if ($type->value() === 'volume') {
$topLevelVolumes->put($source->value(), null);
$slug = Str::slug($source, '-');
if ($isNew) {
$name = "{$savedService->service->uuid}-{$slug}";
} else {
$name = "{$savedService->service->uuid}_{$slug}";
}
if (is_string($volume)) {
$source = Str::of($volume)->before(':');
$target = Str::of($volume)->after(':')->beforeLast(':');
$source = $name;
$volume = "$source:$target";
} else if(is_array($volume)) {
data_set($volume, 'source', $name);
}
$topLevelVolumes->put($name, null);
LocalPersistentVolume::updateOrCreate(
[
'mount_path' => $target,
@@ -305,7 +319,7 @@ class Service extends BaseModel
'resource_type' => get_class($savedService)
],
[
'name' => Str::slug($source, '-'),
'name' => $name,
'mount_path' => $target,
'resource_id' => $savedService->id,
'resource_type' => get_class($savedService)
@@ -313,7 +327,9 @@ class Service extends BaseModel
);
}
$savedService->getFilesFromServer();
}
return $volume;
});
data_set($service, 'volumes', $serviceVolumes->toArray());
}
// Add env_file with at least .env to the service

View File

@@ -104,16 +104,16 @@ function getContainerStatus(Server $server, string $container_id, bool $all_data
return data_get($container[0], 'State.Status', 'exited');
}
function generateApplicationContainerName(Application $application)
function generateApplicationContainerName(Application $application, $pull_request_id = 0)
{
$now = now()->format('Hisu');
if ($application->pull_request_id !== 0 && $application->pull_request_id !== null) {
return $application->uuid . '-pr-' . $application->pull_request_id;
if ($pull_request_id !== 0 && $pull_request_id !== null) {
return $application->uuid . '-pr-' . $pull_request_id;
} else {
return $application->uuid . '-' . $now;
}
}
function get_port_from_dockerfile($dockerfile): int
function get_port_from_dockerfile($dockerfile): int|null
{
$dockerfile_array = explode("\n", $dockerfile);
$found_exposed_port = null;
@@ -127,7 +127,7 @@ function get_port_from_dockerfile($dockerfile): int
if ($found_exposed_port) {
return (int)$found_exposed_port->value();
}
return 80;
return null;
}
function defaultLabels($id, $name, $pull_request_id = 0, string $type = 'application', $subType = null, $subId = null)
@@ -207,10 +207,10 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview
{
$pull_request_id = data_get($preview, 'pull_request_id', 0);
$container_name = generateApplicationContainerName($application);
$container_name = generateApplicationContainerName($application, $pull_request_id);
$appId = $application->id;
if ($pull_request_id !== 0) {
$appId = $appId . '-pr-' . $application->pull_request_id;
if ($pull_request_id !== 0 && $pull_request_id !== null) {
$appId = $appId . '-pr-' . $pull_request_id;
}
$labels = collect([]);
$labels = $labels->merge(defaultLabels($appId, $container_name, $pull_request_id));

View File

@@ -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.51',
'release' => '4.0.0-beta.57',
// When left empty or `null` the Laravel environment will be used
'environment' => config('app.env'),

View File

@@ -1,3 +1,3 @@
<?php
return '4.0.0-beta.51';
return '4.0.0-beta.57';

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('applications', function (Blueprint $table) {
$table->boolean('health_check_enabled')->default(true);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('applications', function (Blueprint $table) {
$table->dropColumn('health_check_enabled');
});
}
};

View File

@@ -7,6 +7,10 @@
href="{{ route('project.application.deployments', $parameters) }}">
<button>Deployments</button>
</a>
<a class="{{ request()->routeIs('project.application.logs') ? 'text-white' : '' }}"
href="{{ route('project.application.logs', $parameters) }}">
<button>Logs</button>
</a>
<x-applications.links :application="$application" />
<div class="flex-1"></div>
<x-applications.advanced :application="$application" />

View File

@@ -3,6 +3,10 @@
href="{{ route('project.database.configuration', $parameters) }}">
<button>Configuration</button>
</a>
<a class="{{ request()->routeIs('project.database.logs') ? 'text-white' : '' }}"
href="{{ route('project.database.logs', $parameters) }}">
<button>Logs</button>
</a>
<a class="{{ request()->routeIs('project.database.backups.all') ? 'text-white' : '' }}"
href="{{ route('project.database.backups.all', $parameters) }}">
<button>Backups</button>

View File

@@ -1,4 +1,8 @@
<div class="navbar-main">
<a class="{{ request()->routeIs('project.service') ? 'text-white' : '' }}"
href="{{ route('project.service', $parameters) }}">
<button>Configuration</button>
</a>
<x-services.links :service="$service" />
<div class="flex-1"></div>
@if (serviceStatus($service) === 'degraded')

View File

@@ -40,7 +40,7 @@
<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" />?
or on a <x-highlighted text="Remote Server" />?
</x-slot:question>
<x-slot:actions>
<x-forms.button class="justify-center box" wire:target="setServerType('localhost')"

View File

@@ -54,11 +54,11 @@
<div class="flex flex-col p-4 bg-coolgray-200">
<div class="flex gap-2">PR #{{ data_get($preview, 'pull_request_id') }} |
@if (Str::of(data_get($preview, 'status'))->startsWith('running'))
<x-status.running :status="$status" />
<x-status.running :status="data_get($preview, 'status')" />
@elseif(Str::of(data_get($preview, 'status'))->startsWith('restarting'))
<x-status.restarting :status="$status" />
<x-status.restarting :status="data_get($preview, 'status')" />
@else
<x-status.stopped :status="$status" />
<x-status.stopped :status="data_get($preview, 'status')" />
@endif
@if (data_get($preview, 'status') !== 'exited')
| <a target="_blank" href="{{ data_get($preview, 'fqdn') }}">Open Preview

View File

@@ -24,7 +24,8 @@
<div>Configuration</div>
</div>
<x-forms.button type="submit">Save</x-forms.button>
<x-forms.button class="w-64" onclick="composeModal.showModal()">Edit Compose File</x-forms.button>
<x-forms.button class="w-64" onclick="composeModal.showModal()">Edit Compose
File</x-forms.button>
</div>
<div class="flex gap-2">
<x-forms.input id="service.name" required label="Service Name"
@@ -34,57 +35,66 @@
</form>
<div class="grid grid-cols-1 gap-2 pt-4 xl:grid-cols-3">
@foreach ($service->applications as $application)
<a @class([
<div @class([
'border-l border-dashed border-red-500' => Str::of(
$application->status)->contains(['exited']),
'border-l border-dashed border-success' => Str::of(
$application->status)->contains(['running']),
'border-l border-dashed border-warning' => Str::of(
$application->status)->contains(['starting']),
'flex flex-col justify-center box',
])
href="{{ route('project.service.show', [...$parameters, 'service_name' => $application->name]) }}">
@if ($application->human_name)
{{ Str::headline($application->human_name) }}
@else
{{ Str::headline($application->name) }}
@endif
@if ($application->configuration_required)
<span class="text-xs text-error">(configuration required)</span>
@endif
@if ($application->description)
<span class="text-xs">{{ Str::limit($application->description, 60) }}</span>
@endif
@if ($application->fqdn)
<span class="text-xs">{{ Str::limit($application->fqdn, 60) }}</span>
@endif
<div class="text-xs">{{ $application->status }}</div>
</a>
'flex gap-2 box group',
])>
<a class="flex flex-col flex-1 group-hover:text-white hover:no-underline"
href="{{ route('project.service.show', [...$parameters, 'service_name' => $application->name]) }}">
@if ($application->human_name)
{{ Str::headline($application->human_name) }}
@else
{{ Str::headline($application->name) }}
@endif
@if ($application->configuration_required)
<span class="text-xs text-error">(configuration required)</span>
@endif
@if ($application->description)
<span class="text-xs">{{ Str::limit($application->description, 60) }}</span>
@endif
@if ($application->fqdn)
<span class="text-xs">{{ Str::limit($application->fqdn, 60) }}</span>
@endif
<div class="text-xs">{{ $application->status }}</div>
</a>
<a class="flex gap-2 p-1 mx-4 text-xs font-bold rounded hover:no-underline hover:text-warning"
href="{{ route('project.service.logs', [...$parameters, 'service_name' => $application->name]) }}">Logs</a>
</div>
@endforeach
@foreach ($databases as $database)
<a @class([
<div @class([
'border-l border-dashed border-red-500' => Str::of(
$database->status)->contains(['exited']),
'border-l border-dashed border-success' => Str::of(
$database->status)->contains(['running']),
'border-l border-dashed border-warning' => Str::of(
$database->status)->contains(['restarting']),
'flex flex-col justify-center box',
])
href="{{ route('project.service.show', [...$parameters, 'service_name' => $database->name]) }}">
@if ($database->human_name)
{{ Str::headline($database->human_name) }}
@else
{{ Str::headline($database->name) }}
@endif
@if ($database->configuration_required)
<span class="text-xs text-error">(configuration required)</span>
@endif
@if ($database->description)
<span class="text-xs">{{ Str::limit($database->description, 60) }}</span>
@endif
<div class="text-xs">{{ $database->status }}</div>
</a>
'flex gap-2 box group',
])>
<a class="flex flex-col flex-1 group-hover:text-white hover:no-underline"
href="{{ route('project.service.show', [...$parameters, 'service_name' => $database->name]) }}">
@if ($database->human_name)
{{ Str::headline($database->human_name) }}
@else
{{ Str::headline($database->name) }}
@endif
@if ($database->configuration_required)
<span class="text-xs text-error">(configuration required)</span>
@endif
@if ($database->description)
<span class="text-xs">{{ Str::limit($database->description, 60) }}</span>
@endif
<div class="text-xs">{{ $database->status }}</div>
</a>
<a class="flex gap-2 p-1 mx-4 text-xs font-bold rounded hover:no-underline hover:text-warning"
href="{{ route('project.service.logs', [...$parameters, 'service_name' => $database->name]) }}">Logs</a>
</div>
@endforeach
</div>

View File

@@ -11,6 +11,12 @@
<a :class="activeTab === 'storages' && 'text-white'"
@click.prevent="activeTab = 'storages'; window.location.hash = 'storages'" href="#">Storages
</a>
@if (data_get($parameters, 'service_name'))
<a class="{{ request()->routeIs('project.service.logs') ? 'text-white' : '' }}"
href="{{ route('project.service.logs', $parameters) }}">
<button>Logs</button>
</a>
@endif
</div>
<div class="w-full pl-8">
@isset($serviceApplication)
@@ -32,7 +38,6 @@
@isset($serviceDatabase)
<div x-cloak x-show="activeTab === 'general'" class="h-full">
<livewire:project.service.database :database="$serviceDatabase" />
</div>
<div x-cloak x-show="activeTab === 'storages'">
<livewire:project.shared.storages.all :resource="$serviceDatabase" />

View File

@@ -0,0 +1,22 @@
<div x-init="$wire.getLogs">
<div class="flex gap-2">
<h2>Logs</h2>
@if ($streamLogs)
<span wire:poll.2000ms='getLogs(true)' class="loading loading-xs text-warning loading-spinner"></span>
@endif
</div>
<form wire:submit.prevent='getLogs(true)' class="flex items-end gap-2">
<x-forms.input label="Only Show Number of Lines" placeholder="1000" required id="numberOfLines"></x-forms.input>
<x-forms.button type="submit">Refresh</x-forms.button>
</form>
<div class="w-32">
<x-forms.checkbox instantSave label="Stream Logs" id="streamLogs"></x-forms.checkbox>
</div>
<div class="container w-full pt-4 mx-auto">
<div
class="scrollbar flex flex-col-reverse w-full overflow-y-auto border border-solid rounded border-coolgray-300 max-h-[32rem] p-4 pt-6 text-xs text-white">
<pre class="font-mono whitespace-pre-wrap">{{ $outputs }}</pre>
</div>
</div>
</div>

View File

@@ -5,24 +5,27 @@
</div>
<div class="pb-4">Define how your resource's health should be checked.</div>
<div class="flex flex-col gap-4">
<div class="flex gap-2">
<x-forms.input id="resource.health_check_method" placeholder="GET" label="Method" required />
<div class="w-32">
<x-forms.checkbox instantSave id="resource.health_check_enabled" label="Enabled" />
</div>
<div class="flex gap-2">
<x-forms.input id="resource.health_check_method" placeholder="GET" label="Method" required />
<x-forms.input id="resource.health_check_scheme" placeholder="http" label="Scheme" required />
<x-forms.input id="resource.health_check_host" placeholder="localhost" label="Host" required />
<x-forms.input id="resource.health_check_port"
helper="If no port is defined, the first exposed port will be used." placeholder="80" label="Port" />
<x-forms.input id="resource.health_check_path" placeholder="/health" label="Path" required />
<x-forms.input id="resource.health_check_scheme" placeholder="http" label="Scheme" required />
<x-forms.input id="resource.health_check_host" placeholder="localhost" label="Host" required />
<x-forms.input id="resource.health_check_port"
helper="If no port is defined, the first exposed port will be used." placeholder="80" label="Port" />
<x-forms.input id="resource.health_check_path" placeholder="/health" label="Path" required />
</div>
<div class="flex gap-2">
<x-forms.input id="resource.health_check_return_code" placeholder="200" label="Return Code" required />
<x-forms.input id="resource.health_check_response_text" placeholder="OK" label="Response Text" />
</div>
<div class="flex gap-2">
<x-forms.input id="resource.health_check_interval" placeholder="30" label="Interval" required />
<x-forms.input id="resource.health_check_timeout" placeholder="30" label="Timeout" required />
<x-forms.input id="resource.health_check_retries" placeholder="3" label="Retries" required />
<x-forms.input id="resource.health_check_start_period" placeholder="30" label="Start Period" required />
</div>
</div>
<div class="flex gap-2">
<x-forms.input id="resource.health_check_return_code" placeholder="200" label="Return Code" required />
<x-forms.input id="resource.health_check_response_text" placeholder="OK" label="Response Text" />
</div>
<div class="flex gap-2">
<x-forms.input id="resource.health_check_interval" placeholder="30" label="Interval" required />
<x-forms.input id="resource.health_check_timeout" placeholder="30" label="Timeout" required />
<x-forms.input id="resource.health_check_retries" placeholder="3" label="Retries" required />
<x-forms.input id="resource.health_check_start_period" placeholder="30" label="Start Period" required />
</div>
</div>
</form>

View File

@@ -0,0 +1,41 @@
<x-layout>
@if ($type === 'application')
<h1>Logs</h1>
<livewire:project.application.heading :application="$resource" />
<div class="pt-4">
@if (Str::of($status)->startsWith('running'))
<livewire:project.shared.get-logs :server="$server" :container="$container" />
@else
Application is not running.
@endif
</div>
@elseif ($type === 'database')
<h1>Logs</h1>
<livewire:project.database.heading :database="$resource" />
<div class="pt-4">
@if (Str::of($status)->startsWith('running'))
<livewire:project.shared.get-logs :server="$server" :container="$container" />
@else
Database is not running.
@endif
</div>
@elseif ($type === 'service')
<livewire:project.service.navbar :service="$resource" :parameters="$parameters" :query="$query" />
<div class="flex gap-4 pt-6">
<div>
<a class="{{ request()->routeIs('project.service.show') ? 'text-white' : '' }}"
href="{{ route('project.service.show', $parameters) }}">
<button><- Back</button>
</a>
</div>
<div class="flex-1 pl-8">
@if (serviceStatus($resource) === 'running')
<livewire:project.shared.get-logs :server="$server" :container="$container" />
@else
Service is not running.
@endif
</div>
</div>
@endif
</x-layout>

View File

@@ -8,7 +8,7 @@
volume
name, example: <span class='text-helper'>-pr-1</span>" />
<x-forms.button class="btn" onclick="newStorage.showModal()">+ Add</x-forms.button>
<livewire:project.shared.storages.add />
<livewire:project.shared.storages.add :uuid="$resource->uuid" />
@endif
</div>
<div>Persistent storage to preserve data between deployments.</div>

View File

@@ -12,7 +12,7 @@
@endonce
<form wire:submit.prevent='submit' class="flex flex-col gap-2 pt-4 xl:items-end xl:flex-row">
@if ($isReadOnly)
<x-forms.input id="realName" label="Volume Name" required readonly />
<x-forms.input id="storage.name" label="Volume Name" required readonly />
<x-forms.input id="storage.host_path" label="Source Path" readonly />
<x-forms.input id="storage.mount_path" label="Destination Path" required readonly />
@else

View File

@@ -10,6 +10,7 @@ use App\Http\Livewire\Boarding\Index as BoardingIndex;
use App\Http\Livewire\Project\Service\Index as ServiceIndex;
use App\Http\Livewire\Project\Service\Show as ServiceShow;
use App\Http\Livewire\Dashboard;
use App\Http\Livewire\Project\Shared\Logs;
use App\Http\Livewire\Server\All;
use App\Http\Livewire\Server\Show;
use App\Http\Livewire\Waitlist\Index as WaitlistIndex;
@@ -80,14 +81,20 @@ Route::middleware(['auth'])->group(function () {
[ApplicationController::class, 'deployment']
)->name('project.application.deployment');
Route::get('/project/{project_uuid}/{environment_name}/application/{application_uuid}/logs', Logs::class)->name('project.application.logs');
// Databases
Route::get('/project/{project_uuid}/{environment_name}/database/{database_uuid}', [DatabaseController::class, 'configuration'])->name('project.database.configuration');
Route::get('/project/{project_uuid}/{environment_name}/database/{database_uuid}/backups', [DatabaseController::class, 'backups'])->name('project.database.backups.all');
Route::get('/project/{project_uuid}/{environment_name}/database/{database_uuid}/backups/{backup_uuid}', [DatabaseController::class, 'executions'])->name('project.database.backups.executions');
Route::get('/project/{project_uuid}/{environment_name}/database/{database_uuid}/logs', Logs::class)->name('project.database.logs');
// Services
Route::get('/project/{project_uuid}/{environment_name}/service/{service_uuid}', ServiceIndex::class)->name('project.service');
Route::get('/project/{project_uuid}/{environment_name}/service/{service_uuid}/{service_name}', ServiceShow::class)->name('project.service.show');
Route::get('/project/{project_uuid}/{environment_name}/service/{service_uuid}/{service_name}/logs', Logs::class)->name('project.service.logs');
});
Route::middleware(['auth'])->group(function () {

View File

@@ -111,13 +111,17 @@ Route::post('/source/github/events', function () {
$applications = Application::where('repository_project_id', $id)->whereRelation('source', 'is_public', false);
if ($x_github_event === 'push') {
$applications = $applications->where('git_branch', $branch)->get();
if ($applications->isEmpty()) {
return response("Nothing to do. No applications found with branch '$branch'.");
}
}
if ($x_github_event === 'pull_request') {
$applications = $applications->where('git_branch', $base_branch)->get();
if ($applications->isEmpty()) {
return response("Nothing to do. No applications found with branch '$base_branch'.");
}
}
if ($applications->isEmpty()) {
return response("Nothing to do. No applications found with branch '$base_branch'.");
}
foreach ($applications as $application) {
$isFunctional = $application->destination->server->isFunctional();
if (!$isFunctional) {
@@ -168,9 +172,9 @@ Route::post('/source/github/events', function () {
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
if ($found) {
$found->delete();
$container_name = generateApplicationContainerName($application);
ray('Stopping container: ' . $container_name);
remote_process(["docker rm -f $container_name"], $application->destination->server);
$container_name = generateApplicationContainerName($application,$pull_request_id);
// ray('Stopping container: ' . $container_name);
instant_remote_process(["docker rm -f $container_name"], $application->destination->server);
return response('Preview Deployment closed.');
}
return response('Nothing to do. No Preview Deployment found');
@@ -323,7 +327,7 @@ Route::post('/payments/stripe/events', function () {
}
if ($alreadyCancelAtPeriodEnd !== $cancelAtPeriodEnd) {
if ($cancelAtPeriodEnd) {
send_internal_notification('Subscription cancelled at period end for team: ' . $subscription->team->id);
// send_internal_notification('Subscription cancelled at period end for team: ' . $subscription->team->id);
} else {
send_internal_notification('Subscription resumed for team: ' . $subscription->team->id);
}
@@ -342,7 +346,7 @@ Route::post('/payments/stripe/events', function () {
'stripe_invoice_paid' => false,
'stripe_trial_already_ended' => true,
]);
send_internal_notification('Subscription cancelled: ' . $subscription->team->id);
// send_internal_notification('Subscription cancelled: ' . $subscription->team->id);
break;
case 'customer.subscription.trial_will_end':
$customerId = data_get($data, 'customer');

View File

@@ -4,7 +4,7 @@
"version": "3.12.36"
},
"v4": {
"version": "4.0.0-beta.51"
"version": "4.0.0-beta.57"
}
}
}