This commit is contained in:
ShadowArcanist 2026-02-28 08:08:07 -05:00 committed by GitHub
commit e399f16e7c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
23 changed files with 674 additions and 44 deletions

View file

@ -1237,7 +1237,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
});
foreach ($runtime_environment_variables as $env) {
$envs->push($env->key.'='.$env->real_value);
$envs->push($env->key.'='.$env->getResolvedValueWithServer($this->server));
}
// Check for PORT environment variable mismatch with ports_exposes
@ -1303,7 +1303,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
});
foreach ($runtime_environment_variables_preview as $env) {
$envs->push($env->key.'='.$env->real_value);
$envs->push($env->key.'='.$env->getResolvedValueWithServer($this->server));
}
// Add PORT if not exists, use the first port as default
if ($this->build_pack !== 'dockercompose') {
@ -2324,14 +2324,16 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
$this->env_nixpacks_args = collect([]);
if ($this->pull_request_id === 0) {
foreach ($this->application->nixpacks_environment_variables as $env) {
if (! is_null($env->real_value) && $env->real_value !== '') {
$this->env_nixpacks_args->push("--env {$env->key}={$env->real_value}");
$resolvedValue = $env->getResolvedValueWithServer($this->server);
if (! is_null($resolvedValue) && $resolvedValue !== '') {
$this->env_nixpacks_args->push("--env {$env->key}={$resolvedValue}");
}
}
} else {
foreach ($this->application->nixpacks_environment_variables_preview as $env) {
if (! is_null($env->real_value) && $env->real_value !== '') {
$this->env_nixpacks_args->push("--env {$env->key}={$env->real_value}");
$resolvedValue = $env->getResolvedValueWithServer($this->server);
if (! is_null($resolvedValue) && $resolvedValue !== '') {
$this->env_nixpacks_args->push("--env {$env->key}={$resolvedValue}");
}
}
}
@ -2467,8 +2469,9 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
->get();
foreach ($envs as $env) {
if (! is_null($env->real_value)) {
$this->env_args->put($env->key, $env->real_value);
$resolvedValue = $env->getResolvedValueWithServer($this->server);
if (! is_null($resolvedValue)) {
$this->env_args->put($env->key, $resolvedValue);
}
}
} else {
@ -2478,8 +2481,9 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
->get();
foreach ($envs as $env) {
if (! is_null($env->real_value)) {
$this->env_args->put($env->key, $env->real_value);
$resolvedValue = $env->getResolvedValueWithServer($this->server);
if (! is_null($resolvedValue)) {
$this->env_args->put($env->key, $resolvedValue);
}
}
}

View file

@ -67,6 +67,7 @@ class Add extends Component
'team' => [],
'project' => [],
'environment' => [],
'server' => [],
];
// Early return if no team
@ -122,6 +123,66 @@ class Add extends Component
}
}
// Get server variables
$serverUuid = data_get($this->parameters, 'server_uuid');
if ($serverUuid) {
// If we have a specific server_uuid, show variables for that server
$server = \App\Models\Server::where('team_id', $team->id)
->where('uuid', $serverUuid)
->first();
if ($server) {
try {
$this->authorize('view', $server);
$result['server'] = $server->environment_variables()
->pluck('key')
->toArray();
} catch (\Illuminate\Auth\Access\AuthorizationException $e) {
// User not authorized to view server variables
}
}
} else {
// For application environment variables, try to use the application's destination server
$applicationUuid = data_get($this->parameters, 'application_uuid');
if ($applicationUuid) {
$application = \App\Models\Application::whereRelation('environment.project.team', 'id', $team->id)
->where('uuid', $applicationUuid)
->with('destination.server')
->first();
if ($application && $application->destination && $application->destination->server) {
try {
$this->authorize('view', $application->destination->server);
$result['server'] = $application->destination->server->environment_variables()
->pluck('key')
->toArray();
} catch (\Illuminate\Auth\Access\AuthorizationException $e) {
// User not authorized to view server variables
}
}
} else {
// For service environment variables, try to use the service's server
$serviceUuid = data_get($this->parameters, 'service_uuid');
if ($serviceUuid) {
$service = \App\Models\Service::whereRelation('environment.project.team', 'id', $team->id)
->where('uuid', $serviceUuid)
->with('server')
->first();
if ($service && $service->server) {
try {
$this->authorize('view', $service->server);
$result['server'] = $service->server->environment_variables()
->pluck('key')
->toArray();
} catch (\Illuminate\Auth\Access\AuthorizationException $e) {
// User not authorized to view server variables
}
}
}
}
}
return $result;
}

