@@ -1,101 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Livewire;
|
|
||||||
|
|
||||||
use Livewire\Attributes\On;
|
|
||||||
use Livewire\Component;
|
|
||||||
|
|
||||||
class RunCommand extends Component
|
|
||||||
{
|
|
||||||
public $selected_uuid = 'default';
|
|
||||||
|
|
||||||
public $servers = [];
|
|
||||||
|
|
||||||
public $containers = [];
|
|
||||||
|
|
||||||
public function mount($servers)
|
|
||||||
{
|
|
||||||
$this->servers = $servers;
|
|
||||||
$this->containers = $this->getAllActiveContainers();
|
|
||||||
}
|
|
||||||
|
|
||||||
private function getAllActiveContainers()
|
|
||||||
{
|
|
||||||
return collect($this->servers)->flatMap(function ($server) {
|
|
||||||
if (! $server->isFunctional()) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return $server->definedResources()
|
|
||||||
->filter(function ($resource) {
|
|
||||||
$status = method_exists($resource, 'realStatus') ? $resource->realStatus() : (method_exists($resource, 'status') ? $resource->status() : 'exited');
|
|
||||||
|
|
||||||
return str_starts_with($status, 'running:');
|
|
||||||
})
|
|
||||||
->map(function ($resource) use ($server) {
|
|
||||||
if (isDev()) {
|
|
||||||
if (data_get($resource, 'name') === 'coolify-db') {
|
|
||||||
$container_name = 'coolify-db';
|
|
||||||
|
|
||||||
return [
|
|
||||||
'name' => $resource->name,
|
|
||||||
'connection_name' => $container_name,
|
|
||||||
'uuid' => $resource->uuid,
|
|
||||||
'status' => 'running',
|
|
||||||
'server' => $server,
|
|
||||||
'server_uuid' => $server->uuid,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (class_basename($resource) === 'Application') {
|
|
||||||
if (! $server->isSwarm()) {
|
|
||||||
$current_containers = getCurrentApplicationContainerStatus($server, $resource->id, includePullrequests: true);
|
|
||||||
}
|
|
||||||
$status = $resource->status;
|
|
||||||
} elseif (class_basename($resource) === 'Service') {
|
|
||||||
$current_containers = getCurrentServiceContainerStatus($server, $resource->id);
|
|
||||||
$status = $resource->status();
|
|
||||||
} else {
|
|
||||||
$status = getContainerStatus($server, $resource->uuid);
|
|
||||||
if ($status === 'running') {
|
|
||||||
$current_containers = collect([
|
|
||||||
'Names' => $resource->name,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ($server->isSwarm()) {
|
|
||||||
$container_name = $resource->uuid.'_'.$resource->uuid;
|
|
||||||
} else {
|
|
||||||
$container_name = data_get($current_containers->first(), 'Names');
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
|
||||||
'name' => $resource->name,
|
|
||||||
'connection_name' => $container_name,
|
|
||||||
'uuid' => $resource->uuid,
|
|
||||||
'status' => $status,
|
|
||||||
'server' => $server,
|
|
||||||
'server_uuid' => $server->uuid,
|
|
||||||
];
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public function updatedSelectedUuid($value)
|
|
||||||
{
|
|
||||||
$this->connectToContainer();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[On('connectToContainer')]
|
|
||||||
public function connectToContainer()
|
|
||||||
{
|
|
||||||
$container = collect($this->containers)->firstWhere('uuid', $this->selected_uuid);
|
|
||||||
|
|
||||||
$this->dispatch('send-terminal-command',
|
|
||||||
isset($container),
|
|
||||||
$container['connection_name'] ?? $this->selected_uuid,
|
|
||||||
$container['server_uuid'] ?? $this->selected_uuid
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,15 +3,70 @@
|
|||||||
namespace App\Livewire\Terminal;
|
namespace App\Livewire\Terminal;
|
||||||
|
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
|
use Livewire\Attributes\On;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
class Index extends Component
|
class Index extends Component
|
||||||
{
|
{
|
||||||
|
public $selected_uuid = 'default';
|
||||||
|
|
||||||
public $servers = [];
|
public $servers = [];
|
||||||
|
|
||||||
|
public $containers = [];
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
|
if (! auth()->user()->isAdmin()) {
|
||||||
|
abort(403);
|
||||||
|
}
|
||||||
$this->servers = Server::isReachable()->get();
|
$this->servers = Server::isReachable()->get();
|
||||||
|
$this->containers = $this->getAllActiveContainers();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getAllActiveContainers()
|
||||||
|
{
|
||||||
|
return collect($this->servers)->flatMap(function ($server) {
|
||||||
|
if (! $server->isFunctional()) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $server->loadAllContainers()->map(function ($container) use ($server) {
|
||||||
|
$state = data_get_str($container, 'State')->lower();
|
||||||
|
if ($state->contains('running')) {
|
||||||
|
return [
|
||||||
|
'name' => data_get($container, 'Names'),
|
||||||
|
'connection_name' => data_get($container, 'Names'),
|
||||||
|
'uuid' => data_get($container, 'Names'),
|
||||||
|
'status' => data_get_str($container, 'State')->lower(),
|
||||||
|
'server' => $server,
|
||||||
|
'server_uuid' => $server->uuid,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
})->filter();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updatedSelectedUuid()
|
||||||
|
{
|
||||||
|
$this->connectToContainer();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[On('connectToContainer')]
|
||||||
|
public function connectToContainer()
|
||||||
|
{
|
||||||
|
if ($this->selected_uuid === 'default') {
|
||||||
|
$this->dispatch('error', 'Please select a server or a container.');
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$container = collect($this->containers)->firstWhere('uuid', $this->selected_uuid);
|
||||||
|
$this->dispatch('send-terminal-command',
|
||||||
|
isset($container),
|
||||||
|
$container['connection_name'] ?? $this->selected_uuid,
|
||||||
|
$container['server_uuid'] ?? $this->selected_uuid
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function render()
|
public function render()
|
||||||
|
|||||||
@@ -775,6 +775,18 @@ $schema://$host {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function loadAllContainers(): Collection
|
||||||
|
{
|
||||||
|
if ($this->isFunctional()) {
|
||||||
|
$containers = instant_remote_process(["docker ps -a --format '{{json .}}'"], $this);
|
||||||
|
$containers = format_docker_command_output_to_json($containers);
|
||||||
|
|
||||||
|
return collect($containers);
|
||||||
|
}
|
||||||
|
|
||||||
|
return collect([]);
|
||||||
|
}
|
||||||
|
|
||||||
public function loadUnmanagedContainers(): Collection
|
public function loadUnmanagedContainers(): Collection
|
||||||
{
|
{
|
||||||
if ($this->isFunctional()) {
|
if ($this->isFunctional()) {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ return [
|
|||||||
|
|
||||||
// The release version of your application
|
// The release version of your application
|
||||||
// Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD'))
|
// Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD'))
|
||||||
'release' => '4.0.0-beta.337',
|
'release' => '4.0.0-beta.338',
|
||||||
// When left empty or `null` the Laravel environment will be used
|
// When left empty or `null` the Laravel environment will be used
|
||||||
'environment' => config('app.env'),
|
'environment' => config('app.env'),
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
return '4.0.0-beta.337';
|
return '4.0.0-beta.338';
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ services:
|
|||||||
retries: 10
|
retries: 10
|
||||||
timeout: 2s
|
timeout: 2s
|
||||||
soketi:
|
soketi:
|
||||||
image: 'ghcr.io/coollabsio/coolify-realtime:1.0.0'
|
image: 'ghcr.io/coollabsio/coolify-realtime:1.0.1'
|
||||||
ports:
|
ports:
|
||||||
- "${SOKETI_PORT:-6001}:6001"
|
- "${SOKETI_PORT:-6001}:6001"
|
||||||
- "6002:6002"
|
- "6002:6002"
|
||||||
@@ -123,7 +123,7 @@ services:
|
|||||||
SOKETI_DEFAULT_APP_KEY: "${PUSHER_APP_KEY}"
|
SOKETI_DEFAULT_APP_KEY: "${PUSHER_APP_KEY}"
|
||||||
SOKETI_DEFAULT_APP_SECRET: "${PUSHER_APP_SECRET}"
|
SOKETI_DEFAULT_APP_SECRET: "${PUSHER_APP_SECRET}"
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:6001/ready && wget -qO- http://127.0.0.1:6002/ready || exit 1"]
|
test: [ "CMD-SHELL", "wget -qO- http://127.0.0.1:6001/ready && wget -qO- http://127.0.0.1:6002/ready || exit 1" ]
|
||||||
interval: 5s
|
interval: 5s
|
||||||
retries: 10
|
retries: 10
|
||||||
timeout: 2s
|
timeout: 2s
|
||||||
|
|||||||
@@ -123,7 +123,6 @@ async function handleCommand(ws, command, userId) {
|
|||||||
cols: 80,
|
cols: 80,
|
||||||
rows: 30,
|
rows: 30,
|
||||||
cwd: process.env.HOME,
|
cwd: process.env.HOME,
|
||||||
env: process.env
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// NOTE: - Initiates a process within the Terminal container
|
// NOTE: - Initiates a process within the Terminal container
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ services:
|
|||||||
retries: 10
|
retries: 10
|
||||||
timeout: 2s
|
timeout: 2s
|
||||||
soketi:
|
soketi:
|
||||||
image: 'ghcr.io/coollabsio/coolify-realtime:1.0.0'
|
image: 'ghcr.io/coollabsio/coolify-realtime:1.0.1'
|
||||||
ports:
|
ports:
|
||||||
- "${SOKETI_PORT:-6001}:6001"
|
- "${SOKETI_PORT:-6001}:6001"
|
||||||
- "6002:6002"
|
- "6002:6002"
|
||||||
@@ -123,7 +123,7 @@ services:
|
|||||||
SOKETI_DEFAULT_APP_KEY: "${PUSHER_APP_KEY}"
|
SOKETI_DEFAULT_APP_KEY: "${PUSHER_APP_KEY}"
|
||||||
SOKETI_DEFAULT_APP_SECRET: "${PUSHER_APP_SECRET}"
|
SOKETI_DEFAULT_APP_SECRET: "${PUSHER_APP_SECRET}"
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:6001/ready && wget -qO- http://127.0.0.1:6002/ready || exit 1"]
|
test: [ "CMD-SHELL", "wget -qO- http://127.0.0.1:6001/ready && wget -qO- http://127.0.0.1:6002/ready || exit 1" ]
|
||||||
interval: 5s
|
interval: 5s
|
||||||
retries: 10
|
retries: 10
|
||||||
timeout: 2s
|
timeout: 2s
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
{
|
{
|
||||||
"coolify": {
|
"coolify": {
|
||||||
"v4": {
|
"v4": {
|
||||||
"version": "4.0.0-beta.337"
|
"version": "4.0.0-beta.338"
|
||||||
},
|
},
|
||||||
"nightly": {
|
"nightly": {
|
||||||
"version": "4.0.0-beta.338"
|
"version": "4.0.0-beta.339"
|
||||||
},
|
},
|
||||||
"helper": {
|
"helper": {
|
||||||
"version": "1.0.1"
|
"version": "1.0.1"
|
||||||
},
|
},
|
||||||
"realtime": {
|
"realtime": {
|
||||||
"version": "1.0.0"
|
"version": "1.0.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -67,6 +67,12 @@
|
|||||||
socket.onerror = (e) => {
|
socket.onerror = (e) => {
|
||||||
console.error('WebSocket error:', e);
|
console.error('WebSocket error:', e);
|
||||||
};
|
};
|
||||||
|
socket.onclose = () => {
|
||||||
|
console.log('WebSocket connection closed');
|
||||||
|
setInterval(() => {
|
||||||
|
$wire.dispatch('error', 'Connection to terminal lost, please refresh the page.');
|
||||||
|
}, 2000);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -209,8 +215,8 @@
|
|||||||
term.resize(termWidth, termHeight);
|
term.resize(termWidth, termHeight);
|
||||||
socket.send(JSON.stringify({
|
socket.send(JSON.stringify({
|
||||||
resize: {
|
resize: {
|
||||||
cols: termWidth,
|
cols: 600,
|
||||||
rows: termHeight
|
rows: 600
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
<div>
|
|
||||||
<form class="flex flex-col gap-2 justify-center xl:items-end xl:flex-row"
|
|
||||||
wire:submit="$dispatchSelf('connectToContainer')">
|
|
||||||
<x-forms.select id="server" required wire:model.live="selected_uuid">
|
|
||||||
@foreach ($servers as $server)
|
|
||||||
@if ($loop->first)
|
|
||||||
<option disabled value="default">Select a server or container</option>
|
|
||||||
@endif
|
|
||||||
<option value="{{ $server->uuid }}">{{ $server->name }}</option>
|
|
||||||
@foreach ($containers as $container)
|
|
||||||
@if ($container['server_uuid'] == $server->uuid)
|
|
||||||
<option value="{{ $container['uuid'] }}">
|
|
||||||
{{ $server->name }} -> {{ $container['name'] }}
|
|
||||||
</option>
|
|
||||||
@endif
|
|
||||||
@endforeach
|
|
||||||
@endforeach
|
|
||||||
</x-forms.select>
|
|
||||||
<x-forms.button type="submit">Connect</x-forms.button>
|
|
||||||
</form>
|
|
||||||
<livewire:project.shared.terminal />
|
|
||||||
</div>
|
|
||||||
@@ -8,11 +8,27 @@
|
|||||||
<x-helper
|
<x-helper
|
||||||
helper="If you're having trouble connecting to your server, make sure that the port is open.<br><br><a class='underline' href='https://coolify.io/docs/knowledge-base/server/firewall/#terminal' target='_blank'>Documentation</a>"></x-helper>
|
helper="If you're having trouble connecting to your server, make sure that the port is open.<br><br><a class='underline' href='https://coolify.io/docs/knowledge-base/server/firewall/#terminal' target='_blank'>Documentation</a>"></x-helper>
|
||||||
</div>
|
</div>
|
||||||
@if ($servers->count() > 0)
|
<div>
|
||||||
<livewire:run-command :servers="$servers" />
|
<form class="flex flex-col gap-2 justify-center xl:items-end xl:flex-row"
|
||||||
@else
|
wire:submit="$dispatchSelf('connectToContainer')">
|
||||||
<div>
|
<x-forms.select id="server" required wire:model.live="selected_uuid">
|
||||||
<div>No servers found. Without a server, you won't be able to do much.</div>
|
@foreach ($servers as $server)
|
||||||
</div>
|
@if ($loop->first)
|
||||||
@endif
|
<option disabled value="default">Select a server or container</option>
|
||||||
|
@endif
|
||||||
|
<option value="{{ $server->uuid }}">{{ $server->name }}</option>
|
||||||
|
@foreach ($containers as $container)
|
||||||
|
@if ($container['server_uuid'] == $server->uuid)
|
||||||
|
<option value="{{ $container['uuid'] }}">
|
||||||
|
{{ $server->name }} -> {{ $container['name'] }}
|
||||||
|
</option>
|
||||||
|
@endif
|
||||||
|
@endforeach
|
||||||
|
@endforeach
|
||||||
|
</x-forms.select>
|
||||||
|
<x-forms.button type="submit">Connect</x-forms.button>
|
||||||
|
</form>
|
||||||
|
<livewire:project.shared.terminal />
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
{
|
{
|
||||||
"coolify": {
|
"coolify": {
|
||||||
"v4": {
|
"v4": {
|
||||||
"version": "4.0.0-beta.337"
|
"version": "4.0.0-beta.338"
|
||||||
},
|
},
|
||||||
"nightly": {
|
"nightly": {
|
||||||
"version": "4.0.0-beta.338"
|
"version": "4.0.0-beta.339"
|
||||||
},
|
},
|
||||||
"helper": {
|
"helper": {
|
||||||
"version": "1.0.1"
|
"version": "1.0.1"
|
||||||
},
|
},
|
||||||
"realtime": {
|
"realtime": {
|
||||||
"version": "1.0.0"
|
"version": "1.0.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user