diff --git a/.github/workflows/remove-labels-and-assignees-on-close.yml b/.github/workflows/remove-labels-and-assignees-on-close.yml
new file mode 100644
index 000000000..04d62623c
--- /dev/null
+++ b/.github/workflows/remove-labels-and-assignees-on-close.yml
@@ -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);
+ }
+ }
diff --git a/app/Jobs/PullHelperImageJob.php b/app/Jobs/PullHelperImageJob.php
index 420119069..63b7fa920 100644
--- a/app/Jobs/PullHelperImageJob.php
+++ b/app/Jobs/PullHelperImageJob.php
@@ -42,8 +42,8 @@ class PullHelperImageJob implements ShouldBeEncrypted, ShouldQueue
$current_version = $settings->helper_version;
if (version_compare($latest_version, $current_version, '>')) {
// New version available
- $helperImage = config('coolify.helper_image');
- instant_remote_process(["docker pull -q {$helperImage}:{$latest_version}"], $this->server);
+ // $helperImage = config('coolify.helper_image');
+ // instant_remote_process(["docker pull -q {$helperImage}:{$latest_version}"], $this->server);
$settings->update(['helper_version' => $latest_version]);
}
}
diff --git a/app/Livewire/Project/Service/Navbar.php b/app/Livewire/Project/Service/Navbar.php
index 674182df5..e6bb6d9bf 100644
--- a/app/Livewire/Project/Service/Navbar.php
+++ b/app/Livewire/Project/Service/Navbar.php
@@ -20,6 +20,8 @@ class Navbar extends Component
public $isDeploymentProgress = false;
+ public $title = 'Configuration';
+
public function mount()
{
if (str($this->service->status())->contains('running') && is_null($this->service->config_hash)) {
diff --git a/app/Livewire/Project/Shared/ExecuteContainerCommand.php b/app/Livewire/Project/Shared/ExecuteContainerCommand.php
index 79f32ab8b..d95443621 100644
--- a/app/Livewire/Project/Shared/ExecuteContainerCommand.php
+++ b/app/Livewire/Project/Shared/ExecuteContainerCommand.php
@@ -33,6 +33,9 @@ class ExecuteContainerCommand extends Component
public function mount()
{
+ if (! auth()->user()->isAdmin()) {
+ abort(403);
+ }
$this->parameters = get_route_parameters();
$this->containers = collect();
$this->servers = collect();
@@ -130,7 +133,6 @@ class ExecuteContainerCommand extends Component
{
try {
$container_name = data_get($this->container, 'container.Names');
- ray($this->container);
if (is_null($container_name)) {
throw new \RuntimeException('Container not found.');
}
diff --git a/app/Livewire/Project/Shared/Terminal.php b/app/Livewire/Project/Shared/Terminal.php
index 7c23c291d..802e65a30 100644
--- a/app/Livewire/Project/Shared/Terminal.php
+++ b/app/Livewire/Project/Shared/Terminal.php
@@ -14,13 +14,6 @@ class Terminal extends Component
$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) {
$status = getContainerStatus($server, $identifier);
if ($status !== 'running') {
diff --git a/app/Livewire/RunCommand.php b/app/Livewire/RunCommand.php
deleted file mode 100644
index 290618bef..000000000
--- a/app/Livewire/RunCommand.php
+++ /dev/null
@@ -1,101 +0,0 @@
-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
- );
- }
-}
diff --git a/app/Livewire/Terminal/Index.php b/app/Livewire/Terminal/Index.php
index 3f777a8ff..945b25714 100644
--- a/app/Livewire/Terminal/Index.php
+++ b/app/Livewire/Terminal/Index.php
@@ -3,15 +3,70 @@
namespace App\Livewire\Terminal;
use App\Models\Server;
+use Livewire\Attributes\On;
use Livewire\Component;
class Index extends Component
{
+ public $selected_uuid = 'default';
+
public $servers = [];
+ public $containers = [];
+
public function mount()
{
+ if (! auth()->user()->isAdmin()) {
+ abort(403);
+ }
$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()
diff --git a/app/Models/Server.php b/app/Models/Server.php
index 3a11c2206..90e6ade14 100644
--- a/app/Models/Server.php
+++ b/app/Models/Server.php
@@ -413,7 +413,7 @@ $schema://$host {
handle /app/* {
reverse_proxy coolify-realtime:6001
}
- handle /terminal/ws/* {
+ handle /terminal/ws {
reverse_proxy coolify-realtime:6002
}
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
{
if ($this->isFunctional()) {
diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php
index abfde7135..cd2779466 100644
--- a/bootstrap/helpers/shared.php
+++ b/bootstrap/helpers/shared.php
@@ -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
if ($key->startsWith('SERVICE_FQDN_')) {
// SERVICE_FQDN_APP or SERVICE_FQDN_APP_3000
- $fqdnFor = $key->after('SERVICE_FQDN_')->lower()->value();
+ if (substr_count(str($key)->value(), '_') === 3) {
+ $fqdnFor = $key->after('SERVICE_FQDN_')->beforeLast('_')->lower()->value();
+ } else {
+ $fqdnFor = $key->after('SERVICE_FQDN_')->lower()->value();
+ }
if ($isApplication) {
$fqdn = generateFqdn($server, "{$resource->name}-$uuid");
} elseif ($isService) {
diff --git a/config/sentry.php b/config/sentry.php
index 8fb0e5cdd..471a1e0fc 100644
--- a/config/sentry.php
+++ b/config/sentry.php
@@ -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.337',
+ 'release' => '4.0.0-beta.341',
// When left empty or `null` the Laravel environment will be used
'environment' => config('app.env'),
diff --git a/config/version.php b/config/version.php
index aac06d60d..32eb01cd0 100644
--- a/config/version.php
+++ b/config/version.php
@@ -1,3 +1,3 @@
Scheduled Tasks
-