View file

@ -204,6 +204,7 @@ class Show extends Component
'team' => [],
'project' => [],
'environment' => [],
'server' => [],
];
// Early return if no team
@ -259,6 +260,66 @@ class Show extends Component
}
}
// Get server variables
$serverUuid = data_get($this->parameters, 'server_uuid');
if ($serverUuid) {
// If we have a specific server_uuid, show variables for that server
$server = \App\Models\Server::where('team_id', $team->id)
->where('uuid', $serverUuid)
->first();
if ($server) {
try {
$this->authorize('view', $server);
$result['server'] = $server->environment_variables()
->pluck('key')
->toArray();
} catch (\Illuminate\Auth\Access\AuthorizationException $e) {
// User not authorized to view server variables
}
}
} else {
// For application environment variables, try to use the application's destination server
$applicationUuid = data_get($this->parameters, 'application_uuid');
if ($applicationUuid) {
$application = \App\Models\Application::whereRelation('environment.project.team', 'id', $team->id)
->where('uuid', $applicationUuid)
->with('destination.server')
->first();
if ($application && $application->destination && $application->destination->server) {
try {
$this->authorize('view', $application->destination->server);
$result['server'] = $application->destination->server->environment_variables()
->pluck('key')
->toArray();
} catch (\Illuminate\Auth\Access\AuthorizationException $e) {
// User not authorized to view server variables
}
}
} else {
// For service environment variables, try to use the service's server
$serviceUuid = data_get($this->parameters, 'service_uuid');
if ($serviceUuid) {
$service = \App\Models\Service::whereRelation('environment.project.team', 'id', $team->id)
->where('uuid', $serviceUuid)
->with('server')
->first();
if ($service && $service->server) {
try {
$this->authorize('view', $service->server);
$result['server'] = $service->server->environment_variables()
->pluck('key')
->toArray();
} catch (\Illuminate\Auth\Access\AuthorizationException $e) {
// User not authorized to view server variables
}
}
}
}
}
return $result;
}

View file

@ -0,0 +1,22 @@
<?php
namespace App\Livewire\SharedVariables\Server;
use App\Models\Server;
use Illuminate\Support\Collection;
use Livewire\Component;
class Index extends Component
{
public Collection $servers;
public function mount()
{
$this->servers = Server::ownedByCurrentTeamCached();
}
public function render()
{
return view('livewire.shared-variables.server.index');
}
}

View file

