Merge branch 'coollabsio:main' into improve-release.md
This commit is contained in:
75
.github/workflows/remove-labels-and-assignees-on-close.yml
vendored
Normal file
75
.github/workflows/remove-labels-and-assignees-on-close.yml
vendored
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
name: Remove Labels and Assignees on Issue Close
|
||||||
|
|
||||||
|
on:
|
||||||
|
issues:
|
||||||
|
types: [closed]
|
||||||
|
pull_request:
|
||||||
|
types: [closed]
|
||||||
|
pull_request_target:
|
||||||
|
types: [closed]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
remove-labels-and-assignees:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Remove labels and assignees
|
||||||
|
uses: actions/github-script@v7
|
||||||
|
with:
|
||||||
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
script: |
|
||||||
|
const { owner, repo } = context.repo;
|
||||||
|
|
||||||
|
async function processIssue(issueNumber) {
|
||||||
|
try {
|
||||||
|
const { data: currentLabels } = await github.rest.issues.listLabelsOnIssue({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
issue_number: issueNumber
|
||||||
|
});
|
||||||
|
|
||||||
|
const labelsToKeep = currentLabels
|
||||||
|
.filter(label => label.name === '⏱︎ Stale')
|
||||||
|
.map(label => label.name);
|
||||||
|
|
||||||
|
await github.rest.issues.setLabels({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
issue_number: issueNumber,
|
||||||
|
labels: labelsToKeep
|
||||||
|
});
|
||||||
|
|
||||||
|
const { data: issue } = await github.rest.issues.get({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
issue_number: issueNumber
|
||||||
|
});
|
||||||
|
|
||||||
|
if (issue.assignees && issue.assignees.length > 0) {
|
||||||
|
await github.rest.issues.removeAssignees({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
issue_number: issueNumber,
|
||||||
|
assignees: issue.assignees.map(assignee => assignee.login)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
if (error.status !== 404) {
|
||||||
|
console.error(`Error processing issue ${issueNumber}:`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context.eventName === 'issues' || context.eventName === 'pull_request' || context.eventName === 'pull_request_target') {
|
||||||
|
const issue = context.payload.issue || context.payload.pull_request;
|
||||||
|
await processIssue(issue.number);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context.eventName === 'pull_request' || context.eventName === 'pull_request_target') {
|
||||||
|
const { data: closedIssues } = await github.rest.search.issuesAndPullRequests({
|
||||||
|
q: `repo:${owner}/${repo} is:issue is:closed linked:${context.payload.pull_request.number}`,
|
||||||
|
per_page: 100
|
||||||
|
});
|
||||||
|
for (const issue of closedIssues.items) {
|
||||||
|
await processIssue(issue.number);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -42,8 +42,8 @@ class PullHelperImageJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
$current_version = $settings->helper_version;
|
$current_version = $settings->helper_version;
|
||||||
if (version_compare($latest_version, $current_version, '>')) {
|
if (version_compare($latest_version, $current_version, '>')) {
|
||||||
// New version available
|
// New version available
|
||||||
$helperImage = config('coolify.helper_image');
|
// $helperImage = config('coolify.helper_image');
|
||||||
instant_remote_process(["docker pull -q {$helperImage}:{$latest_version}"], $this->server);
|
// instant_remote_process(["docker pull -q {$helperImage}:{$latest_version}"], $this->server);
|
||||||
$settings->update(['helper_version' => $latest_version]);
|
$settings->update(['helper_version' => $latest_version]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ class Navbar extends Component
|
|||||||
|
|
||||||
public $isDeploymentProgress = false;
|
public $isDeploymentProgress = false;
|
||||||
|
|
||||||
|
public $title = 'Configuration';
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
if (str($this->service->status())->contains('running') && is_null($this->service->config_hash)) {
|
if (str($this->service->status())->contains('running') && is_null($this->service->config_hash)) {
|
||||||
|
|||||||
@@ -33,6 +33,9 @@ class ExecuteContainerCommand extends Component
|
|||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
|
if (! auth()->user()->isAdmin()) {
|
||||||
|
abort(403);
|
||||||
|
}
|
||||||
$this->parameters = get_route_parameters();
|
$this->parameters = get_route_parameters();
|
||||||
$this->containers = collect();
|
$this->containers = collect();
|
||||||
$this->servers = collect();
|
$this->servers = collect();
|
||||||
@@ -130,7 +133,6 @@ class ExecuteContainerCommand extends Component
|
|||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$container_name = data_get($this->container, 'container.Names');
|
$container_name = data_get($this->container, 'container.Names');
|
||||||
ray($this->container);
|
|
||||||
if (is_null($container_name)) {
|
if (is_null($container_name)) {
|
||||||
throw new \RuntimeException('Container not found.');
|
throw new \RuntimeException('Container not found.');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,13 +14,6 @@ class Terminal extends Component
|
|||||||
|
|
||||||
$server = Server::ownedByCurrentTeam()->whereUuid($serverUuid)->firstOrFail();
|
$server = Server::ownedByCurrentTeam()->whereUuid($serverUuid)->firstOrFail();
|
||||||
|
|
||||||
// if (auth()->user()) {
|
|
||||||
// $teams = auth()->user()->teams->pluck('id');
|
|
||||||
// if (! $teams->contains($server->team_id) && ! $teams->contains(0)) {
|
|
||||||
// throw new \Exception('User is not part of the team that owns this server');
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
if ($isContainer) {
|
if ($isContainer) {
|
||||||
$status = getContainerStatus($server, $identifier);
|
$status = getContainerStatus($server, $identifier);
|
||||||
if ($status !== 'running') {
|
if ($status !== 'running') {
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -413,7 +413,7 @@ $schema://$host {
|
|||||||
handle /app/* {
|
handle /app/* {
|
||||||
reverse_proxy coolify-realtime:6001
|
reverse_proxy coolify-realtime:6001
|
||||||
}
|
}
|
||||||
handle /terminal/ws/* {
|
handle /terminal/ws {
|
||||||
reverse_proxy coolify-realtime:6002
|
reverse_proxy coolify-realtime:6002
|
||||||
}
|
}
|
||||||
reverse_proxy coolify:80
|
reverse_proxy coolify:80
|
||||||
@@ -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()) {
|
||||||
|
|||||||
@@ -2985,7 +2985,11 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
|
|||||||
// Get magic environments where we need to preset the FQDN
|
// Get magic environments where we need to preset the FQDN
|
||||||
if ($key->startsWith('SERVICE_FQDN_')) {
|
if ($key->startsWith('SERVICE_FQDN_')) {
|
||||||
// SERVICE_FQDN_APP or SERVICE_FQDN_APP_3000
|
// SERVICE_FQDN_APP or SERVICE_FQDN_APP_3000
|
||||||
|
if (substr_count(str($key)->value(), '_') === 3) {
|
||||||
|
$fqdnFor = $key->after('SERVICE_FQDN_')->beforeLast('_')->lower()->value();
|
||||||
|
} else {
|
||||||
$fqdnFor = $key->after('SERVICE_FQDN_')->lower()->value();
|
$fqdnFor = $key->after('SERVICE_FQDN_')->lower()->value();
|
||||||
|
}
|
||||||
if ($isApplication) {
|
if ($isApplication) {
|
||||||
$fqdn = generateFqdn($server, "{$resource->name}-$uuid");
|
$fqdn = generateFqdn($server, "{$resource->name}-$uuid");
|
||||||
} elseif ($isService) {
|
} elseif ($isService) {
|
||||||
|
|||||||
@@ -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.341',
|
||||||
// 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.341';
|
||||||
|
|||||||
@@ -46,6 +46,9 @@ services:
|
|||||||
- PUSHER_APP_ID
|
- PUSHER_APP_ID
|
||||||
- PUSHER_APP_KEY
|
- PUSHER_APP_KEY
|
||||||
- PUSHER_APP_SECRET
|
- PUSHER_APP_SECRET
|
||||||
|
- TERMINAL_PROTOCOL
|
||||||
|
- TERMINAL_HOST
|
||||||
|
- TERMINAL_PORT
|
||||||
- AUTOUPDATE
|
- AUTOUPDATE
|
||||||
- SELF_HOSTED
|
- SELF_HOSTED
|
||||||
- SSH_MUX_ENABLED
|
- SSH_MUX_ENABLED
|
||||||
@@ -110,7 +113,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,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"
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
{
|
{
|
||||||
"coolify": {
|
"coolify": {
|
||||||
"v4": {
|
"v4": {
|
||||||
"version": "4.0.0-beta.337"
|
"version": "4.0.0-beta.339"
|
||||||
},
|
},
|
||||||
"nightly": {
|
"nightly": {
|
||||||
"version": "4.0.0-beta.338"
|
"version": "4.0.0-beta.340"
|
||||||
},
|
},
|
||||||
"helper": {
|
"helper": {
|
||||||
"version": "1.0.1"
|
"version": "1.0.1"
|
||||||
},
|
},
|
||||||
"realtime": {
|
"realtime": {
|
||||||
"version": "1.0.0"
|
"version": "1.0.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
8
package-lock.json
generated
8
package-lock.json
generated
@@ -26,7 +26,7 @@
|
|||||||
"postcss": "8.4.38",
|
"postcss": "8.4.38",
|
||||||
"pusher-js": "8.4.0-rc2",
|
"pusher-js": "8.4.0-rc2",
|
||||||
"tailwindcss": "3.4.4",
|
"tailwindcss": "3.4.4",
|
||||||
"vite": "4.5.3",
|
"vite": "4.5.5",
|
||||||
"vue": "3.4.29"
|
"vue": "3.4.29"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -2082,9 +2082,9 @@
|
|||||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
|
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
|
||||||
},
|
},
|
||||||
"node_modules/vite": {
|
"node_modules/vite": {
|
||||||
"version": "4.5.3",
|
"version": "4.5.5",
|
||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-4.5.3.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-4.5.5.tgz",
|
||||||
"integrity": "sha512-kQL23kMeX92v3ph7IauVkXkikdDRsYMGTVl5KY2E9OY4ONLvkHf04MDTbnfo6NKxZiDLWzVpP5oTa8hQD8U3dg==",
|
"integrity": "sha512-ifW3Lb2sMdX+WU91s3R0FyQlAyLxOzCSCP37ujw0+r5POeHPwe6udWVIElKQq8gk3t7b8rkmvqC6IHBpCff4GQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esbuild": "^0.18.10",
|
"esbuild": "^0.18.10",
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
"postcss": "8.4.38",
|
"postcss": "8.4.38",
|
||||||
"pusher-js": "8.4.0-rc2",
|
"pusher-js": "8.4.0-rc2",
|
||||||
"tailwindcss": "3.4.4",
|
"tailwindcss": "3.4.4",
|
||||||
"vite": "4.5.3",
|
"vite": "4.5.5",
|
||||||
"vue": "3.4.29"
|
"vue": "3.4.29"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
BIN
public/svgs/budge.png
Normal file
BIN
public/svgs/budge.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.3 KiB |
@@ -23,10 +23,6 @@
|
|||||||
@click.prevent="activeTab = 'scheduled-tasks'; window.location.hash = 'scheduled-tasks'"
|
@click.prevent="activeTab = 'scheduled-tasks'; window.location.hash = 'scheduled-tasks'"
|
||||||
href="#">Scheduled Tasks
|
href="#">Scheduled Tasks
|
||||||
</a>
|
</a>
|
||||||
<a class="menu-item sm:min-w-fit" :class="activeTab === 'terminal' && 'menu-item-active'"
|
|
||||||
@click.prevent="activeTab = 'terminal';
|
|
||||||
window.location.hash = 'terminal'"
|
|
||||||
href="#">Terminal</a>
|
|
||||||
<a class="menu-item sm:min-w-fit" :class="activeTab === 'logs' && 'menu-item-active'"
|
<a class="menu-item sm:min-w-fit" :class="activeTab === 'logs' && 'menu-item-active'"
|
||||||
@click.prevent="activeTab = 'logs';
|
@click.prevent="activeTab = 'logs';
|
||||||
window.location.hash = 'logs'"
|
window.location.hash = 'logs'"
|
||||||
@@ -191,9 +187,6 @@
|
|||||||
<div x-cloak x-show="activeTab === 'logs'">
|
<div x-cloak x-show="activeTab === 'logs'">
|
||||||
<livewire:project.shared.logs :resource="$service" />
|
<livewire:project.shared.logs :resource="$service" />
|
||||||
</div>
|
</div>
|
||||||
<div x-cloak x-show="activeTab === 'terminal'">
|
|
||||||
<livewire:project.shared.execute-container-command :resource="$service" />
|
|
||||||
</div>
|
|
||||||
<div x-cloak x-show="activeTab === 'environment-variables'">
|
<div x-cloak x-show="activeTab === 'environment-variables'">
|
||||||
<livewire:project.shared.environment-variable.all :resource="$service" />
|
<livewire:project.shared.environment-variable.all :resource="$service" />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,17 +6,21 @@
|
|||||||
<livewire:activity-monitor header="Logs" showWaiting fullHeight />
|
<livewire:activity-monitor header="Logs" showWaiting fullHeight />
|
||||||
</x-slot:content>
|
</x-slot:content>
|
||||||
</x-slide-over>
|
</x-slide-over>
|
||||||
<h1>Configuration</h1>
|
<h1>{{ $title }}</h1>
|
||||||
<x-resources.breadcrumbs :resource="$service" :parameters="$parameters" />
|
<x-resources.breadcrumbs :resource="$service" :parameters="$parameters" />
|
||||||
<div class="navbar-main" x-data>
|
<div class="navbar-main" x-data>
|
||||||
<nav class="flex items-center flex-shrink-0 gap-6 scrollbar min-h-10 whitespace-nowrap">
|
<nav class="flex flex-shrink-0 gap-6 items-center whitespace-nowrap scrollbar min-h-10">
|
||||||
<a class="{{ request()->routeIs('project.service.configuration') ? 'dark:text-white' : '' }}"
|
<a class="{{ request()->routeIs('project.service.configuration') ? 'dark:text-white' : '' }}"
|
||||||
href="{{ route('project.service.configuration', $parameters) }}">
|
href="{{ route('project.service.configuration', $parameters) }}">
|
||||||
<button>Configuration</button>
|
<button>Configuration</button>
|
||||||
</a>
|
</a>
|
||||||
|
<a class="{{ request()->routeIs('project.service.command') ? 'dark:text-white' : '' }}"
|
||||||
|
href="{{ route('project.service.command', $parameters) }}">
|
||||||
|
<button>Terminal</button>
|
||||||
|
</a>
|
||||||
<x-services.links :service="$service" />
|
<x-services.links :service="$service" />
|
||||||
</nav>
|
</nav>
|
||||||
<div class="flex flex-wrap items-center order-first gap-2 sm:order-last">
|
<div class="flex flex-wrap order-first gap-2 items-center sm:order-last">
|
||||||
@if (str($service->status())->contains('running'))
|
@if (str($service->status())->contains('running'))
|
||||||
<button @click="$wire.dispatch('restartEvent')" class="gap-2 button">
|
<button @click="$wire.dispatch('restartEvent')" class="gap-2 button">
|
||||||
<svg class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
<svg class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
<h1>Terminal</h1>
|
<h1>Terminal</h1>
|
||||||
<livewire:project.database.heading :database="$resource" />
|
<livewire:project.database.heading :database="$resource" />
|
||||||
@elseif ($type === 'service')
|
@elseif ($type === 'service')
|
||||||
<h2>Terminal</h2>
|
<livewire:project.service.navbar :service="$resource" :parameters="$parameters" title="Terminal" />
|
||||||
@endif
|
@endif
|
||||||
<div x-init="$wire.loadContainers">
|
<div x-init="$wire.loadContainers">
|
||||||
<div class="pt-4" wire:loading wire:target='loadContainers'>
|
<div class="pt-4" wire:loading wire:target='loadContainers'>
|
||||||
|
|||||||
@@ -50,23 +50,42 @@
|
|||||||
|
|
||||||
function initializeWebSocket() {
|
function initializeWebSocket() {
|
||||||
if (!socket || socket.readyState === WebSocket.CLOSED) {
|
if (!socket || socket.readyState === WebSocket.CLOSED) {
|
||||||
// Only use port if Coolify is used with ip (so it has a port in the url)
|
const predefined = {
|
||||||
let postPath = ':6002/terminal/ws';
|
protocol: "{{ env('TERMINAL_PROTOCOL') }}",
|
||||||
const port = window.location.port;
|
host: "{{ env('TERMINAL_HOST') }}",
|
||||||
if (!port) {
|
port: "{{ env('TERMINAL_PORT') }}"
|
||||||
postPath = '/terminal/ws';
|
|
||||||
}
|
}
|
||||||
let url = window.location.hostname;
|
const connectionString = {
|
||||||
// make sure the port is not included
|
protocol: window.location.protocol === 'https:' ? 'wss' : 'ws',
|
||||||
url = url.split(':')[0];
|
host: window.location.hostname,
|
||||||
socket = new WebSocket((window.location.protocol === 'https:' ? 'wss://' : 'ws://') +
|
port: ":6002",
|
||||||
url +
|
path: '/terminal/ws'
|
||||||
postPath);
|
}
|
||||||
|
if (!window.location.port) {
|
||||||
|
connectionString.port = ''
|
||||||
|
}
|
||||||
|
if (predefined.host) {
|
||||||
|
connectionString.host = predefined.host
|
||||||
|
}
|
||||||
|
if (predefined.port) {
|
||||||
|
connectionString.port = `:${predefined.port}`
|
||||||
|
}
|
||||||
|
if (predefined.protocol) {
|
||||||
|
connectionString.protocol = predefined.protocol
|
||||||
|
}
|
||||||
|
|
||||||
|
const url =
|
||||||
|
`${connectionString.protocol}://${connectionString.host}${connectionString.port}${connectionString.path}`
|
||||||
|
socket = new WebSocket(url);
|
||||||
|
|
||||||
socket.onmessage = handleSocketMessage;
|
socket.onmessage = handleSocketMessage;
|
||||||
socket.onerror = (e) => {
|
socket.onerror = (e) => {
|
||||||
console.error('WebSocket error:', e);
|
console.error('WebSocket error:', e);
|
||||||
};
|
};
|
||||||
|
socket.onclose = () => {
|
||||||
|
console.log('WebSocket connection closed');
|
||||||
|
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,9 +131,8 @@
|
|||||||
socket.send(JSON.stringify({
|
socket.send(JSON.stringify({
|
||||||
message: data
|
message: data
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Type CTRL + D or exit in the terminal
|
// Type CTRL + D or exit in the terminal
|
||||||
if (data === '\x04' || (data === '\r' && stripAnsiCommands(commandBuffer).trim() === 'exit')) {
|
if (data === '\x04' || (data === '\r' && stripAnsiCommands(commandBuffer).trim().includes('exit'))) {
|
||||||
checkIfProcessIsRunningAndKillIt();
|
checkIfProcessIsRunningAndKillIt();
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
$data.terminalActive = false;
|
$data.terminalActive = false;
|
||||||
|
|||||||
@@ -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)
|
|
||||||
<livewire:run-command :servers="$servers" />
|
|
||||||
@else
|
|
||||||
<div>
|
<div>
|
||||||
<div>No servers found. Without a server, you won't be able to do much.</div>
|
<form class="flex flex-col gap-2 justify-center xl:items-end xl:flex-row"
|
||||||
</div>
|
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
|
@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>
|
||||||
|
|||||||
@@ -195,8 +195,8 @@ Route::middleware(['auth', 'verified'])->group(function () {
|
|||||||
});
|
});
|
||||||
Route::prefix('project/{project_uuid}/{environment_name}/service/{service_uuid}')->group(function () {
|
Route::prefix('project/{project_uuid}/{environment_name}/service/{service_uuid}')->group(function () {
|
||||||
Route::get('/', ServiceConfiguration::class)->name('project.service.configuration');
|
Route::get('/', ServiceConfiguration::class)->name('project.service.configuration');
|
||||||
Route::get('/{stack_service_uuid}', ServiceIndex::class)->name('project.service.index');
|
|
||||||
Route::get('/terminal', ExecuteContainerCommand::class)->name('project.service.command');
|
Route::get('/terminal', ExecuteContainerCommand::class)->name('project.service.command');
|
||||||
|
Route::get('/{stack_service_uuid}', ServiceIndex::class)->name('project.service.index');
|
||||||
Route::get('/tasks/{task_uuid}', ScheduledTaskShow::class)->name('project.service.scheduled-tasks');
|
Route::get('/tasks/{task_uuid}', ScheduledTaskShow::class)->name('project.service.scheduled-tasks');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
# documentation: https://github.com/linuxserver/budge
|
# documentation: https://github.com/linuxserver/budge
|
||||||
# slogan: A budgeting personal finance app.
|
# slogan: A budgeting personal finance app.
|
||||||
# tags: personal finance, budgeting, expense tracking
|
# tags: personal finance, budgeting, expense tracking
|
||||||
|
# logo: svgs/budge.png
|
||||||
|
|
||||||
services:
|
services:
|
||||||
budge:
|
budge:
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -1,16 +1,16 @@
|
|||||||
{
|
{
|
||||||
"coolify": {
|
"coolify": {
|
||||||
"v4": {
|
"v4": {
|
||||||
"version": "4.0.0-beta.337"
|
"version": "4.0.0-beta.341"
|
||||||
},
|
},
|
||||||
"nightly": {
|
"nightly": {
|
||||||
"version": "4.0.0-beta.338"
|
"version": "4.0.0-beta.342"
|
||||||
},
|
},
|
||||||
"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