Merge pull request #4926 from coollabsio/next

v4.0.0-beta.389
This commit is contained in:
Andras Bacsai
2025-01-23 15:20:19 +01:00
committed by GitHub
26 changed files with 248 additions and 74 deletions

View File

@@ -13,16 +13,16 @@ jobs:
id: stale id: stale
with: with:
stale-issue-message: 'This issue will be automatically closed in a few days if no response is received. Please provide an update with the requested information.' stale-issue-message: 'This issue will be automatically closed in a few days if no response is received. Please provide an update with the requested information.'
stale-pr-message: 'This pull request will be automatically closed in a few days if no response is received. Please update your PR or comment if you would like to continue working on it.' stale-pr-message: 'This pull request requires attention. If no changes or response is received within the next few days, it will be automatically closed. Please update your PR or leave a comment with the requested information.'
close-issue-message: 'This issue has been automatically closed due to inactivity.' close-issue-message: 'This issue has been automatically closed due to inactivity.'
close-pr-message: 'This pull request has been automatically closed due to inactivity.' close-pr-message: 'Thank you for your contribution. Due to inactivity, this PR was automatically closed. If you would like to continue working on this change in the future, feel free to reopen this PR or submit a new one.'
days-before-stale: 14 days-before-stale: 14
days-before-close: 7 days-before-close: 7
stale-issue-label: '⏱︎ Stale' stale-issue-label: '⏱︎ Stale'
stale-pr-label: '⏱︎ Stale' stale-pr-label: '⏱︎ Stale'
only-labels: '💤 Waiting for feedback' only-labels: '💤 Waiting for feedback, 💤 Waiting for changes'
remove-stale-when-updated: true remove-stale-when-updated: true
operations-per-run: 100 operations-per-run: 100
labels-to-remove-when-unstale: '⏱︎ Stale, 💤 Waiting for feedback' labels-to-remove-when-unstale: '⏱︎ Stale, 💤 Waiting for feedback, 💤 Waiting for changes'
close-issue-reason: 'not_planned' close-issue-reason: 'not_planned'
exempt-all-milestones: false exempt-all-milestones: false

View File