@ -0,0 +1,184 @@
<?php
namespace App\Livewire\SharedVariables\Server;
use App\Models\Server;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Support\Facades\DB;
use Livewire\Component;
class Show extends Component
{
use AuthorizesRequests;
public Server $server;
public string $view = 'normal';
public ?string $variables = null;
protected $listeners = ['refreshEnvs' => 'refreshEnvs', 'saveKey' => 'saveKey', 'environmentVariableDeleted' => 'refreshEnvs'];
public function saveKey($data)
{
try {
$this->authorize('update', $this->server);
if (in_array($data['key'], ['COOLIFY_SERVER_UUID', 'COOLIFY_SERVER_NAME'])) {
throw new \Exception('Cannot create predefined variable.');
}
$found = $this->server->environment_variables()->where('key', $data['key'])->first();
if ($found) {
throw new \Exception('Variable already exists.');
}
$this->server->environment_variables()->create([
'key' => $data['key'],
'value' => $data['value'],
'is_multiline' => $data['is_multiline'],
'is_literal' => $data['is_literal'],
'type' => 'server',
'team_id' => currentTeam()->id,
]);
$this->server->refresh();
$this->getDevView();
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function mount()
{
$serverUuid = request()->route('server_uuid');
$teamId = currentTeam()->id;
$server = Server::where('team_id', $teamId)->where('uuid', $serverUuid)->first();
if (! $server) {
return redirect()->route('dashboard');
}
$this->authorize('view', $server);
$this->server = $server;
$this->getDevView();
}
public function switch()
{
$this->authorize('view', $this->server);
$this->view = $this->view === 'normal' ? 'dev' : 'normal';
$this->getDevView();
}
public function getDevView()
{
$this->variables = $this->formatEnvironmentVariables($this->server->environment_variables->whereNotIn('key', ['COOLIFY_SERVER_UUID', 'COOLIFY_SERVER_NAME'])->sortBy('key'));
}
private function formatEnvironmentVariables($variables)
{
return $variables->map(function ($item) {
if ($item->is_shown_once) {
return "$item->key=(Locked Secret, delete and add again to change)";
}
if ($item->is_multiline) {
return "$item->key=(Multiline environment variable, edit in normal view)";
}
return "$item->key=$item->value";
})->join("\n");
}
public function submit()
{
try {
$this->authorize('update', $this->server);
$this->handleBulkSubmit();
$this->getDevView();
} catch (\Throwable $e) {
return handleError($e, $this);
} finally {
$this->refreshEnvs();
}
}
private function handleBulkSubmit()
{
$variables = parseEnvFormatToArray($this->variables);
$changesMade = DB::transaction(function () use ($variables) {
// Delete removed variables
$deletedCount = $this->deleteRemovedVariables($variables);
// Update or create variables
$updatedCount = $this->updateOrCreateVariables($variables);
return $deletedCount > 0 || $updatedCount > 0;
});
if ($changesMade) {
$this->dispatch('success', 'Environment variables updated.');
}
}
private function deleteRemovedVariables($variables)
{
$variablesToDelete = $this->server->environment_variables()
->whereNotIn('key', array_keys($variables))
->whereNotIn('key', ['COOLIFY_SERVER_UUID', 'COOLIFY_SERVER_NAME'])
->get();
if ($variablesToDelete->isEmpty()) {
return 0;
}
$this->server->environment_variables()
->whereNotIn('key', array_keys($variables))
->whereNotIn('key', ['COOLIFY_SERVER_UUID', 'COOLIFY_SERVER_NAME'])
->delete();
return $variablesToDelete->count();
}
private function updateOrCreateVariables($variables)
{
$count = 0;
foreach ($variables as $key => $value) {
// Skip predefined variables
if (in_array($key, ['COOLIFY_SERVER_UUID', 'COOLIFY_SERVER_NAME'])) {
continue;
}
$found = $this->server->environment_variables()->where('key', $key)->first();
if ($found) {
if (! $found->is_shown_once && ! $found->is_multiline) {
if ($found->value !== $value) {
$found->value = $value;
$found->save();
$count++;
}
}
} else {
$this->server->environment_variables()->create([
'key' => $key,
'value' => $value,
'is_multiline' => false,
'is_literal' => false,
'type' => 'server',
'team_id' => currentTeam()->id,
]);
$count++;
}
}
return $count;
}
public function refreshEnvs()
{
$this->server->refresh();
$this->getDevView();
}
public function render()
{
return view('livewire.shared-variables.server.show');
}
}

View file

@ -56,7 +56,7 @@ class Environment extends BaseModel
public function environment_variables()
{
return $this->hasMany(SharedEnvironmentVariable::class);
return $this->hasMany(SharedEnvironmentVariable::class)->where('type', 'environment');
}
public function applications()

View file

@ -122,6 +122,17 @@ class EnvironmentVariable extends BaseModel
return null;
}
// Load relationships needed for shared variable resolution
if (! $resource->relationLoaded('environment')) {
$resource->load('environment');
}
if (! $resource->relationLoaded('server') && method_exists($resource, 'server')) {
$resource->load('server');
}
if (! $resource->relationLoaded('destination') && method_exists($resource, 'destination')) {
$resource->load('destination.server');
}
$real_value = $this->get_real_environment_variables($this->value, $resource);
// Skip escaping for valid JSON objects/arrays to prevent quote corruption (see #6160)
@ -187,7 +198,43 @@ class EnvironmentVariable extends BaseModel
);
}
private function get_real_environment_variables(?string $environment_variable = null, $resource = null)
public function get_real_environment_variables_with_server(?string $environment_variable = null, $resource = null, $server = null)
{
return $this->get_real_environment_variables_internal($environment_variable, $resource, $server);
}
public function getResolvedValueWithServer($server = null)
{
if (! $this->relationLoaded('resourceable')) {
$this->load('resourceable');
}
$resource = $this->resourceable;
if (! $resource) {
return null;
}
// Load relationships needed for shared variable resolution
if (! $resource->relationLoaded('environment')) {
$resource->load('environment');
}
if (! $resource->relationLoaded('server') && method_exists($resource, 'server')) {
$resource->load('server');
}
if (! $resource->relationLoaded('destination') && method_exists($resource, 'destination')) {
$resource->load('destination.server');
}
$real_value = $this->get_real_environment_variables_internal($this->value, $resource, $server);
if ($this->is_literal || $this->is_multiline) {
$real_value = '\''.$real_value.'\'';
} else {
$real_value = escapeEnvVariables($real_value);
}
return $real_value;
}
private function get_real_environment_variables_internal(?string $environment_variable = null, $resource = null, $serverOverride = null)
{
if ((is_null($environment_variable) && $environment_variable === '') || is_null($resource)) {
return null;
@ -209,6 +256,17 @@ class EnvironmentVariable extends BaseModel
$id = $resource->environment->project->id;
} elseif ($type->value() === 'team') {
$id = $resource->team()->id;
} elseif ($type->value() === 'server') {
// Use server override if provided (for deployment context), otherwise use resource's server
if ($serverOverride) {
$id = $serverOverride->id;
} elseif (isset($resource->server) && $resource->server) {
$id = $resource->server->id;
} elseif (isset($resource->destination) && $resource->destination && isset($resource->destination->server)) {
$id = $resource->destination->server->id;
} else {
$id = null;
}
}
if (is_null($id)) {
continue;
@ -222,6 +280,11 @@ class EnvironmentVariable extends BaseModel
return str($environment_variable)->value();
}
private function get_real_environment_variables(?string $environment_variable = null, $resource = null)
{
return $this->get_real_environment_variables_internal($environment_variable, $resource);
}
private function get_environment_variables(?string $environment_variable = null): ?string
{
if (! $environment_variable) {

View file

@ -67,7 +67,7 @@ class Project extends BaseModel
public function environment_variables()
{
return $this->hasMany(SharedEnvironmentVariable::class);
return $this->hasMany(SharedEnvironmentVariable::class)->where('type', 'project');
}
public function environments()

View file

@ -178,6 +178,24 @@ class Server extends BaseModel
if (! isset($server->proxy->redirect_enabled)) {
$server->proxy->redirect_enabled = true;
}
// Create predefined server shared variables
SharedEnvironmentVariable::create([
'key' => 'COOLIFY_SERVER_UUID',
'value' => $server->uuid,
'type' => 'server',
'server_id' => $server->id,
'team_id' => $server->team_id,
'is_literal' => true,
]);
SharedEnvironmentVariable::create([
'key' => 'COOLIFY_SERVER_NAME',
'value' => $server->name,
'type' => 'server',
'server_id' => $server->id,
'team_id' => $server->team_id,
'is_literal' => true,
]);
});
static::retrieved(function ($server) {
if (! isset($server->proxy->redirect_enabled)) {
@ -1003,6 +1021,11 @@ $schema://$host {
return $this->belongsTo(Team::class);
}
public function environment_variables()
{
return $this->hasMany(SharedEnvironmentVariable::class)->where('type', 'server');
}
public function isProxyShouldRun()
{
// TODO: Do we need "|| $this->proxy->force_stop" here?

View file

@ -27,4 +27,9 @@ class SharedEnvironmentVariable extends Model
{
return $this->belongsTo(Environment::class);
}
public function server()
{
return $this->belongsTo(Server::class);
}
}

View file

@ -215,7 +215,7 @@ class Team extends Model implements SendsDiscord, SendsEmail, SendsPushover, Sen
public function environment_variables()
{
return $this->hasMany(SharedEnvironmentVariable::class)->whereNull('project_id')->whereNull('environment_id');
return $this->hasMany(SharedEnvironmentVariable::class)->where('type', 'team');
}
public function members()

View file

@ -38,6 +38,7 @@ class EnvVarInput extends Component
public array $availableVars = [],
public ?string $projectUuid = null,
public ?string $environmentUuid = null,
public ?string $serverUuid = null,
) {
// Handle authorization-based disabling
if ($this->canGate && $this->canResource && $this->autoDisable) {
@ -86,6 +87,9 @@ class EnvVarInput extends Component
'environment_uuid' => $this->environmentUuid,
])
: route('shared-variables.environment.index'),
'server' => $this->serverUuid
? route('shared-variables.server.show', ['server_uuid' => $this->serverUuid])
: route('shared-variables.server.index'),
'default' => route('shared-variables.index'),
];

View file

@ -81,4 +81,4 @@ const NEEDS_TO_DISABLE_GZIP = [
const NEEDS_TO_DISABLE_STRIPPREFIX = [
'appwrite' => ['appwrite', 'appwrite-console', 'appwrite-realtime'],
];
const SHARED_VARIABLE_TYPES = ['team', 'project', 'environment'];
const SHARED_VARIABLE_TYPES = ['team', 'project', 'environment', 'server'];

View file

@ -0,0 +1,35 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
/**
* Run the migrations.
*/
public function up(): void
{
DB::statement("ALTER TABLE shared_environment_variables DROP CONSTRAINT shared_environment_variables_type_check");
DB::statement("ALTER TABLE shared_environment_variables ADD CONSTRAINT shared_environment_variables_type_check CHECK (type IN ('team', 'project', 'environment', 'server'))");
Schema::table('shared_environment_variables', function (Blueprint $table) {
$table->foreignId('server_id')->nullable()->constrained()->onDelete('cascade');
$table->unique(['key', 'server_id', 'team_id']);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('shared_environment_variables', function (Blueprint $table) {
$table->dropUnique(['key', 'server_id', 'team_id']);
$table->dropForeign(['server_id']);
$table->dropColumn('server_id');
});
DB::statement("ALTER TABLE shared_environment_variables DROP CONSTRAINT shared_environment_variables_type_check");
DB::statement("ALTER TABLE shared_environment_variables ADD CONSTRAINT shared_environment_variables_type_check CHECK (type IN ('team', 'project', 'environment'))");
}
};

View file

@ -0,0 +1,56 @@
<?php
use App\Models\Server;
use App\Models\SharedEnvironmentVariable;
use Illuminate\Database\Migrations\Migration;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Server::query()->chunk(100, function ($servers) {
foreach ($servers as $server) {
$existingKeys = SharedEnvironmentVariable::where('type', 'server')
->where('server_id', $server->id)
->whereIn('key', ['COOLIFY_SERVER_UUID', 'COOLIFY_SERVER_NAME'])
->pluck('key')
->toArray();
if (! in_array('COOLIFY_SERVER_UUID', $existingKeys)) {
SharedEnvironmentVariable::create([
'key' => 'COOLIFY_SERVER_UUID',
'value' => $server->uuid,
'type' => 'server',
'server_id' => $server->id,
'team_id' => $server->team_id,
'is_literal' => true,
]);
}
if (! in_array('COOLIFY_SERVER_NAME', $existingKeys)) {
SharedEnvironmentVariable::create([
'key' => 'COOLIFY_SERVER_NAME',
'value' => $server->name,
'type' => 'server',
'server_id' => $server->id,
'team_id' => $server->team_id,
'is_literal' => true,
]);
}
}
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
SharedEnvironmentVariable::where('type', 'server')
->whereIn('key', ['COOLIFY_SERVER_UUID', 'COOLIFY_SERVER_NAME'])
->delete();
}
};

View file

@ -2,6 +2,7 @@
namespace Database\Seeders;
use App\Models\Server;
use App\Models\SharedEnvironmentVariable;
use Illuminate\Database\Seeder;
@ -32,5 +33,29 @@ class SharedEnvironmentVariableSeeder extends Seeder
'project_id' => 1,
'team_id' => 0,
]);
// Add predefined server variables to all existing servers
$servers = \App\Models\Server::all();
foreach ($servers as $server) {
SharedEnvironmentVariable::firstOrCreate([
'key' => 'COOLIFY_SERVER_UUID',
'type' => 'server',
'server_id' => $server->id,
'team_id' => $server->team_id,
], [
'value' => $server->uuid,
'is_literal' => true,
]);
SharedEnvironmentVariable::firstOrCreate([
'key' => 'COOLIFY_SERVER_NAME',
'type' => 'server',
'server_id' => $server->id,
'team_id' => $server->team_id,
], [
'value' => $server->name,
'is_literal' => true,
]);
}
}
}

