fixes for terminal

This commit is contained in:
Andras Bacsai
2024-09-11 12:19:27 +02:00
parent 33e9c9b0f9
commit 117fbeb07c
12 changed files with 279 additions and 226 deletions

View File

@@ -11,17 +11,19 @@ class Terminal extends Component
#[On('send-terminal-command')] #[On('send-terminal-command')]
public function sendTerminalCommand($isContainer, $identifier, $serverUuid) public function sendTerminalCommand($isContainer, $identifier, $serverUuid)
{ {
$server = Server::whereUuid($serverUuid)->firstOrFail(); $server = Server::ownedByCurrentTeam()->whereUuid($serverUuid)->firstOrFail();
if (auth()->user()) { // if (auth()->user()) {
$teams = auth()->user()->teams->pluck('id'); // $teams = auth()->user()->teams->pluck('id');
if (! $teams->contains($server->team_id) && ! $teams->contains(0)) { // if (! $teams->contains($server->team_id) && ! $teams->contains(0)) {
throw new \Exception('User is not part of the team that owns this server'); // throw new \Exception('User is not part of the team that owns this server');
} // }
} // }
if ($isContainer) { if ($isContainer) {
ray($identifier);
$status = getContainerStatus($server, $identifier); $status = getContainerStatus($server, $identifier);
ray($status);
if ($status !== 'running') { if ($status !== 'running') {
return handleError(new \Exception('Container is not running'), $this); return handleError(new \Exception('Container is not running'), $this);
} }

View File

@@ -2,7 +2,6 @@
namespace App\Livewire; namespace App\Livewire;
use App\Models\Server;
use Livewire\Attributes\On; use Livewire\Attributes\On;
use Livewire\Component; use Livewire\Component;
@@ -23,7 +22,7 @@ class RunCommand extends Component
private function getAllActiveContainers() private function getAllActiveContainers()
{ {
return Server::all()->flatMap(function ($server) { return collect($this->servers)->flatMap(function ($server) {
if (! $server->isFunctional()) { if (! $server->isFunctional()) {
return []; return [];
} }
@@ -31,25 +30,52 @@ class RunCommand extends Component
return $server->definedResources() return $server->definedResources()
->filter(function ($resource) { ->filter(function ($resource) {
$status = method_exists($resource, 'realStatus') ? $resource->realStatus() : (method_exists($resource, 'status') ? $resource->status() : 'exited'); $status = method_exists($resource, 'realStatus') ? $resource->realStatus() : (method_exists($resource, 'status') ? $resource->status() : 'exited');
return str_starts_with($status, 'running:'); return str_starts_with($status, 'running:');
}) })
->map(function ($resource) use ($server) { ->map(function ($resource) use ($server) {
$container_name = $resource->uuid; if (isDev()) {
if (data_get($resource, 'name') === 'coolify-db') {
$container_name = 'coolify-db';
if (class_basename($resource) === 'Application' || class_basename($resource) === 'Service') { 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()) { if ($server->isSwarm()) {
$container_name = $resource->uuid.'_'.$resource->uuid; $container_name = $resource->uuid.'_'.$resource->uuid;
} else { } else {
$current_containers = getCurrentApplicationContainerStatus($server, $resource->id, includePullrequests: true);
$container_name = data_get($current_containers->first(), 'Names'); $container_name = data_get($current_containers->first(), 'Names');
} }
}
return [ return [
'name' => $resource->name, 'name' => $resource->name,
'connection_name' => $container_name, 'connection_name' => $container_name,
'uuid' => $resource->uuid, 'uuid' => $resource->uuid,
'status' => $resource->status, 'status' => $status,
'server' => $server, 'server' => $server,
'server_uuid' => $server->uuid, 'server_uuid' => $server->uuid,
]; ];

View File

@@ -40,6 +40,20 @@ function getCurrentApplicationContainerStatus(Server $server, int $id, ?int $pul
return $containers; return $containers;
} }
function getCurrentServiceContainerStatus(Server $server, int $id): Collection
{
$containers = collect([]);
if (! $server->isSwarm()) {
$containers = instant_remote_process(["docker ps -a --filter='label=coolify.serviceId={$id}' --format '{{json .}}' "], $server);
$containers = format_docker_command_output_to_json($containers);
$containers = $containers->filter();
return $containers;
}
return $containers;
}
function format_docker_command_output_to_json($rawOutput): Collection function format_docker_command_output_to_json($rawOutput): Collection
{ {
$outputLines = explode(PHP_EOL, $rawOutput); $outputLines = explode(PHP_EOL, $rawOutput);

View File

@@ -43,25 +43,23 @@ services:
- /data/coolify/_volumes/redis/:/data - /data/coolify/_volumes/redis/:/data
# - coolify-redis-data-dev:/data # - coolify-redis-data-dev:/data
soketi: soketi:
build:
context: .
dockerfile: ./docker/coolify-realtime/Dockerfile
env_file: env_file:
- .env - .env
ports: ports:
- "${FORWARD_SOKETI_PORT:-6001}:6001" - "${FORWARD_SOKETI_PORT:-6001}:6001"
- "6002:6002" - "6002:6002"
volumes: volumes:
- ./docker/soketi-entrypoint/soketi-entrypoint.sh:/soketi-entrypoint.sh
- ./package.json:/terminal/package.json
- ./package-lock.json:/terminal/package-lock.json
- ./terminal-server.js:/terminal/terminal-server.js
- ./storage:/var/www/html/storage - ./storage:/var/www/html/storage
entrypoint: ["/bin/sh", "/soketi-entrypoint.sh"]
environment: environment:
SOKETI_DEBUG: "false" SOKETI_DEBUG: "false"
SOKETI_DEFAULT_APP_ID: "${PUSHER_APP_ID:-coolify}" SOKETI_DEFAULT_APP_ID: "${PUSHER_APP_ID:-coolify}"
SOKETI_DEFAULT_APP_KEY: "${PUSHER_APP_KEY:-coolify}" SOKETI_DEFAULT_APP_KEY: "${PUSHER_APP_KEY:-coolify}"
SOKETI_DEFAULT_APP_SECRET: "${PUSHER_APP_SECRET:-coolify}" SOKETI_DEFAULT_APP_SECRET: "${PUSHER_APP_SECRET:-coolify}"
vite: vite:
image: node:alpine image: node:20
pull_policy: always pull_policy: always
working_dir: /var/www/html working_dir: /var/www/html
# environment: # environment:

View File

@@ -109,6 +109,7 @@ services:
retries: 10 retries: 10
timeout: 2s timeout: 2s
soketi: soketi:
image: 'ghcr.io/coollabsio/coolify-realtime:latest'
ports: ports:
- "${SOKETI_PORT:-6001}:6001" - "${SOKETI_PORT:-6001}:6001"
- "6002:6002" - "6002:6002"

View File

@@ -103,7 +103,7 @@ services:
retries: 10 retries: 10
timeout: 2s timeout: 2s
soketi: soketi:
image: 'quay.io/soketi/soketi:1.6-16-alpine' image: 'ghcr.io/coollabsio/coolify-realtime:latest'
pull_policy: always pull_policy: always
container_name: coolify-realtime container_name: coolify-realtime
restart: always restart: always

View File

@@ -24,7 +24,6 @@ services:
networks: networks:
- coolify - coolify
soketi: soketi:
image: 'quay.io/soketi/soketi:1.6-16-alpine'
container_name: coolify-realtime container_name: coolify-realtime
restart: always restart: always
networks: networks:

View File

@@ -0,0 +1,9 @@
FROM quay.io/soketi/soketi:1.6-16-alpine
WORKDIR /terminal
RUN apk add --no-cache openssh-client make g++ python3
COPY docker/coolify-realtime/package.json ./
RUN npm i
RUN npm rebuild node-pty --update-binary
COPY docker/coolify-realtime/soketi-entrypoint.sh /soketi-entrypoint.sh
COPY docker/coolify-realtime/terminal-server.js /terminal/terminal-server.js
ENTRYPOINT ["/bin/sh", "/soketi-entrypoint.sh"]

View File

@@ -0,0 +1,13 @@
{
"private": true,
"type": "module",
"dependencies": {
"@xterm/addon-fit": "^0.10.0",
"@xterm/xterm": "^5.5.0",
"cookie": "^0.6.0",
"axios": "1.7.5",
"dotenv": "^16.4.5",
"node-pty": "^1.0.0",
"ws": "^8.17.0"
}
}

View File

@@ -1,16 +1,4 @@
#!/bin/sh #!/bin/sh
# Install openssh-client
apk add --no-cache openssh-client make g++ python3
cd /terminal
# Install npm dependencies
npm ci
# Rebuild node-pty
npm rebuild node-pty --update-binary
# Function to timestamp logs # Function to timestamp logs
timestamp() { timestamp() {
date "+%Y-%m-%d %H:%M:%S" date "+%Y-%m-%d %H:%M:%S"

View File

@@ -1,6 +1,7 @@
<div x-data="data()"> <div x-data="data()">
<div x-show="!terminalActive" class="flex items-center justify-center w-full py-4 mx-auto h-[510px]"> <div x-show="!terminalActive" class="flex items-center justify-center w-full py-4 mx-auto h-[510px]">
<div class="w-full h-full border rounded dark:bg-coolgray-100 dark:border-coolgray-300"> <div
class="w-full h-full bg-white border border-solid rounded dark:text-white dark:bg-coolgray-100 scrollbar border-neutral-300 dark:border-coolgray-300 p-1">
<span class="font-mono text-sm text-gray-500 ">(connection closed)</span> <span class="font-mono text-sm text-gray-500 ">(connection closed)</span>
</div> </div>
</div> </div>
@@ -34,7 +35,9 @@
function initializeWebSocket() { function initializeWebSocket() {
if (!socket || socket.readyState === WebSocket.CLOSED) { if (!socket || socket.readyState === WebSocket.CLOSED) {
const url = "{{ str_replace(['http://', 'https://'], '', config('app.url')) }}" || window.location.hostname; let url = "{{ str_replace(['http://', 'https://'], '', config('app.url')) }}" || window.location.hostname;
// make sure the port is not included
url = url.split(':')[0];
socket = new WebSocket((window.location.protocol === 'https:' ? 'wss://' : 'ws://') + socket = new WebSocket((window.location.protocol === 'https:' ? 'wss://' : 'ws://') +
url + url +
':6002/terminal'); ':6002/terminal');