@@ -19,8 +19,12 @@ jobs:
script: | script: |
const { owner, repo } = context.repo; const { owner, repo } = context.repo;
async function processIssue(issueNumber) { async function processIssue(issueNumber, isFromPR = false, prBaseBranch = null) {
try { try {
if (isFromPR && prBaseBranch !== 'main') {
return;
}
const { data: currentLabels } = await github.rest.issues.listLabelsOnIssue({ const { data: currentLabels } = await github.rest.issues.listLabelsOnIssue({
owner, owner,
repo, repo,
@@ -59,19 +63,19 @@ jobs:
} }
} }
if (context.eventName === 'issues' || context.eventName === 'pull_request' || context.eventName === 'pull_request_target') { if (context.eventName === 'issues') {
const issue = context.payload.issue || context.payload.pull_request; await processIssue(context.payload.issue.number);
await processIssue(issue.number);
} }
if (context.eventName === 'pull_request' || context.eventName === 'pull_request_target') { if (context.eventName === 'pull_request' || context.eventName === 'pull_request_target') {
const pr = context.payload.pull_request; const pr = context.payload.pull_request;
if (pr.body) { await processIssue(pr.number);
if (pr.merged && pr.base.ref === 'main' && pr.body) {
const issueReferences = pr.body.match(/#(\d+)/g); const issueReferences = pr.body.match(/#(\d+)/g);
if (issueReferences) { if (issueReferences) {
for (const reference of issueReferences) { for (const reference of issueReferences) {
const issueNumber = parseInt(reference.substring(1)); const issueNumber = parseInt(reference.substring(1));
await processIssue(issueNumber); await processIssue(issueNumber, true, pr.base.ref);
} }
} }
} }

View File

@@ -4,6 +4,8 @@
You can ask for guidance anytime on our [Discord server](https://coollabs.io/discord) in the `#contribute` channel. You can ask for guidance anytime on our [Discord server](https://coollabs.io/discord) in the `#contribute` channel.
To understand the tech stack, please refer to the [Tech Stack](TECH_STACK.md) document.
## Table of Contents ## Table of Contents
1. [Setup Development Environment](#1-setup-development-environment) 1. [Setup Development Environment](#1-setup-development-environment)

29
TECH_STACK.md Normal file
View File

@@ -0,0 +1,29 @@
# Coolify Technology Stack
## Frontend
- Livewire and Alpine.js
- Blade (PHP templating engine)
- Tailwind CSS
- Monaco Editor (Code editor component)
- XTerm.js (Terminal component)
## Backend
- Laravel 11 (PHP Framework)
- PostgreSQL 15 (Database)
- Redis 7 (Caching & Real-time features)
- Soketi (WebSocket Server)
## DevOps & Infrastructure
- Docker & Docker Compose
- Nginx (Web Server)
- S6 Overlay (Process Supervisor)
- GitHub Actions (CI/CD)
## Languages
- PHP 8.4
- JavaScript
- Shell/Bash scripts

View File

@@ -294,7 +294,7 @@ class General extends Component
public function resetDefaultLabels($manualReset = false) public function resetDefaultLabels($manualReset = false)
{ {
try { try {
if ($this->application->settings->is_container_label_readonly_enabled && ! $manualReset) { if (! $this->application->settings->is_container_label_readonly_enabled && ! $manualReset) {
return; return;
} }
$this->customLabels = str(implode('|coolify|', generateLabelsApplication($this->application)))->replace('|coolify|', "\n"); $this->customLabels = str(implode('|coolify|', generateLabelsApplication($this->application)))->replace('|coolify|', "\n");
@@ -324,6 +324,7 @@ class General extends Component
} }
check_domain_usage(resource: $this->application); check_domain_usage(resource: $this->application);
$this->application->fqdn = $domains->implode(','); $this->application->fqdn = $domains->implode(',');
$this->resetDefaultLabels(false);
} }
} }

View File

@@ -146,11 +146,16 @@ class ExecuteContainerCommand extends Component
private function checkShellAvailability(Server $server, string $container): bool private function checkShellAvailability(Server $server, string $container): bool
{ {
$escapedContainer = escapeshellarg($container); $escapedContainer = escapeshellarg($container);
$result = instant_remote_process([ try {
"docker exec {$escapedContainer} which bash || docker exec {$escapedContainer} which sh", instant_remote_process([
], $server, false); "docker exec {$escapedContainer} bash -c 'exit 0' 2>/dev/null || ".
"docker exec {$escapedContainer} sh -c 'exit 0' 2>/dev/null",
], $server);
return ! empty($result); return true;
} catch (\Throwable) {
return false;
}
} }
#[On('connectToServer')] #[On('connectToServer')]

View File

@@ -9,6 +9,8 @@ use Livewire\Component;
class Terminal extends Component class Terminal extends Component
{ {
public bool $hasShell = true;
public function getListeners() public function getListeners()
{ {
$teamId = auth()->user()->currentTeam()->id; $teamId = auth()->user()->currentTeam()->id;
@@ -23,6 +25,21 @@ class Terminal extends Component
$this->dispatch('reloadWindow'); $this->dispatch('reloadWindow');
} }
private function checkShellAvailability(Server $server, string $container): bool
{
$escapedContainer = escapeshellarg($container);
try {
instant_remote_process([
"docker exec {$escapedContainer} bash -c 'exit 0' 2>/dev/null || ".
"docker exec {$escapedContainer} sh -c 'exit 0' 2>/dev/null",
], $server);
return true;
} catch (\Throwable) {
return false;
}
}
#[On('send-terminal-command')] #[On('send-terminal-command')]
public function sendTerminalCommand($isContainer, $identifier, $serverUuid) public function sendTerminalCommand($isContainer, $identifier, $serverUuid)
{ {
@@ -40,6 +57,12 @@ class Terminal extends Component
return; return;
} }
// Check shell availability
$this->hasShell = $this->checkShellAvailability($server, $identifier);
if (! $this->hasShell) {
return;
}
// Escape the identifier for shell usage // Escape the identifier for shell usage
$escapedIdentifier = escapeshellarg($identifier); $escapedIdentifier = escapeshellarg($identifier);
$command = SshMultiplexingHelper::generateSshCommand($server, "docker exec -it {$escapedIdentifier} sh -c 'PATH=\$PATH:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin && if [ -f ~/.profile ]; then . ~/.profile; fi && if [ -n \"\$SHELL\" ]; then exec \$SHELL; else sh; fi'"); $command = SshMultiplexingHelper::generateSshCommand($server, "docker exec -it {$escapedIdentifier} sh -c 'PATH=\$PATH:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin && if [ -f ~/.profile ]; then . ~/.profile; fi && if [ -n \"\$SHELL\" ]; then exec \$SHELL; else sh; fi'");

View File

@@ -4,9 +4,7 @@ namespace App\Models;
use App\Models\EnvironmentVariable as ModelsEnvironmentVariable; use App\Models\EnvironmentVariable as ModelsEnvironmentVariable;
use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;
use OpenApi\Attributes as OA; use OpenApi\Attributes as OA;
use Visus\Cuid2\Cuid2;
#[OA\Schema( #[OA\Schema(
description: 'Environment Variable model', description: 'Environment Variable model',
@@ -30,7 +28,7 @@ use Visus\Cuid2\Cuid2;
'updated_at' => ['type' => 'string'], 'updated_at' => ['type' => 'string'],
] ]
)] )]
class EnvironmentVariable extends Model class EnvironmentVariable extends BaseModel
{ {
protected $guarded = []; protected $guarded = [];
@@ -49,12 +47,6 @@ class EnvironmentVariable extends Model
protected static function booted() protected static function booted()
{ {
static::creating(function (Model $model) {
if (! $model->uuid) {
$model->uuid = (string) new Cuid2;
}
});
static::created(function (EnvironmentVariable $environment_variable) { static::created(function (EnvironmentVariable $environment_variable) {
if ($environment_variable->resourceable_type === Application::class && ! $environment_variable->is_preview) { if ($environment_variable->resourceable_type === Application::class && ! $environment_variable->is_preview) {
$found = ModelsEnvironmentVariable::where('key', $environment_variable->key) $found = ModelsEnvironmentVariable::where('key', $environment_variable->key)

View File

@@ -2,8 +2,8 @@
return [ return [
'coolify' => [ 'coolify' => [
'version' => '4.0.0-beta.388', 'version' => '4.0.0-beta.389',
'helper_version' => '1.0.5', 'helper_version' => '1.0.6',
'realtime_version' => '1.0.5', 'realtime_version' => '1.0.5',
'self_hosted' => env('SELF_HOSTED', true), 'self_hosted' => env('SELF_HOSTED', true),
'autoupdate' => env('AUTOUPDATE'), 'autoupdate' => env('AUTOUPDATE'),

View File

@@ -12,7 +12,7 @@ ARG DOCKER_BUILDX_VERSION=0.19.3
# https://github.com/buildpacks/pack/releases # https://github.com/buildpacks/pack/releases
ARG PACK_VERSION=0.36.2 ARG PACK_VERSION=0.36.2
# https://github.com/railwayapp/nixpacks/releases # https://github.com/railwayapp/nixpacks/releases
ARG NIXPACKS_VERSION=1.32.0 ARG NIXPACKS_VERSION=1.29.0
# https://github.com/minio/mc/releases # https://github.com/minio/mc/releases
ARG MINIO_VERSION=RELEASE.2024-11-21T17-21-54Z ARG MINIO_VERSION=RELEASE.2024-11-21T17-21-54Z

21
public/svgs/flipt.svg Normal file
View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="logosandtypes_com" data-name="logosandtypes com" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 150 150">
<defs>
<style>
.cls-1 {
fill: #7c3aed;
fill-rule: evenodd;
}
.cls-2 {
fill: none;
}
</style>
</defs>
<g id="Layer_3" data-name="Layer 3">
<g id="Layer_2" data-name="Layer 2">
<path id="Layer_3-2" data-name="Layer 3-2" class="cls-2" d="M0,0H150V150H0V0Z"/>
</g>
</g>
<path class="cls-1" d="M140.19,76.45c-3.24-14.68-11.77-43.35-15.1-56.52-6.68,.83-18.4,2.17-24.91,2.97,0,0-11.73,1.41-11.73,1.41-1.91,.27-3.79-.53-4.19-2.11-.33-1.03-1.18-5.12-1.95-5.88-.9-.94-2.35-.97-3.55-.69-15.06,1.87-49.7,6.21-49.7,6.21l-3.47-12.55-15.25,1.87L45.82,141.9l14.93-1.99L34.32,41.31s33.41-4.16,48.14-6.1c3.63-.62,5.03,.57,7.18,4.1,.84,1.41,1.68,1.76,3.47,1.52,.07,0,20.89-2.41,20.96-2.38,1.01,3.73,3.08,11.11,4.07,14.81l-18.09,2.35c-2.75,.35-4.55-.47-6.1-2.7-.47-.68-1.17-2.1-1.89-2.5-.7-.52-1.48-.57-2.66-.43-9.03,1.12-35.96,4.46-35.96,4.46l3.23,11.84s24.42-2.93,33.09-4.1c3.05-.61,5.65,.3,6.98,4.42,.31,.96,1.37,1.47,2.38,1.54l22.33-2.92,4.14,14.7s-18.11,2.66-18.11,2.66c-2.87,.35-4.09,.2-5.64-2.03-.17-.28-1.1-1.45-1.63-1.98-1.28-1.28-3.09-1.9-4.88-1.66l-34.58,4.5,4.55,16.65s25.11-3.45,35.45-4.45c3.31-.44,2.75,2.76,3.9,4.64,.61,.94,1.73,1.56,4.12,1.22l14.75-1.85c5.81-.71,11.8-1.61,15.16-6.4,1.51-2.16,2.52-6.64,2.34-9.02-.09-1.22-.56-4.54-.82-5.74Z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -22,7 +22,7 @@
<a wire:navigate class="menu-item {{ $activeMenu === 'log-drains' ? 'menu-item-active' : '' }}" <a wire:navigate class="menu-item {{ $activeMenu === 'log-drains' ? 'menu-item-active' : '' }}"
href="{{ route('server.log-drains', ['server_uuid' => $server->uuid]) }}">Log href="{{ route('server.log-drains', ['server_uuid' => $server->uuid]) }}">Log
Drains</a> Drains</a>
<a wire:navigate class="menu-item {{ $activeMenu === 'metrics' ? 'menu-item-active' : '' }}" <a class="menu-item {{ $activeMenu === 'metrics' ? 'menu-item-active' : '' }}"
href="{{ route('server.charts', ['server_uuid' => $server->uuid]) }}">Metrics</a> href="{{ route('server.charts', ['server_uuid' => $server->uuid]) }}">Metrics</a>
@endif @endif
@if (!$server->isLocalhost()) @if (!$server->isLocalhost())

View File

@@ -74,8 +74,7 @@
href="{{ route('project.application.resource-operations', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}" href="{{ route('project.application.resource-operations', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}"
wire:navigate>Resource Operations</a> wire:navigate>Resource Operations</a>
<a class="menu-item" wire:current.exact="menu-item-active" <a class="menu-item" wire:current.exact="menu-item-active"
href="{{ route('project.application.metrics', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}" href="{{ route('project.application.metrics', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}" >Metrics</a>
wire:navigate>Metrics</a>
<a class="menu-item" wire:current.exact="menu-item-active" <a class="menu-item" wire:current.exact="menu-item-active"
href="{{ route('project.application.tags', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}" href="{{ route('project.application.tags', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}"
wire:navigate>Tags</a> wire:navigate>Tags</a>

View File

@@ -60,9 +60,10 @@
@if(data_get($deployment, 'status') !== 'queued') @if(data_get($deployment, 'status') !== 'queued')
<div class="text-gray-600 dark:text-gray-400 text-sm"> <div class="text-gray-600 dark:text-gray-400 text-sm">
Started: {{ formatDateInServerTimezone(data_get($deployment, 'created_at'), data_get($application, 'destination.server')) }} Started: {{ formatDateInServerTimezone(data_get($deployment, 'created_at'), data_get($application, 'destination.server')) }}
@if($deployment->status !== 'in_progress' && $deployment->status !== 'cancelled-by-user' && $deployment->status !== 'failed') @if($deployment->status !== 'in_progress' && $deployment->status !== 'cancelled-by-user')
<br>Ended: {{ formatDateInServerTimezone(data_get($deployment, 'finished_at'), data_get($application, 'destination.server')) }} <br>Ended: {{ formatDateInServerTimezone(data_get($deployment, 'finished_at'), data_get($application, 'destination.server')) }}
<br>Duration: {{ calculateDuration(data_get($deployment, 'created_at'), data_get($deployment, 'finished_at')) }} <br>Duration: {{ calculateDuration(data_get($deployment, 'created_at'), data_get($deployment, 'finished_at')) }}
<br>Finished {{ \Carbon\Carbon::parse(data_get($deployment, 'finished_at'))->diffForHumans() }}
@elseif($deployment->status === 'in_progress') @elseif($deployment->status === 'in_progress')
<br>Running for: {{ calculateDuration(data_get($deployment, 'created_at'), now()) }} <br>Running for: {{ calculateDuration(data_get($deployment, 'created_at'), now()) }}
@endif @endif

View File

@@ -4,7 +4,7 @@
<h3 class="py-4">Executions</h3> <h3 class="py-4">Executions</h3>
<x-forms.button wire:click='cleanupFailed'>Cleanup Failed Backups</x-forms.button> <x-forms.button wire:click='cleanupFailed'>Cleanup Failed Backups</x-forms.button>
</div> </div>
<div class="flex flex-col gap-4"> <div wire:poll.5000ms="refreshBackupExecutions" class="flex flex-col gap-4">
@forelse($executions as $execution) @forelse($executions as $execution)
<div wire:key="{{ data_get($execution, 'id') }}" @class([ <div wire:key="{{ data_get($execution, 'id') }}" @class([
'flex flex-col border-l-2 transition-colors p-4 bg-white dark:bg-coolgray-100 text-black dark:text-white', 'flex flex-col border-l-2 transition-colors p-4 bg-white dark:bg-coolgray-100 text-black dark:text-white',
@@ -40,6 +40,7 @@
@if(data_get($execution, 'status') !== 'running') @if(data_get($execution, 'status') !== 'running')
<br>Ended: {{ formatDateInServerTimezone(data_get($execution, 'finished_at'), $this->server()) }} <br>Ended: {{ formatDateInServerTimezone(data_get($execution, 'finished_at'), $this->server()) }}
<br>Duration: {{ calculateDuration(data_get($execution, 'created_at'), data_get($execution, 'finished_at')) }} <br>Duration: {{ calculateDuration(data_get($execution, 'created_at'), data_get($execution, 'finished_at')) }}
<br>Finished {{ \Carbon\Carbon::parse(data_get($execution, 'finished_at'))->diffForHumans() }}
@endif @endif
</div> </div>
<div class="text-gray-600 dark:text-gray-400 text-sm"> <div class="text-gray-600 dark:text-gray-400 text-sm">

View File

@@ -32,8 +32,7 @@
href="{{ route('project.database.resource-operations', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'database_uuid' => $database->uuid]) }}" href="{{ route('project.database.resource-operations', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'database_uuid' => $database->uuid]) }}"
wire:navigate>Resource Operations</a> wire:navigate>Resource Operations</a>
<a class='menu-item' wire:current.exact="menu-item-active" <a class='menu-item' wire:current.exact="menu-item-active"
href="{{ route('project.database.metrics', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'database_uuid' => $database->uuid]) }}" href="{{ route('project.database.metrics', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'database_uuid' => $database->uuid]) }}">Metrics</a>
wire:navigate>Metrics</a>
<a class='menu-item' wire:current.exact="menu-item-active" <a class='menu-item' wire:current.exact="menu-item-active"
href="{{ route('project.database.tags', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'database_uuid' => $database->uuid]) }}" href="{{ route('project.database.tags', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'database_uuid' => $database->uuid]) }}"
wire:navigate>Tags</a> wire:navigate>Tags</a>

View File

@@ -8,7 +8,7 @@
@elseif(!$resource->destination->server->isMetricsEnabled()) @elseif(!$resource->destination->server->isMetricsEnabled())
<div class="alert alert-warning">Metrics are only available for servers with Sentinel & Metrics enabled!</div> <div class="alert alert-warning">Metrics are only available for servers with Sentinel & Metrics enabled!</div>
<div> Go to <a class="underline dark:text-white" <div> Go to <a class="underline dark:text-white"
href="{{ route('server.show', $resource->destination->server->uuid) }}">Server settings</a> to wire:navigate href="{{ route('server.show', $resource->destination->server->uuid) }}">Server settings</a> to
enable enable
it.</div> it.</div>
@else @else

View File

@@ -1,4 +1,4 @@
<div class="flex flex-col gap-2" x-data="{ <div class="flex flex-col gap-2" wire:poll.5000ms="refreshExecutions" x-data="{
init() { init() {
let interval; let interval;
$wire.$watch('isPollingActive', value => { $wire.$watch('isPollingActive', value => {
@@ -48,6 +48,7 @@
@if(data_get($execution, 'status') !== 'running') @if(data_get($execution, 'status') !== 'running')
<br>Ended: {{ formatDateInServerTimezone(data_get($execution, 'finished_at'), data_get($task, 'application.destination.server') ?? data_get($task, 'service.destination.server')) }} <br>Ended: {{ formatDateInServerTimezone(data_get($execution, 'finished_at'), data_get($task, 'application.destination.server') ?? data_get($task, 'service.destination.server')) }}
<br>Duration: {{ calculateDuration(data_get($execution, 'created_at'), data_get($execution, 'finished_at')) }} <br>Duration: {{ calculateDuration(data_get($execution, 'created_at'), data_get($execution, 'finished_at')) }}
<br>Finished {{ \Carbon\Carbon::parse(data_get($execution, 'finished_at'))->diffForHumans() }}
@endif @endif
</div> </div>
</a> </a>

View File

@@ -1,23 +1,39 @@
<div id="terminal-container" x-data="terminalData()"> <div id="terminal-container" x-data="terminalData()">
<div x-ref="terminalWrapper" @if(!$hasShell)
:class="fullscreen ? 'fullscreen' : 'relative w-full h-full py-4 mx-auto max-h-[510px]'"> <div class="flex pt-4 items-center justify-center w-full py-4 mx-auto">
<div id="terminal" wire:ignore></div> <div class="p-4 w-full rounded border dark:bg-coolgray-100 dark:border-coolgray-300">
<button title="Minimize" x-show="fullscreen" class="fixed top-4 right-6 text-white" x-on:click="makeFullscreen"><svg <div class="flex flex-col items-center justify-center space-y-4">
class="w-5 h-5 opacity-30 hover:opacity-100" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> <svg class="w-12 h-12 text-red-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/>
stroke-width="2" d="M6 14h4m0 0v4m0-4l-6 6m14-10h-4m0 0V6m0 4l6-6" /> </svg>
</svg></button> <div class="text-center">
<button title="Fullscreen" x-show="!fullscreen && terminalActive" class="absolute right-5 top-6 text-white" <h3 class="text-lg font-medium">Terminal Not Available</h3>
x-on:click="makeFullscreen"> <svg class="w-5 h-5 opacity-30 hover:opacity-100" viewBox="0 0 24 24" <p class="mt-2 text-sm text-gray-500">No shell (bash/sh) is available in this container. Please ensure either bash or sh is installed to use the terminal.</p>
xmlns="http://www.w3.org/2000/svg"> </div>
<g fill="none"> </div>
<path </div>
d="M24 0v24H0V0h24ZM12.593 23.258l-.011.002l-.071.035l-.02.004l-.014-.004l-.071-.035c-.01-.004-.019-.001-.024.005l-.004.01l-.017.428l.005.02l.01.013l.104.074l.015.004l.012-.004l.104-.074l.012-.016l.004-.017l-.017-.427c-.002-.01-.009-.017-.017-.018Zm.265-.113l-.013.002l-.185.093l-.01.01l-.003.011l.018.43l.005.012l.008.007l.201.093c.012.004.023 0 .029-.008l.004-.014l-.034-.614c-.003-.012-.01-.02-.02-.022Zm-.715.002a.023.023 0 0 0-.027.006l-.006.014l-.034.614c0 .012.007.02.017.024l.015-.002l.201-.093l.01-.008l.004-.011l.017-.43l-.003-.012l-.01-.01l-.184-.092Z" /> </div>
<path fill="currentColor" @else
d="M9.793 12.793a1 1 0 0 1 1.497 1.32l-.083.094L6.414 19H9a1 1 0 0 1 .117 1.993L9 21H4a1 1 0 0 1-.993-.883L3 20v-5a1 1 0 0 1 1.993-.117L5 15v2.586l4.793-4.793ZM20 3a1 1 0 0 1 .993.883L21 4v5a1 1 0 0 1-1.993.117L19 9V6.414l-4.793 4.793a1 1 0 0 1-1.497-1.32l.083-.094L17.586 5H15a1 1 0 0 1-.117-1.993L15 3h5Z" /> <div x-ref="terminalWrapper"
</g> :class="fullscreen ? 'fullscreen' : 'relative w-full h-full py-4 mx-auto max-h-[510px]'">
</svg></button> <div id="terminal" wire:ignore></div>
</div> <button title="Minimize" x-show="fullscreen" class="fixed top-4 right-6 text-white" x-on:click="makeFullscreen"><svg
class="w-5 h-5 opacity-30 hover:opacity-100" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
stroke-width="2" d="M6 14h4m0 0v4m0-4l-6 6m14-10h-4m0 0V6m0 4l6-6" />
</svg></button>
<button title="Fullscreen" x-show="!fullscreen && terminalActive" class="absolute right-5 top-6 text-white"
x-on:click="makeFullscreen"> <svg class="w-5 h-5 opacity-30 hover:opacity-100" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg">
<g fill="none">
<path
d="M24 0v24H0V0h24ZM12.593 23.258l-.011.002l-.071.035l-.02.004l-.014-.004l-.071-.035c-.01-.004-.019-.001-.024.005l-.004.01l-.017.428l.005.02l.01.013l.104.074l.015.004l.012-.004l.104-.074l.012-.016l.004-.017l-.017-.427c-.002-.01-.009-.017-.017-.018Zm.265-.113l-.013.002l-.185.093l-.01.01l-.003.011l.018.43l.005.012l.008.007l.201.093c.012.004.023 0 .029-.008l.004-.014l-.034-.614c-.003-.012-.01-.02-.02-.022Zm-.715.002a.023.023 0 0 0-.027.006l-.006.014l-.034.614c0 .012.007.02.017.024l.015-.002l.201-.093l.01-.008l.004-.011l.017-.43l-.003-.012l-.01-.01l-.184-.092Z" />
<path fill="currentColor"
d="M9.793 12.793a1 1 0 0 1 1.497 1.32l-.083.094L6.414 19H9a1 1 0 0 1 .117 1.993L9 21H4a1 1 0 0 1-.993-.883L3 20v-5a1 1 0 0 1 1.993-.117L5 15v2.586l4.793-4.793ZM20 3a1 1 0 0 1 .993.883L21 4v5a1 1 0 0 1-1.993.117L19 9V6.414l-4.793 4.793a1 1 0 0 1-1.497-1.32l.083-.094L17.586 5H15a1 1 0 0 1-.117-1.993L15 3h5Z" />
</g>
</svg></button>
</div>
@endif
@script @script
<script> <script>
// expose terminal config to the terminal.js file // expose terminal config to the terminal.js file

View File

@@ -1,4 +1,4 @@
<div class="flex flex-col gap-2" x-data="{ <div class="flex flex-col gap-2" wire:poll.5000ms="refreshExecutions" x-data="{
init() { init() {
let interval; let interval;
$wire.$watch('isPollingActive', value => { $wire.$watch('isPollingActive', value => {
@@ -48,6 +48,7 @@
@if(data_get($execution, 'status') !== 'running') @if(data_get($execution, 'status') !== 'running')
<br>Ended: {{ formatDateInServerTimezone(data_get($execution, 'finished_at'), $server) }} <br>Ended: {{ formatDateInServerTimezone(data_get($execution, 'finished_at'), $server) }}
<br>Duration: {{ calculateDuration(data_get($execution, 'created_at'), data_get($execution, 'finished_at')) }} <br>Duration: {{ calculateDuration(data_get($execution, 'created_at'), data_get($execution, 'finished_at')) }}
<br>Finished {{ \Carbon\Carbon::parse(data_get($execution, 'finished_at'))->diffForHumans() }}
@endif @endif
</div> </div>
</a> </a>

View File

@@ -37,6 +37,9 @@ services:
- MAILER_USER=${MAILER_USER} - MAILER_USER=${MAILER_USER}
- MAILER_PASSWORD=${MAILER_PASSWORD} - MAILER_PASSWORD=${MAILER_PASSWORD}
- MAILER_SENDER=${MAILER_SENDER} - MAILER_SENDER=${MAILER_SENDER}
- COPILOT_FAL_API_KEY=${COPILOT_FAL_API_KEY}
- COPILOT_PERPLEXITY_API_KEY=${COPILOT_PERPLEXITY_API_KEY}
- COPILOT_OPENAI_API_KEY=${COPILOT_OPENAI_API_KEY}
healthcheck: healthcheck:
test: ["CMD-SHELL", "bash -c ':> /dev/tcp/127.0.0.1/3010' || exit 1"] test: ["CMD-SHELL", "bash -c ':> /dev/tcp/127.0.0.1/3010' || exit 1"]
interval: 5s interval: 5s

View File

@@ -0,0 +1,23 @@
# documentation: https://docs.flipt.io/cloud/overview
# slogan: Flipt is a fully managed feature flag solution that enables you to keep your feature flags and remote config next to your code in Git.
# tags: feature flags,devops, CI, CD
# logo: svgs/flipt.svg
# port: 8080
services:
flipt:
image: 'docker.flipt.io/flipt/flipt:latest'
volumes:
- 'flipt-data:/var/opt/flipt'
environment:
- SERVICE_FQDN_FLIPT_8080
healthcheck:
test:
- CMD
- wget
- '--spider'
- '--quiet'
- 'http://127.0.0.1:8080'
interval: 2s
timeout: 10s
retries: 15

View File

@@ -9,24 +9,49 @@ services:
image: invoiceninja/invoiceninja:5 image: invoiceninja/invoiceninja:5
environment: environment:
- SERVICE_FQDN_INVOICENINJA - SERVICE_FQDN_INVOICENINJA
- APP_NAME=${APP_NAME:-"Invoice Ninja"}
- APP_ENV=${APP_ENV:-production} - APP_ENV=${APP_ENV:-production}
- APP_URL=${SERVICE_FQDN_INVOICENINJA} - APP_URL=${SERVICE_FQDN_INVOICENINJA}
- APP_KEY=base64:${SERVICE_REALBASE64_INVOICENINJA} - APP_KEY=base64:${SERVICE_REALBASE64_INVOICENINJA}
- APP_DEBUG=${APP_DEBUG:-false} - APP_DEBUG=${APP_DEBUG:-false}
- REQUIRE_HTTPS=${REQUIRE_HTTPS:-false} - REQUIRE_HTTPS=${REQUIRE_HTTPS:-false}
- PHANTOMJS_PDF_GENERATION=${PHANTOMJS_PDF_GENERATION:-false} - PHANTOMJS_PDF_GENERATION=${PHANTOMJS_PDF_GENERATION:-false}
- PDF_GENERATOR=${PDF_GENERATOR:-snappdf} - PDF_GENERATOR=${PDF_GENERATOR:-hosted_ninja}
- TRUSTED_PROXIES=${TRUSTED_PROXIES:-*} - TRUSTED_PROXIES=${TRUSTED_PROXIES:-*}
- QUEUE_CONNECTION=${QUEUE_CONNECTION:-database} - CACHE_DRIVER=redis
- IN_USER_EMAIL=${IN_USER_EMAIL:-admin@example.com} - QUEUE_CONNECTION=${QUEUE_CONNECTION:-redis}
- IN_PASSWORD=${SERVICE_PASSWORD_INVOICENINJAUSER} - SESSION_DRIVER=redis
- REDIS_HOST=${REDIS_HOST:-redis}
- REDIS_PASSWORD=${SERVICE_PASSWORD_REDIS}
- REDIS_PORT=${REDIS_PORT:-6379}
- DB_HOST=${DB_HOST:-mariadb} - DB_HOST=${DB_HOST:-mariadb}
- DB_PORT=${DB_PORT:-3306} - DB_PORT=${DB_PORT:-3306}
- DB_DATABASE=${DB_DATABASE:-invoiceninja} - DB_DATABASE=${DB_DATABASE:-invoiceninja}
- DB_USERNAME=$SERVICE_USER_MARIADB - DB_USERNAME=${SERVICE_USER_MARIADB}
- DB_PASSWORD=$SERVICE_PASSWORD_MARIADB - DB_PASSWORD=${SERVICE_PASSWORD_MARIADB}
- IN_USER_EMAIL=${IN_USER_EMAIL:-admin@example.com}
- IN_PASSWORD=${SERVICE_PASSWORD_INVOICENINJAUSER}
- MAIL_MAILER=${MAIL_MAILER:-log}
- MAIL_HOST=${MAIL_HOST}
- MAIL_PORT=${MAIL_PORT}
- MAIL_USERNAME=${MAIL_USERNAME}
- MAIL_PASSWORD=${MAIL_PASSWORD}
- MAIL_ENCRYPTION=${MAIL_ENCRYPTION}
- MAIL_FROM_ADDRESS=${MAIL_FROM_ADDRESS}
- MAIL_FROM_NAME=${MAIL_FROM_NAME}
- AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID}
- AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}
- AWS_DEFAULT_REGION=${AWS_DEFAULT_REGION}
- AWS_BUCKET=${AWS_BUCKET}
- AWS_URL=${AWS_URL}
- AWS_ENDPOINT=${AWS_ENDPOINT}
- NORDIGEN_SECRET_ID=${NORDIGEN_SECRET_ID}
- NORDIGEN_SECRET_KEY=${NORDIGEN_SECRET_KEY}
- IS_DOCKER=true
- SCOUT_DRIVER=${SCOUT_DRIVER}
- LICENSE_KEY=${LICENSE_KEY}
healthcheck: healthcheck:
test: ['CMD', 'echo', 'ok'] test: ["CMD", "echo", "ok"]
interval: 5s interval: 5s
timeout: 20s timeout: 20s
retries: 10 retries: 10
@@ -133,12 +158,25 @@ services:
volumes: volumes:
- mariadb-data:/var/lib/mysql - mariadb-data:/var/lib/mysql
environment: environment:
- MYSQL_ROOT_PASSWORD=$SERVICE_PASSWORD_MARIADBROOT - MYSQL_ROOT_PASSWORD=${SERVICE_PASSWORD_MARIADBROOT}
- MYSQL_DATABASE=${DB_DATABASE:-invoiceninja} - MYSQL_DATABASE=${DB_DATABASE:-invoiceninja}
- MYSQL_USER=$SERVICE_USER_MARIADB - MYSQL_USER=${SERVICE_USER_MARIADB}
- MYSQL_PASSWORD=$SERVICE_PASSWORD_MARIADB - MYSQL_PASSWORD=${SERVICE_PASSWORD_MARIADB}
healthcheck: healthcheck:
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"] test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
interval: 5s interval: 5s
timeout: 20s timeout: 20s
retries: 10 retries: 10
redis:
image: "redis:7.4-alpine"
command: redis-server --requirepass ${SERVICE_PASSWORD_REDIS}
environment:
- REDIS_PASSWORD=${SERVICE_PASSWORD_REDIS}
volumes:
- "invoice-ninja-redis-data:/data"
healthcheck:
test: ["CMD", "redis-cli", "-a", "${SERVICE_PASSWORD_REDIS}", "ping"]
interval: 10s
timeout: 5s
retries: 5

View File

@@ -25,9 +25,10 @@ services:
- APP_URI=${SERVICE_FQDN_PLUNK} - APP_URI=${SERVICE_FQDN_PLUNK}
- API_URI=${SERVICE_FQDN_PLUNK}/api - API_URI=${SERVICE_FQDN_PLUNK}/api
- DISABLE_SIGNUPS=${DISABLE_SIGNUPS:-False} - DISABLE_SIGNUPS=${DISABLE_SIGNUPS:-False}
- NODE_OPTIONS=--no-network-family-autoselection
entrypoint: [ "/app/entry.sh" ] entrypoint: [ "/app/entry.sh" ]
healthcheck: healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://127.0.0.1:3000"] test: ["CMD-SHELL", "(wget -S --spider http://127.0.0.1:3000/api/health 2>&1 | grep -q \"HTTP/1.1 [1-3]\")"]
interval: 2s interval: 2s
timeout: 10s timeout: 10s
retries: 15 retries: 15

File diff suppressed because one or more lines are too long

View File

@@ -1,13 +1,13 @@
{ {
"coolify": { "coolify": {
"v4": { "v4": {
"version": "4.0.0-beta.388"
},
"nightly": {
"version": "4.0.0-beta.389" "version": "4.0.0-beta.389"
}, },
"nightly": {
"version": "4.0.0-beta.390"
},
"helper": { "helper": {
"version": "1.0.5" "version": "1.0.6"
}, },
"realtime": { "realtime": {
"version": "1.0.5" "version": "1.0.5"