View file

@ -17,8 +17,15 @@
selectedIndex: 0,
cursorPosition: 0,
currentScope: null,
availableScopes: ['team', 'project', 'environment'],
availableVars: @js($availableVars),
get availableScopes() {
// Only include scopes that have at least one variable
const allScopes = ['team', 'project', 'environment', 'server'];
return allScopes.filter(scope => {
const vars = this.availableVars[scope];
return vars && vars.length > 0;
});
},
scopeUrls: @js($scopeUrls),
handleInput() {
@ -54,6 +61,11 @@
if (content === '') {
this.currentScope = null;
// Only show dropdown if there are available scopes with variables
if (this.availableScopes.length === 0) {
this.showDropdown = false;
return;
}
this.suggestions = this.availableScopes.map(scope => ({
type: 'scope',
value: scope,

View file

@ -6,7 +6,8 @@
<x-forms.env-var-input placeholder="production" id="value" label="Value" required
:availableVars="$shared ? [] : $this->availableSharedVariables"
:projectUuid="data_get($parameters, 'project_uuid')"
:environmentUuid="data_get($parameters, 'environment_uuid')" />
:environmentUuid="data_get($parameters, 'environment_uuid')"
:serverUuid="data_get($parameters, 'server_uuid')" />
@endif
@if (!$shared && !$is_multiline)

View file

@ -109,9 +109,10 @@
disabled
type="password"
id="value"
:availableVars="$this->availableSharedVariables"
:availableVars="$isSharedVariable ? [] : $this->availableSharedVariables"
:projectUuid="data_get($parameters, 'project_uuid')"
:environmentUuid="data_get($parameters, 'environment_uuid')" />
:environmentUuid="data_get($parameters, 'environment_uuid')"
:serverUuid="data_get($parameters, 'server_uuid')" />
@if ($is_shared)
<x-forms.input disabled type="password" id="real_value" />
@endif
@ -127,9 +128,10 @@
:required="$is_redis_credential"
type="password"
id="value"
:availableVars="$this->availableSharedVariables"
:availableVars="$isSharedVariable ? [] : $this->availableSharedVariables"
:projectUuid="data_get($parameters, 'project_uuid')"
:environmentUuid="data_get($parameters, 'environment_uuid')" />
:environmentUuid="data_get($parameters, 'environment_uuid')"
:serverUuid="data_get($parameters, 'server_uuid')" />
@endif
@if ($is_shared)
<x-forms.input :disabled="$is_redis_credential" :required="$is_redis_credential" disabled type="password" id="real_value" />
@ -143,9 +145,10 @@
disabled
type="password"
id="value"
:availableVars="$this->availableSharedVariables"
:availableVars="$isSharedVariable ? [] : $this->availableSharedVariables"
:projectUuid="data_get($parameters, 'project_uuid')"
:environmentUuid="data_get($parameters, 'environment_uuid')" />
:environmentUuid="data_get($parameters, 'environment_uuid')"
:serverUuid="data_get($parameters, 'server_uuid')" />
@if ($is_shared)
<x-forms.input disabled type="password" id="real_value" />
@endif

View file

@ -5,27 +5,33 @@
<div class="flex items-start gap-2">
<h1>Shared Variables</h1>
</div>
<div class="subtitle">Set Team / Project / Environment wide variables.</div>
<div class="subtitle">Set Team / Project / Environment / Server wide variables.</div>
<div class="flex flex-col gap-2 -mt-1">
<a class="coolbox group" href="{{ route('shared-variables.team.index') }}" {{ wireNavigate() }}>
<div class="flex flex-col justify-center mx-6">
<div class="box-title">Team wide</div>
<div class="box-description">Usable for all resources in a team.</div>
</div>
</a>
<a class="coolbox group" href="{{ route('shared-variables.project.index') }}" {{ wireNavigate() }}>
<div class="flex flex-col justify-center mx-6">
<div class="box-title">Project wide</div>
<div class="box-description">Usable for all resources in a project.</div>
</div>
</a>
<a class="coolbox group" href="{{ route('shared-variables.environment.index') }}" {{ wireNavigate() }}>
<div class="flex flex-col justify-center mx-6">
<div class="box-title">Environment wide</div>
<div class="box-description">Usable for all resources in an environment.</div>
</div>
</a>
<div class="flex flex-col gap-2 -mt-1">
<a class="coolbox group" href="{{ route('shared-variables.team.index') }}" {{ wireNavigate() }}>
<div class="flex flex-col justify-center mx-6">
<div class="box-title">Team wide</div>
<div class="box-description">Usable for all resources in a team.</div>
</div>
</a>
<a class="coolbox group" href="{{ route('shared-variables.project.index') }}" {{ wireNavigate() }}>
<div class="flex flex-col justify-center mx-6">
<div class="box-title">Project wide</div>
<div class="box-description">Usable for all resources in a project.</div>
</div>
</a>
<a class="coolbox group" href="{{ route('shared-variables.environment.index') }}" {{ wireNavigate() }}>
<div class="flex flex-col justify-center mx-6">
<div class="box-title">Environment wide</div>
<div class="box-description">Usable for all resources in an environment.</div>
</div>
</a>
<a class="coolbox group" href="{{ route('shared-variables.server.index') }}" {{ wireNavigate() }}>
<div class="flex flex-col justify-center mx-6">
<div class="box-title">Server wide</div>
<div class="box-description">Usable for all resources in a server.</div>
</div>
</a>
</div>
</div>
</div>

View file

@ -0,0 +1,25 @@
<div>
<x-slot:title>
Server Variables | Coolify
</x-slot>
<div class="flex gap-2">
<h1>Servers</h1>
</div>
<div class="subtitle">List of your servers.</div>
<div class="flex flex-col gap-2">
@forelse ($servers as $server)
<a class="coolbox group"
href="{{ route('shared-variables.server.show', ['server_uuid' => data_get($server, 'uuid')]) }}" {{ wireNavigate() }}>
<div class="flex flex-col justify-center mx-6 ">
<div class="box-title">{{ $server->name }}</div>
<div class="box-description ">
{{ $server->description }}</div>
</div>
</a>
@empty
<div>
<div>No server found.</div>
</div>
@endforelse
</div>
</div>

View file

@ -0,0 +1,36 @@
<div>
<x-slot:title>
Server Variable | Coolify
</x-slot>
<div class="flex gap-2 items-center">
<h1>Shared Variables for {{ data_get($server, 'name') }}</h1>
@can('update', $server)
<x-modal-input buttonTitle="+ Add" title="New Shared Variable">
<livewire:project.shared.environment-variable.add :shared="true" />
</x-modal-input>
@endcan
<x-forms.button canGate="update" :canResource="$server" wire:click='switch'>{{ $view === 'normal' ? 'Developer view' : 'Normal view' }}</x-forms.button>
</div>
<div class="flex flex-wrap gap-1 subtitle">
<div>You can use these variables anywhere with</div>
<div class="dark:text-warning text-coollabs">@{{ server.VARIABLENAME }} </div>
<x-helper
helper="More info <a class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/environment-variables#shared-variables' target='_blank'>here</a>."></x-helper>
</div>
@if ($view === 'normal')
<div class="flex flex-col gap-2">
@forelse ($server->environment_variables->whereNotIn('key', ['COOLIFY_SERVER_UUID', 'COOLIFY_SERVER_NAME'])->sortBy('key') as $env)
<livewire:project.shared.environment-variable.show wire:key="environment-{{ $env->id }}"
:env="$env" type="server" />
@empty
<div>No environment variables found.</div>
@endforelse
</div>
@else
<form wire:submit='submit' class="flex flex-col gap-2">
<x-forms.textarea canGate="update" :canResource="$server" rows="20" class="whitespace-pre-wrap" id="variables" wire:model="variables"
label="Server Shared Variables"></x-forms.textarea>
<x-forms.button canGate="update" :canResource="$server" type="submit" class="btn btn-primary">Save All Environment Variables</x-forms.button>
</form>
@endif
</div>

View file

@ -72,6 +72,8 @@ use App\Livewire\SharedVariables\Environment\Show as EnvironmentSharedVariablesS
use App\Livewire\SharedVariables\Index as SharedVariablesIndex;
use App\Livewire\SharedVariables\Project\Index as ProjectSharedVariablesIndex;
use App\Livewire\SharedVariables\Project\Show as ProjectSharedVariablesShow;
use App\Livewire\SharedVariables\Server\Index as ServerSharedVariablesIndex;
use App\Livewire\SharedVariables\Server\Show as ServerSharedVariablesShow;
use App\Livewire\SharedVariables\Team\Index as TeamSharedVariablesIndex;
use App\Livewire\Source\Github\Change as GitHubChange;
use App\Livewire\Storage\Index as StorageIndex;
@ -148,6 +150,8 @@ Route::middleware(['auth', 'verified'])->group(function () {
Route::get('/project/{project_uuid}', ProjectSharedVariablesShow::class)->name('shared-variables.project.show');
Route::get('/environments', EnvironmentSharedVariablesIndex::class)->name('shared-variables.environment.index');
Route::get('/environments/project/{project_uuid}/environment/{environment_uuid}', EnvironmentSharedVariablesShow::class)->name('shared-variables.environment.show');
Route::get('/servers', ServerSharedVariablesIndex::class)->name('shared-variables.server.index');
Route::get('/server/{server_uuid}', ServerSharedVariablesShow::class)->name('shared-variables.server.show');
});
Route::prefix('team')->group(function () {