mirror of
https://github.com/tiennm99/coolify.git
synced 2026-04-17 17:21:04 +00:00
Merge branch 'next' into v4.x
This commit is contained in:
@@ -96,7 +96,11 @@ class GetContainersStatus
|
||||
}
|
||||
$containerStatus = data_get($container, 'State.Status');
|
||||
$containerHealth = data_get($container, 'State.Health.Status', 'unhealthy');
|
||||
$containerStatus = "$containerStatus ($containerHealth)";
|
||||
if ($containerStatus === 'restarting') {
|
||||
$containerStatus = "restarting ($containerHealth)";
|
||||
} else {
|
||||
$containerStatus = "$containerStatus ($containerHealth)";
|
||||
}
|
||||
$labels = Arr::undot(format_docker_labels_to_json($labels));
|
||||
$applicationId = data_get($labels, 'coolify.applicationId');
|
||||
if ($applicationId) {
|
||||
@@ -386,19 +390,33 @@ class GetContainersStatus
|
||||
return null;
|
||||
}
|
||||
|
||||
// Aggregate status: if any container is running, app is running
|
||||
$hasRunning = false;
|
||||
$hasRestarting = false;
|
||||
$hasUnhealthy = false;
|
||||
$hasExited = false;
|
||||
|
||||
foreach ($relevantStatuses as $status) {
|
||||
if (str($status)->contains('running')) {
|
||||
if (str($status)->contains('restarting')) {
|
||||
$hasRestarting = true;
|
||||
} elseif (str($status)->contains('running')) {
|
||||
$hasRunning = true;
|
||||
if (str($status)->contains('unhealthy')) {
|
||||
$hasUnhealthy = true;
|
||||
}
|
||||
} elseif (str($status)->contains('exited')) {
|
||||
$hasExited = true;
|
||||
$hasUnhealthy = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($hasRestarting) {
|
||||
return 'degraded (unhealthy)';
|
||||
}
|
||||
|
||||
if ($hasRunning && $hasExited) {
|
||||
return 'degraded (unhealthy)';
|
||||
}
|
||||
|
||||
if ($hasRunning) {
|
||||
return $hasUnhealthy ? 'running (unhealthy)' : 'running (healthy)';
|
||||
}
|
||||
|
||||
@@ -26,22 +26,22 @@ class ComplexStatusCheck
|
||||
continue;
|
||||
}
|
||||
}
|
||||
$container = instant_remote_process(["docker container inspect $(docker container ls -q --filter 'label=coolify.applicationId={$application->id}' --filter 'label=coolify.pullRequestId=0') --format '{{json .}}'"], $server, false);
|
||||
$container = format_docker_command_output_to_json($container);
|
||||
if ($container->count() === 1) {
|
||||
$container = $container->first();
|
||||
$containerStatus = data_get($container, 'State.Status');
|
||||
$containerHealth = data_get($container, 'State.Health.Status', 'unhealthy');
|
||||
$containers = instant_remote_process(["docker container inspect $(docker container ls -q --filter 'label=coolify.applicationId={$application->id}' --filter 'label=coolify.pullRequestId=0') --format '{{json .}}'"], $server, false);
|
||||
$containers = format_docker_command_output_to_json($containers);
|
||||
|
||||
if ($containers->count() > 0) {
|
||||
$statusToSet = $this->aggregateContainerStatuses($application, $containers);
|
||||
|
||||
if ($is_main_server) {
|
||||
$statusFromDb = $application->status;
|
||||
if ($statusFromDb !== $containerStatus) {
|
||||
$application->update(['status' => "$containerStatus:$containerHealth"]);
|
||||
if ($statusFromDb !== $statusToSet) {
|
||||
$application->update(['status' => $statusToSet]);
|
||||
}
|
||||
} else {
|
||||
$additional_server = $application->additional_servers()->wherePivot('server_id', $server->id);
|
||||
$statusFromDb = $additional_server->first()->pivot->status;
|
||||
if ($statusFromDb !== $containerStatus) {
|
||||
$additional_server->updateExistingPivot($server->id, ['status' => "$containerStatus:$containerHealth"]);
|
||||
if ($statusFromDb !== $statusToSet) {
|
||||
$additional_server->updateExistingPivot($server->id, ['status' => $statusToSet]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -57,4 +57,78 @@ class ComplexStatusCheck
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function aggregateContainerStatuses($application, $containers)
|
||||
{
|
||||
$dockerComposeRaw = data_get($application, 'docker_compose_raw');
|
||||
$excludedContainers = collect();
|
||||
|
||||
if ($dockerComposeRaw) {
|
||||
try {
|
||||
$dockerCompose = \Symfony\Component\Yaml\Yaml::parse($dockerComposeRaw);
|
||||
$services = data_get($dockerCompose, 'services', []);
|
||||
|
||||
foreach ($services as $serviceName => $serviceConfig) {
|
||||
$excludeFromHc = data_get($serviceConfig, 'exclude_from_hc', false);
|
||||
$restartPolicy = data_get($serviceConfig, 'restart', 'always');
|
||||
|
||||
if ($excludeFromHc || $restartPolicy === 'no') {
|
||||
$excludedContainers->push($serviceName);
|
||||
}
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
// If we can't parse, treat all containers as included
|
||||
}
|
||||
}
|
||||
|
||||
$hasRunning = false;
|
||||
$hasRestarting = false;
|
||||
$hasUnhealthy = false;
|
||||
$hasExited = false;
|
||||
$relevantContainerCount = 0;
|
||||
|
||||
foreach ($containers as $container) {
|
||||
$labels = data_get($container, 'Config.Labels', []);
|
||||
$serviceName = data_get($labels, 'com.docker.compose.service');
|
||||
|
||||
if ($serviceName && $excludedContainers->contains($serviceName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$relevantContainerCount++;
|
||||
$containerStatus = data_get($container, 'State.Status');
|
||||
$containerHealth = data_get($container, 'State.Health.Status', 'unhealthy');
|
||||
|
||||
if ($containerStatus === 'restarting') {
|
||||
$hasRestarting = true;
|
||||
$hasUnhealthy = true;
|
||||
} elseif ($containerStatus === 'running') {
|
||||
$hasRunning = true;
|
||||
if ($containerHealth === 'unhealthy') {
|
||||
$hasUnhealthy = true;
|
||||
}
|
||||
} elseif ($containerStatus === 'exited') {
|
||||
$hasExited = true;
|
||||
$hasUnhealthy = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($relevantContainerCount === 0) {
|
||||
return 'running:healthy';
|
||||
}
|
||||
|
||||
if ($hasRestarting) {
|
||||
return 'degraded:unhealthy';
|
||||
}
|
||||
|
||||
if ($hasRunning && $hasExited) {
|
||||
return 'degraded:unhealthy';
|
||||
}
|
||||
|
||||
if ($hasRunning) {
|
||||
return $hasUnhealthy ? 'running:unhealthy' : 'running:healthy';
|
||||
}
|
||||
|
||||
return 'exited:unhealthy';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2532,8 +2532,11 @@ class ApplicationsController extends Controller
|
||||
if ($env->is_shown_once != $request->is_shown_once) {
|
||||
$env->is_shown_once = $request->is_shown_once;
|
||||
}
|
||||
if ($request->has('is_buildtime_only') && $env->is_buildtime_only != $request->is_buildtime_only) {
|
||||
$env->is_buildtime_only = $request->is_buildtime_only;
|
||||
if ($request->has('is_runtime') && $env->is_runtime != $request->is_runtime) {
|
||||
$env->is_runtime = $request->is_runtime;
|
||||
}
|
||||
if ($request->has('is_buildtime') && $env->is_buildtime != $request->is_buildtime) {
|
||||
$env->is_buildtime = $request->is_buildtime;
|
||||
}
|
||||
$env->save();
|
||||
|
||||
@@ -2559,8 +2562,11 @@ class ApplicationsController extends Controller
|
||||
if ($env->is_shown_once != $request->is_shown_once) {
|
||||
$env->is_shown_once = $request->is_shown_once;
|
||||
}
|
||||
if ($request->has('is_buildtime_only') && $env->is_buildtime_only != $request->is_buildtime_only) {
|
||||
$env->is_buildtime_only = $request->is_buildtime_only;
|
||||
if ($request->has('is_runtime') && $env->is_runtime != $request->is_runtime) {
|
||||
$env->is_runtime = $request->is_runtime;
|
||||
}
|
||||
if ($request->has('is_buildtime') && $env->is_buildtime != $request->is_buildtime) {
|
||||
$env->is_buildtime = $request->is_buildtime;
|
||||
}
|
||||
$env->save();
|
||||
|
||||
@@ -2723,8 +2729,11 @@ class ApplicationsController extends Controller
|
||||
if ($env->is_shown_once != $item->get('is_shown_once')) {
|
||||
$env->is_shown_once = $item->get('is_shown_once');
|
||||
}
|
||||
if ($item->has('is_buildtime_only') && $env->is_buildtime_only != $item->get('is_buildtime_only')) {
|
||||
$env->is_buildtime_only = $item->get('is_buildtime_only');
|
||||
if ($item->has('is_runtime') && $env->is_runtime != $item->get('is_runtime')) {
|
||||
$env->is_runtime = $item->get('is_runtime');
|
||||
}
|
||||
if ($item->has('is_buildtime') && $env->is_buildtime != $item->get('is_buildtime')) {
|
||||
$env->is_buildtime = $item->get('is_buildtime');
|
||||
}
|
||||
$env->save();
|
||||
} else {
|
||||
@@ -2735,7 +2744,8 @@ class ApplicationsController extends Controller
|
||||
'is_literal' => $is_literal,
|
||||
'is_multiline' => $is_multi_line,
|
||||
'is_shown_once' => $is_shown_once,
|
||||
'is_buildtime_only' => $item->get('is_buildtime_only', false),
|
||||
'is_runtime' => $item->get('is_runtime', true),
|
||||
'is_buildtime' => $item->get('is_buildtime', true),
|
||||
'resourceable_type' => get_class($application),
|
||||
'resourceable_id' => $application->id,
|
||||
]);
|
||||
@@ -2753,8 +2763,11 @@ class ApplicationsController extends Controller
|
||||
if ($env->is_shown_once != $item->get('is_shown_once')) {
|
||||
$env->is_shown_once = $item->get('is_shown_once');
|
||||
}
|
||||
if ($item->has('is_buildtime_only') && $env->is_buildtime_only != $item->get('is_buildtime_only')) {
|
||||
$env->is_buildtime_only = $item->get('is_buildtime_only');
|
||||
if ($item->has('is_runtime') && $env->is_runtime != $item->get('is_runtime')) {
|
||||
$env->is_runtime = $item->get('is_runtime');
|
||||
}
|
||||
if ($item->has('is_buildtime') && $env->is_buildtime != $item->get('is_buildtime')) {
|
||||
$env->is_buildtime = $item->get('is_buildtime');
|
||||
}
|
||||
$env->save();
|
||||
} else {
|
||||
@@ -2765,7 +2778,8 @@ class ApplicationsController extends Controller
|
||||
'is_literal' => $is_literal,
|
||||
'is_multiline' => $is_multi_line,
|
||||
'is_shown_once' => $is_shown_once,
|
||||
'is_buildtime_only' => $item->get('is_buildtime_only', false),
|
||||
'is_runtime' => $item->get('is_runtime', true),
|
||||
'is_buildtime' => $item->get('is_buildtime', true),
|
||||
'resourceable_type' => get_class($application),
|
||||
'resourceable_id' => $application->id,
|
||||
]);
|
||||
@@ -2904,7 +2918,8 @@ class ApplicationsController extends Controller
|
||||
'is_literal' => $request->is_literal ?? false,
|
||||
'is_multiline' => $request->is_multiline ?? false,
|
||||
'is_shown_once' => $request->is_shown_once ?? false,
|
||||
'is_buildtime_only' => $request->is_buildtime_only ?? false,
|
||||
'is_runtime' => $request->is_runtime ?? true,
|
||||
'is_buildtime' => $request->is_buildtime ?? true,
|
||||
'resourceable_type' => get_class($application),
|
||||
'resourceable_id' => $application->id,
|
||||
]);
|
||||
@@ -2927,7 +2942,8 @@ class ApplicationsController extends Controller
|
||||
'is_literal' => $request->is_literal ?? false,
|
||||
'is_multiline' => $request->is_multiline ?? false,
|
||||
'is_shown_once' => $request->is_shown_once ?? false,
|
||||
'is_buildtime_only' => $request->is_buildtime_only ?? false,
|
||||
'is_runtime' => $request->is_runtime ?? true,
|
||||
'is_buildtime' => $request->is_buildtime ?? true,
|
||||
'resourceable_type' => get_class($application),
|
||||
'resourceable_id' => $application->id,
|
||||
]);
|
||||
|
||||
@@ -169,6 +169,8 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
||||
|
||||
private bool $dockerBuildkitSupported = false;
|
||||
|
||||
private bool $skip_build = false;
|
||||
|
||||
private Collection|string $build_secrets;
|
||||
|
||||
public function tags()
|
||||
@@ -566,7 +568,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
||||
if ($this->application->settings->is_raw_compose_deployment_enabled) {
|
||||
$this->application->oldRawParser();
|
||||
$yaml = $composeFile = $this->application->docker_compose_raw;
|
||||
$this->save_environment_variables();
|
||||
$this->generate_runtime_environment_variables();
|
||||
|
||||
// For raw compose, we cannot automatically add secrets configuration
|
||||
// User must define it manually in their docker-compose file
|
||||
@@ -575,7 +577,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
||||
}
|
||||
} else {
|
||||
$composeFile = $this->application->parse(pull_request_id: $this->pull_request_id, preview_id: data_get($this->preview, 'id'));
|
||||
$this->save_environment_variables();
|
||||
$this->generate_runtime_environment_variables();
|
||||
if (filled($this->env_filename)) {
|
||||
$services = collect(data_get($composeFile, 'services', []));
|
||||
$services = $services->map(function ($service, $name) {
|
||||
@@ -604,6 +606,9 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
||||
executeInDocker($this->deployment_uuid, "echo '{$this->docker_compose_base64}' | base64 -d | tee {$this->workdir}{$this->docker_compose_location} > /dev/null"),
|
||||
'hidden' => true,
|
||||
]);
|
||||
|
||||
// Modify Dockerfiles for ARGs and build secrets
|
||||
$this->modify_dockerfiles_for_compose($composeFile);
|
||||
// Build new container to limit downtime.
|
||||
$this->application_deployment_queue->addLogEntry('Pulling & building required images.');
|
||||
|
||||
@@ -630,6 +635,13 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
||||
} else {
|
||||
$command .= " --project-name {$this->application->uuid} --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} build --pull";
|
||||
}
|
||||
|
||||
if (! $this->application->settings->use_build_secrets && $this->build_args instanceof \Illuminate\Support\Collection && $this->build_args->isNotEmpty()) {
|
||||
$build_args_string = $this->build_args->implode(' ');
|
||||
$command .= " {$build_args_string}";
|
||||
$this->application_deployment_queue->addLogEntry('Adding build arguments to Docker Compose build command.');
|
||||
}
|
||||
|
||||
$this->execute_remote_command(
|
||||
[executeInDocker($this->deployment_uuid, $command), 'hidden' => true],
|
||||
);
|
||||
@@ -759,6 +771,10 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
||||
$this->generate_compose_file();
|
||||
$this->generate_build_env_variables();
|
||||
$this->build_image();
|
||||
|
||||
// For Nixpacks, save runtime environment variables AFTER the build
|
||||
// to prevent them from being accessible during the build process
|
||||
$this->save_runtime_environment_variables();
|
||||
$this->push_to_docker_registry();
|
||||
$this->rolling_update();
|
||||
}
|
||||
@@ -952,18 +968,17 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
||||
{
|
||||
if (str($this->saved_outputs->get('local_image_found'))->isNotEmpty()) {
|
||||
if ($this->is_this_additional_server) {
|
||||
$this->skip_build = true;
|
||||
$this->application_deployment_queue->addLogEntry("Image found ({$this->production_image_name}) with the same Git Commit SHA. Build step skipped.");
|
||||
$this->generate_compose_file();
|
||||
$this->push_to_docker_registry();
|
||||
$this->rolling_update();
|
||||
if ($this->restart_only) {
|
||||
$this->post_deployment();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
if (! $this->application->isConfigurationChanged()) {
|
||||
$this->application_deployment_queue->addLogEntry("No configuration changed & image found ({$this->production_image_name}) with the same Git Commit SHA. Build step skipped.");
|
||||
$this->skip_build = true;
|
||||
$this->generate_compose_file();
|
||||
$this->push_to_docker_registry();
|
||||
$this->rolling_update();
|
||||
@@ -1004,7 +1019,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
||||
}
|
||||
}
|
||||
|
||||
private function save_environment_variables()
|
||||
private function generate_runtime_environment_variables()
|
||||
{
|
||||
$envs = collect([]);
|
||||
$sort = $this->application->settings->is_env_sorting_enabled;
|
||||
@@ -1061,9 +1076,9 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
||||
}
|
||||
}
|
||||
|
||||
// Filter out buildtime-only variables from runtime environment
|
||||
// Filter runtime variables (only include variables that are available at runtime)
|
||||
$runtime_environment_variables = $sorted_environment_variables->filter(function ($env) {
|
||||
return ! $env->is_buildtime_only;
|
||||
return $env->is_runtime;
|
||||
});
|
||||
|
||||
// Sort runtime environment variables: those referencing SERVICE_ variables come after others
|
||||
@@ -1117,9 +1132,9 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
||||
}
|
||||
}
|
||||
|
||||
// Filter out buildtime-only variables from runtime environment for preview
|
||||
// Filter runtime variables for preview (only include variables that are available at runtime)
|
||||
$runtime_environment_variables_preview = $sorted_environment_variables_preview->filter(function ($env) {
|
||||
return ! $env->is_buildtime_only;
|
||||
return $env->is_runtime;
|
||||
});
|
||||
|
||||
// Sort runtime environment variables: those referencing SERVICE_ variables come after others
|
||||
@@ -1176,13 +1191,53 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
||||
}
|
||||
$this->env_filename = null;
|
||||
} else {
|
||||
$envs_base64 = base64_encode($envs->implode("\n"));
|
||||
// For Nixpacks builds, we save the .env file AFTER the build to prevent
|
||||
// runtime-only variables from being accessible during the build process
|
||||
if ($this->application->build_pack !== 'nixpacks' || $this->skip_build) {
|
||||
$envs_base64 = base64_encode($envs->implode("\n"));
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
executeInDocker($this->deployment_uuid, "echo '$envs_base64' | base64 -d | tee $this->workdir/{$this->env_filename} > /dev/null"),
|
||||
],
|
||||
|
||||
);
|
||||
if ($this->use_build_server) {
|
||||
$this->server = $this->original_server;
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
"echo '$envs_base64' | base64 -d | tee $this->configuration_dir/{$this->env_filename} > /dev/null",
|
||||
]
|
||||
);
|
||||
$this->server = $this->build_server;
|
||||
} else {
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
"echo '$envs_base64' | base64 -d | tee $this->configuration_dir/{$this->env_filename} > /dev/null",
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->environment_variables = $envs;
|
||||
}
|
||||
|
||||
private function save_runtime_environment_variables()
|
||||
{
|
||||
// This method saves the .env file with runtime variables
|
||||
// It should be called AFTER the build for Nixpacks to prevent runtime-only variables
|
||||
// from being accessible during the build process
|
||||
|
||||
if ($this->environment_variables && $this->environment_variables->isNotEmpty() && $this->env_filename) {
|
||||
$envs_base64 = base64_encode($this->environment_variables->implode("\n"));
|
||||
|
||||
// Write .env file to workdir (for container runtime)
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
executeInDocker($this->deployment_uuid, "echo '$envs_base64' | base64 -d | tee $this->workdir/{$this->env_filename} > /dev/null"),
|
||||
],
|
||||
|
||||
);
|
||||
|
||||
// Write .env file to configuration directory
|
||||
if ($this->use_build_server) {
|
||||
$this->server = $this->original_server;
|
||||
$this->execute_remote_command(
|
||||
@@ -1199,7 +1254,6 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
||||
);
|
||||
}
|
||||
}
|
||||
$this->environment_variables = $envs;
|
||||
}
|
||||
|
||||
private function elixir_finetunes()
|
||||
@@ -1418,6 +1472,10 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
||||
$this->add_build_env_variables_to_dockerfile();
|
||||
}
|
||||
$this->build_image();
|
||||
// For Nixpacks, save runtime environment variables AFTER the build
|
||||
if ($this->application->build_pack === 'nixpacks') {
|
||||
$this->save_runtime_environment_variables();
|
||||
}
|
||||
$this->push_to_docker_registry();
|
||||
$this->rolling_update();
|
||||
}
|
||||
@@ -1681,6 +1739,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
||||
{
|
||||
$nixpacks_command = $this->nixpacks_build_cmd();
|
||||
$this->application_deployment_queue->addLogEntry("Generating nixpacks configuration with: $nixpacks_command");
|
||||
|
||||
$this->execute_remote_command(
|
||||
[executeInDocker($this->deployment_uuid, $nixpacks_command), 'save' => 'nixpacks_plan', 'hidden' => true],
|
||||
[executeInDocker($this->deployment_uuid, "nixpacks detect {$this->workdir}"), 'save' => 'nixpacks_type', 'hidden' => true],
|
||||
@@ -1700,6 +1759,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
||||
$parsed = Toml::Parse($this->nixpacks_plan);
|
||||
|
||||
// Do any modifications here
|
||||
// We need to generate envs here because nixpacks need to know to generate a proper Dockerfile
|
||||
$this->generate_env_variables();
|
||||
$merged_envs = collect(data_get($parsed, 'variables', []))->merge($this->env_args);
|
||||
$aptPkgs = data_get($parsed, 'phases.setup.aptPkgs', []);
|
||||
@@ -1872,13 +1932,13 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
||||
$this->env_args->put('SOURCE_COMMIT', $this->commit);
|
||||
$coolify_envs = $this->generate_coolify_env_variables();
|
||||
|
||||
// Include ALL environment variables (both build-time and runtime) for all build packs
|
||||
// This deprecates the need for is_build_time flag
|
||||
// For build process, include only environment variables where is_buildtime = true
|
||||
if ($this->pull_request_id === 0) {
|
||||
// Get all environment variables except NIXPACKS_ prefixed ones for non-nixpacks builds
|
||||
$envs = $this->application->build_pack === 'nixpacks'
|
||||
? $this->application->runtime_environment_variables
|
||||
: $this->application->environment_variables()->where('key', 'not like', 'NIXPACKS_%')->get();
|
||||
// Get environment variables that are marked as available during build
|
||||
$envs = $this->application->environment_variables()
|
||||
->where('key', 'not like', 'NIXPACKS_%')
|
||||
->where('is_buildtime', true)
|
||||
->get();
|
||||
|
||||
foreach ($envs as $env) {
|
||||
if (! is_null($env->real_value)) {
|
||||
@@ -1900,10 +1960,11 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Get all preview environment variables except NIXPACKS_ prefixed ones for non-nixpacks builds
|
||||
$envs = $this->application->build_pack === 'nixpacks'
|
||||
? $this->application->runtime_environment_variables_preview
|
||||
: $this->application->environment_variables_preview()->where('key', 'not like', 'NIXPACKS_%')->get();
|
||||
// Get preview environment variables that are marked as available during build
|
||||
$envs = $this->application->environment_variables_preview()
|
||||
->where('key', 'not like', 'NIXPACKS_%')
|
||||
->where('is_buildtime', true)
|
||||
->get();
|
||||
|
||||
foreach ($envs as $env) {
|
||||
if (! is_null($env->real_value)) {
|
||||
@@ -1935,8 +1996,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
||||
$persistent_storages = $this->generate_local_persistent_volumes();
|
||||
$persistent_file_volumes = $this->application->fileStorages()->get();
|
||||
$volume_names = $this->generate_local_persistent_volumes_only_volume_names();
|
||||
// $environment_variables = $this->generate_environment_variables($ports);
|
||||
$this->save_environment_variables();
|
||||
$this->generate_runtime_environment_variables();
|
||||
if (data_get($this->application, 'custom_labels')) {
|
||||
$this->application->parseContainerLabels();
|
||||
$labels = collect(preg_split("/\r\n|\n|\r/", base64_decode($this->application->custom_labels)));
|
||||
@@ -2652,6 +2712,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
||||
if ($this->application->build_pack === 'nixpacks') {
|
||||
$variables = collect($this->nixpacks_plan_json->get('variables'));
|
||||
} else {
|
||||
// Generate environment variables for build process (filters by is_buildtime = true)
|
||||
$this->generate_env_variables();
|
||||
$variables = collect([])->merge($this->env_args);
|
||||
}
|
||||
@@ -2678,8 +2739,8 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
||||
}
|
||||
|
||||
$variables = $this->pull_request_id === 0
|
||||
? $this->application->environment_variables()->where('key', 'not like', 'NIXPACKS_%')->get()
|
||||
: $this->application->environment_variables_preview()->where('key', 'not like', 'NIXPACKS_%')->get();
|
||||
? $this->application->environment_variables()->where('key', 'not like', 'NIXPACKS_%')->where('is_buildtime', true)->get()
|
||||
: $this->application->environment_variables_preview()->where('key', 'not like', 'NIXPACKS_%')->where('is_buildtime', true)->get();
|
||||
|
||||
if ($variables->isEmpty()) {
|
||||
return '';
|
||||
@@ -2722,7 +2783,11 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
||||
$dockerfile = collect(str($this->saved_outputs->get('dockerfile'))->trim()->explode("\n"));
|
||||
|
||||
if ($this->pull_request_id === 0) {
|
||||
$envs = $this->application->environment_variables()->where('key', 'not like', 'NIXPACKS_%')->get();
|
||||
// Only add environment variables that are available during build
|
||||
$envs = $this->application->environment_variables()
|
||||
->where('key', 'not like', 'NIXPACKS_%')
|
||||
->where('is_buildtime', true)
|
||||
->get();
|
||||
foreach ($envs as $env) {
|
||||
if (data_get($env, 'is_multiline') === true) {
|
||||
$dockerfile->splice(1, 0, ["ARG {$env->key}"]);
|
||||
@@ -2731,8 +2796,11 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Get all preview environment variables except NIXPACKS_ prefixed ones
|
||||
$envs = $this->application->environment_variables_preview()->where('key', 'not like', 'NIXPACKS_%')->get();
|
||||
// Only add preview environment variables that are available during build
|
||||
$envs = $this->application->environment_variables_preview()
|
||||
->where('key', 'not like', 'NIXPACKS_%')
|
||||
->where('is_buildtime', true)
|
||||
->get();
|
||||
foreach ($envs as $env) {
|
||||
if (data_get($env, 'is_multiline') === true) {
|
||||
$dockerfile->splice(1, 0, ["ARG {$env->key}"]);
|
||||
@@ -2772,8 +2840,8 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
||||
|
||||
// Get environment variables for secrets
|
||||
$variables = $this->pull_request_id === 0
|
||||
? $this->application->environment_variables()->where('key', 'not like', 'NIXPACKS_%')->get()
|
||||
: $this->application->environment_variables_preview()->where('key', 'not like', 'NIXPACKS_%')->get();
|
||||
? $this->application->environment_variables()->where('key', 'not like', 'NIXPACKS_%')->where('is_buildtime', true)->get()
|
||||
: $this->application->environment_variables_preview()->where('key', 'not like', 'NIXPACKS_%')->where('is_buildtime', true)->get();
|
||||
|
||||
if ($variables->isEmpty()) {
|
||||
return;
|
||||
@@ -2810,6 +2878,164 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
||||
}
|
||||
}
|
||||
|
||||
private function modify_dockerfiles_for_compose($composeFile)
|
||||
{
|
||||
if ($this->application->build_pack !== 'dockercompose') {
|
||||
return;
|
||||
}
|
||||
|
||||
$variables = $this->pull_request_id === 0
|
||||
? $this->application->environment_variables()
|
||||
->where('key', 'not like', 'NIXPACKS_%')
|
||||
->where('is_buildtime', true)
|
||||
->get()
|
||||
: $this->application->environment_variables_preview()
|
||||
->where('key', 'not like', 'NIXPACKS_%')
|
||||
->where('is_buildtime', true)
|
||||
->get();
|
||||
|
||||
if ($variables->isEmpty()) {
|
||||
$this->application_deployment_queue->addLogEntry('No build-time variables to add to Dockerfiles.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$services = data_get($composeFile, 'services', []);
|
||||
|
||||
foreach ($services as $serviceName => $service) {
|
||||
if (! isset($service['build'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$context = '.';
|
||||
$dockerfile = 'Dockerfile';
|
||||
|
||||
if (is_string($service['build'])) {
|
||||
$context = $service['build'];
|
||||
} elseif (is_array($service['build'])) {
|
||||
$context = data_get($service['build'], 'context', '.');
|
||||
$dockerfile = data_get($service['build'], 'dockerfile', 'Dockerfile');
|
||||
}
|
||||
|
||||
$dockerfilePath = rtrim($context, '/').'/'.ltrim($dockerfile, '/');
|
||||
if (str_starts_with($dockerfilePath, './')) {
|
||||
$dockerfilePath = substr($dockerfilePath, 2);
|
||||
}
|
||||
if (str_starts_with($dockerfilePath, '/')) {
|
||||
$dockerfilePath = substr($dockerfilePath, 1);
|
||||
}
|
||||
|
||||
$this->execute_remote_command([
|
||||
executeInDocker($this->deployment_uuid, "test -f {$this->workdir}/{$dockerfilePath} && echo 'exists' || echo 'not found'"),
|
||||
'hidden' => true,
|
||||
'save' => 'dockerfile_check_'.$serviceName,
|
||||
]);
|
||||
|
||||
if (str($this->saved_outputs->get('dockerfile_check_'.$serviceName))->trim()->toString() !== 'exists') {
|
||||
$this->application_deployment_queue->addLogEntry("Dockerfile not found for service {$serviceName} at {$dockerfilePath}, skipping ARG injection.");
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->execute_remote_command([
|
||||
executeInDocker($this->deployment_uuid, "cat {$this->workdir}/{$dockerfilePath}"),
|
||||
'hidden' => true,
|
||||
'save' => 'dockerfile_content_'.$serviceName,
|
||||
]);
|
||||
|
||||
$dockerfileContent = $this->saved_outputs->get('dockerfile_content_'.$serviceName);
|
||||
if (! $dockerfileContent) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$dockerfile_lines = collect(str($dockerfileContent)->trim()->explode("\n"));
|
||||
|
||||
$fromIndices = [];
|
||||
$dockerfile_lines->each(function ($line, $index) use (&$fromIndices) {
|
||||
if (str($line)->trim()->startsWith('FROM')) {
|
||||
$fromIndices[] = $index;
|
||||
}
|
||||
});
|
||||
|
||||
if (empty($fromIndices)) {
|
||||
$this->application_deployment_queue->addLogEntry("No FROM instruction found in Dockerfile for service {$serviceName}, skipping.");
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$isMultiStage = count($fromIndices) > 1;
|
||||
|
||||
$argsToAdd = collect([]);
|
||||
foreach ($variables as $env) {
|
||||
$argsToAdd->push("ARG {$env->key}");
|
||||
}
|
||||
|
||||
ray($argsToAdd);
|
||||
if ($argsToAdd->isEmpty()) {
|
||||
$this->application_deployment_queue->addLogEntry("Service {$serviceName}: No build-time variables to add.");
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$totalAdded = 0;
|
||||
$offset = 0;
|
||||
|
||||
foreach ($fromIndices as $stageIndex => $fromIndex) {
|
||||
$adjustedIndex = $fromIndex + $offset;
|
||||
|
||||
$stageStart = $adjustedIndex + 1;
|
||||
$stageEnd = isset($fromIndices[$stageIndex + 1])
|
||||
? $fromIndices[$stageIndex + 1] + $offset
|
||||
: $dockerfile_lines->count();
|
||||
|
||||
$existingStageArgs = collect([]);
|
||||
for ($i = $stageStart; $i < $stageEnd; $i++) {
|
||||
$line = $dockerfile_lines->get($i);
|
||||
if (! $line || ! str($line)->trim()->startsWith('ARG')) {
|
||||
break;
|
||||
}
|
||||
$parts = explode(' ', trim($line), 2);
|
||||
if (count($parts) >= 2) {
|
||||
$argPart = $parts[1];
|
||||
$keyValue = explode('=', $argPart, 2);
|
||||
$existingStageArgs->push($keyValue[0]);
|
||||
}
|
||||
}
|
||||
|
||||
$stageArgsToAdd = $argsToAdd->filter(function ($arg) use ($existingStageArgs) {
|
||||
$key = str($arg)->after('ARG ')->trim()->toString();
|
||||
|
||||
return ! $existingStageArgs->contains($key);
|
||||
});
|
||||
|
||||
if ($stageArgsToAdd->isNotEmpty()) {
|
||||
$dockerfile_lines->splice($adjustedIndex + 1, 0, $stageArgsToAdd->toArray());
|
||||
$totalAdded += $stageArgsToAdd->count();
|
||||
$offset += $stageArgsToAdd->count();
|
||||
}
|
||||
}
|
||||
|
||||
if ($totalAdded > 0) {
|
||||
$dockerfile_base64 = base64_encode($dockerfile_lines->implode("\n"));
|
||||
$this->execute_remote_command([
|
||||
executeInDocker($this->deployment_uuid, "echo '{$dockerfile_base64}' | base64 -d | tee {$this->workdir}/{$dockerfilePath} > /dev/null"),
|
||||
'hidden' => true,
|
||||
]);
|
||||
|
||||
$stageInfo = $isMultiStage ? ' (multi-stage build, added to '.count($fromIndices).' stages)' : '';
|
||||
$this->application_deployment_queue->addLogEntry("Added {$totalAdded} ARG declarations to Dockerfile for service {$serviceName}{$stageInfo}.");
|
||||
} else {
|
||||
$this->application_deployment_queue->addLogEntry("Service {$serviceName}: All required ARG declarations already exist.");
|
||||
}
|
||||
|
||||
if ($this->application->settings->use_build_secrets && $this->dockerBuildkitSupported && ! empty($this->build_secrets)) {
|
||||
$fullDockerfilePath = "{$this->workdir}/{$dockerfilePath}";
|
||||
$this->modify_dockerfile_for_secrets($fullDockerfilePath);
|
||||
$this->application_deployment_queue->addLogEntry("Modified Dockerfile for service {$serviceName} to use build secrets.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function add_build_secrets_to_compose($composeFile)
|
||||
{
|
||||
// Get environment variables for secrets
|
||||
|
||||
372
app/Livewire/GlobalSearch.php
Normal file
372
app/Livewire/GlobalSearch.php
Normal file
@@ -0,0 +1,372 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire;
|
||||
|
||||
use App\Models\Application;
|
||||
use App\Models\Server;
|
||||
use App\Models\Service;
|
||||
use App\Models\StandaloneClickhouse;
|
||||
use App\Models\StandaloneDragonfly;
|
||||
use App\Models\StandaloneKeydb;
|
||||
use App\Models\StandaloneMariadb;
|
||||
use App\Models\StandaloneMongodb;
|
||||
use App\Models\StandaloneMysql;
|
||||
use App\Models\StandalonePostgresql;
|
||||
use App\Models\StandaloneRedis;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Livewire\Component;
|
||||
|
||||
class GlobalSearch extends Component
|
||||
{
|
||||
public $searchQuery = '';
|
||||
|
||||
public $isModalOpen = false;
|
||||
|
||||
public $searchResults = [];
|
||||
|
||||
public $allSearchableItems = [];
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->searchQuery = '';
|
||||
$this->isModalOpen = false;
|
||||
$this->searchResults = [];
|
||||
$this->allSearchableItems = [];
|
||||
}
|
||||
|
||||
public function openSearchModal()
|
||||
{
|
||||
$this->isModalOpen = true;
|
||||
$this->loadSearchableItems();
|
||||
$this->dispatch('search-modal-opened');
|
||||
}
|
||||
|
||||
public function closeSearchModal()
|
||||
{
|
||||
$this->isModalOpen = false;
|
||||
$this->searchQuery = '';
|
||||
$this->searchResults = [];
|
||||
}
|
||||
|
||||
public static function getCacheKey($teamId)
|
||||
{
|
||||
return 'global_search_items_'.$teamId;
|
||||
}
|
||||
|
||||
public static function clearTeamCache($teamId)
|
||||
{
|
||||
Cache::forget(self::getCacheKey($teamId));
|
||||
}
|
||||
|
||||
public function updatedSearchQuery()
|
||||
{
|
||||
$this->search();
|
||||
}
|
||||
|
||||
private function loadSearchableItems()
|
||||
{
|
||||
// Try to get from Redis cache first
|
||||
$cacheKey = self::getCacheKey(auth()->user()->currentTeam()->id);
|
||||
|
||||
$this->allSearchableItems = Cache::remember($cacheKey, 300, function () {
|
||||
ray()->showQueries();
|
||||
$items = collect();
|
||||
$team = auth()->user()->currentTeam();
|
||||
|
||||
// Get all applications
|
||||
$applications = Application::ownedByCurrentTeam()
|
||||
->with(['environment.project'])
|
||||
->get()
|
||||
->map(function ($app) {
|
||||
// Collect all FQDNs from the application
|
||||
$fqdns = collect([]);
|
||||
|
||||
// For regular applications
|
||||
if ($app->fqdn) {
|
||||
$fqdns = collect(explode(',', $app->fqdn))->map(fn ($fqdn) => trim($fqdn));
|
||||
}
|
||||
|
||||
// For docker compose based applications
|
||||
if ($app->build_pack === 'dockercompose' && $app->docker_compose_domains) {
|
||||
try {
|
||||
$composeDomains = json_decode($app->docker_compose_domains, true);
|
||||
if (is_array($composeDomains)) {
|
||||
foreach ($composeDomains as $serviceName => $domains) {
|
||||
if (is_array($domains)) {
|
||||
$fqdns = $fqdns->merge($domains);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
// Ignore JSON parsing errors
|
||||
}
|
||||
}
|
||||
|
||||
$fqdnsString = $fqdns->implode(' ');
|
||||
|
||||
return [
|
||||
'id' => $app->id,
|
||||
'name' => $app->name,
|
||||
'type' => 'application',
|
||||
'uuid' => $app->uuid,
|
||||
'description' => $app->description,
|
||||
'link' => $app->link(),
|
||||
'project' => $app->environment->project->name ?? null,
|
||||
'environment' => $app->environment->name ?? null,
|
||||
'fqdns' => $fqdns->take(2)->implode(', '), // Show first 2 FQDNs in UI
|
||||
'search_text' => strtolower($app->name.' '.$app->description.' '.$fqdnsString),
|
||||
];
|
||||
});
|
||||
|
||||
// Get all services
|
||||
$services = Service::ownedByCurrentTeam()
|
||||
->with(['environment.project', 'applications'])
|
||||
->get()
|
||||
->map(function ($service) {
|
||||
// Collect all FQDNs from service applications
|
||||
$fqdns = collect([]);
|
||||
foreach ($service->applications as $app) {
|
||||
if ($app->fqdn) {
|
||||
$appFqdns = collect(explode(',', $app->fqdn))->map(fn ($fqdn) => trim($fqdn));
|
||||
$fqdns = $fqdns->merge($appFqdns);
|
||||
}
|
||||
}
|
||||
$fqdnsString = $fqdns->implode(' ');
|
||||
|
||||
return [
|
||||
'id' => $service->id,
|
||||
'name' => $service->name,
|
||||
'type' => 'service',
|
||||
'uuid' => $service->uuid,
|
||||
'description' => $service->description,
|
||||
'link' => $service->link(),
|
||||
'project' => $service->environment->project->name ?? null,
|
||||
'environment' => $service->environment->name ?? null,
|
||||
'fqdns' => $fqdns->take(2)->implode(', '), // Show first 2 FQDNs in UI
|
||||
'search_text' => strtolower($service->name.' '.$service->description.' '.$fqdnsString),
|
||||
];
|
||||
});
|
||||
|
||||
// Get all standalone databases
|
||||
$databases = collect();
|
||||
|
||||
// PostgreSQL
|
||||
$databases = $databases->merge(
|
||||
StandalonePostgresql::ownedByCurrentTeam()
|
||||
->with(['environment.project'])
|
||||
->get()
|
||||
->map(function ($db) {
|
||||
return [
|
||||
'id' => $db->id,
|
||||
'name' => $db->name,
|
||||
'type' => 'database',
|
||||
'subtype' => 'postgresql',
|
||||
'uuid' => $db->uuid,
|
||||
'description' => $db->description,
|
||||
'link' => $db->link(),
|
||||
'project' => $db->environment->project->name ?? null,
|
||||
'environment' => $db->environment->name ?? null,
|
||||
'search_text' => strtolower($db->name.' postgresql '.$db->description),
|
||||
];
|
||||
})
|
||||
);
|
||||
|
||||
// MySQL
|
||||
$databases = $databases->merge(
|
||||
StandaloneMysql::ownedByCurrentTeam()
|
||||
->with(['environment.project'])
|
||||
->get()
|
||||
->map(function ($db) {
|
||||
return [
|
||||
'id' => $db->id,
|
||||
'name' => $db->name,
|
||||
'type' => 'database',
|
||||
'subtype' => 'mysql',
|
||||
'uuid' => $db->uuid,
|
||||
'description' => $db->description,
|
||||
'link' => $db->link(),
|
||||
'project' => $db->environment->project->name ?? null,
|
||||
'environment' => $db->environment->name ?? null,
|
||||
'search_text' => strtolower($db->name.' mysql '.$db->description),
|
||||
];
|
||||
})
|
||||
);
|
||||
|
||||
// MariaDB
|
||||
$databases = $databases->merge(
|
||||
StandaloneMariadb::ownedByCurrentTeam()
|
||||
->with(['environment.project'])
|
||||
->get()
|
||||
->map(function ($db) {
|
||||
return [
|
||||
'id' => $db->id,
|
||||
'name' => $db->name,
|
||||
'type' => 'database',
|
||||
'subtype' => 'mariadb',
|
||||
'uuid' => $db->uuid,
|
||||
'description' => $db->description,
|
||||
'link' => $db->link(),
|
||||
'project' => $db->environment->project->name ?? null,
|
||||
'environment' => $db->environment->name ?? null,
|
||||
'search_text' => strtolower($db->name.' mariadb '.$db->description),
|
||||
];
|
||||
})
|
||||
);
|
||||
|
||||
// MongoDB
|
||||
$databases = $databases->merge(
|
||||
StandaloneMongodb::ownedByCurrentTeam()
|
||||
->with(['environment.project'])
|
||||
->get()
|
||||
->map(function ($db) {
|
||||
return [
|
||||
'id' => $db->id,
|
||||
'name' => $db->name,
|
||||
'type' => 'database',
|
||||
'subtype' => 'mongodb',
|
||||
'uuid' => $db->uuid,
|
||||
'description' => $db->description,
|
||||
'link' => $db->link(),
|
||||
'project' => $db->environment->project->name ?? null,
|
||||
'environment' => $db->environment->name ?? null,
|
||||
'search_text' => strtolower($db->name.' mongodb '.$db->description),
|
||||
];
|
||||
})
|
||||
);
|
||||
|
||||
// Redis
|
||||
$databases = $databases->merge(
|
||||
StandaloneRedis::ownedByCurrentTeam()
|
||||
->with(['environment.project'])
|
||||
->get()
|
||||
->map(function ($db) {
|
||||
return [
|
||||
'id' => $db->id,
|
||||
'name' => $db->name,
|
||||
'type' => 'database',
|
||||
'subtype' => 'redis',
|
||||
'uuid' => $db->uuid,
|
||||
'description' => $db->description,
|
||||
'link' => $db->link(),
|
||||
'project' => $db->environment->project->name ?? null,
|
||||
'environment' => $db->environment->name ?? null,
|
||||
'search_text' => strtolower($db->name.' redis '.$db->description),
|
||||
];
|
||||
})
|
||||
);
|
||||
|
||||
// KeyDB
|
||||
$databases = $databases->merge(
|
||||
StandaloneKeydb::ownedByCurrentTeam()
|
||||
->with(['environment.project'])
|
||||
->get()
|
||||
->map(function ($db) {
|
||||
return [
|
||||
'id' => $db->id,
|
||||
'name' => $db->name,
|
||||
'type' => 'database',
|
||||
'subtype' => 'keydb',
|
||||
'uuid' => $db->uuid,
|
||||
'description' => $db->description,
|
||||
'link' => $db->link(),
|
||||
'project' => $db->environment->project->name ?? null,
|
||||
'environment' => $db->environment->name ?? null,
|
||||
'search_text' => strtolower($db->name.' keydb '.$db->description),
|
||||
];
|
||||
})
|
||||
);
|
||||
|
||||
// Dragonfly
|
||||
$databases = $databases->merge(
|
||||
StandaloneDragonfly::ownedByCurrentTeam()
|
||||
->with(['environment.project'])
|
||||
->get()
|
||||
->map(function ($db) {
|
||||
return [
|
||||
'id' => $db->id,
|
||||
'name' => $db->name,
|
||||
'type' => 'database',
|
||||
'subtype' => 'dragonfly',
|
||||
'uuid' => $db->uuid,
|
||||
'description' => $db->description,
|
||||
'link' => $db->link(),
|
||||
'project' => $db->environment->project->name ?? null,
|
||||
'environment' => $db->environment->name ?? null,
|
||||
'search_text' => strtolower($db->name.' dragonfly '.$db->description),
|
||||
];
|
||||
})
|
||||
);
|
||||
|
||||
// Clickhouse
|
||||
$databases = $databases->merge(
|
||||
StandaloneClickhouse::ownedByCurrentTeam()
|
||||
->with(['environment.project'])
|
||||
->get()
|
||||
->map(function ($db) {
|
||||
return [
|
||||
'id' => $db->id,
|
||||
'name' => $db->name,
|
||||
'type' => 'database',
|
||||
'subtype' => 'clickhouse',
|
||||
'uuid' => $db->uuid,
|
||||
'description' => $db->description,
|
||||
'link' => $db->link(),
|
||||
'project' => $db->environment->project->name ?? null,
|
||||
'environment' => $db->environment->name ?? null,
|
||||
'search_text' => strtolower($db->name.' clickhouse '.$db->description),
|
||||
];
|
||||
})
|
||||
);
|
||||
|
||||
// Get all servers
|
||||
$servers = Server::ownedByCurrentTeam()
|
||||
->get()
|
||||
->map(function ($server) {
|
||||
return [
|
||||
'id' => $server->id,
|
||||
'name' => $server->name,
|
||||
'type' => 'server',
|
||||
'uuid' => $server->uuid,
|
||||
'description' => $server->description,
|
||||
'link' => $server->url(),
|
||||
'project' => null,
|
||||
'environment' => null,
|
||||
'search_text' => strtolower($server->name.' '.$server->ip.' '.$server->description),
|
||||
];
|
||||
});
|
||||
|
||||
// Merge all collections
|
||||
$items = $items->merge($applications)
|
||||
->merge($services)
|
||||
->merge($databases)
|
||||
->merge($servers);
|
||||
|
||||
return $items->toArray();
|
||||
});
|
||||
}
|
||||
|
||||
private function search()
|
||||
{
|
||||
if (strlen($this->searchQuery) < 2) {
|
||||
$this->searchResults = [];
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$query = strtolower($this->searchQuery);
|
||||
|
||||
// Case-insensitive search in the items
|
||||
$this->searchResults = collect($this->allSearchableItems)
|
||||
->filter(function ($item) use ($query) {
|
||||
return str_contains($item['search_text'], $query);
|
||||
})
|
||||
->take(20)
|
||||
->values()
|
||||
->toArray();
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.global-search');
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,9 @@ class Add extends Component
|
||||
|
||||
public bool $is_literal = false;
|
||||
|
||||
public bool $is_buildtime_only = false;
|
||||
public bool $is_runtime = true;
|
||||
|
||||
public bool $is_buildtime = true;
|
||||
|
||||
protected $listeners = ['clearAddEnv' => 'clear'];
|
||||
|
||||
@@ -32,7 +34,8 @@ class Add extends Component
|
||||
'value' => 'nullable',
|
||||
'is_multiline' => 'required|boolean',
|
||||
'is_literal' => 'required|boolean',
|
||||
'is_buildtime_only' => 'required|boolean',
|
||||
'is_runtime' => 'required|boolean',
|
||||
'is_buildtime' => 'required|boolean',
|
||||
];
|
||||
|
||||
protected $validationAttributes = [
|
||||
@@ -40,7 +43,8 @@ class Add extends Component
|
||||
'value' => 'value',
|
||||
'is_multiline' => 'multiline',
|
||||
'is_literal' => 'literal',
|
||||
'is_buildtime_only' => 'buildtime only',
|
||||
'is_runtime' => 'runtime',
|
||||
'is_buildtime' => 'buildtime',
|
||||
];
|
||||
|
||||
public function mount()
|
||||
@@ -56,7 +60,8 @@ class Add extends Component
|
||||
'value' => $this->value,
|
||||
'is_multiline' => $this->is_multiline,
|
||||
'is_literal' => $this->is_literal,
|
||||
'is_buildtime_only' => $this->is_buildtime_only,
|
||||
'is_runtime' => $this->is_runtime,
|
||||
'is_buildtime' => $this->is_buildtime,
|
||||
'is_preview' => $this->is_preview,
|
||||
]);
|
||||
$this->clear();
|
||||
@@ -68,6 +73,7 @@ class Add extends Component
|
||||
$this->value = '';
|
||||
$this->is_multiline = false;
|
||||
$this->is_literal = false;
|
||||
$this->is_buildtime_only = false;
|
||||
$this->is_runtime = true;
|
||||
$this->is_buildtime = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,7 +221,8 @@ class All extends Component
|
||||
$environment->value = $data['value'];
|
||||
$environment->is_multiline = $data['is_multiline'] ?? false;
|
||||
$environment->is_literal = $data['is_literal'] ?? false;
|
||||
$environment->is_buildtime_only = $data['is_buildtime_only'] ?? false;
|
||||
$environment->is_runtime = $data['is_runtime'] ?? true;
|
||||
$environment->is_buildtime = $data['is_buildtime'] ?? true;
|
||||
$environment->is_preview = $data['is_preview'] ?? false;
|
||||
$environment->resourceable_id = $this->resource->id;
|
||||
$environment->resourceable_type = $this->resource->getMorphClass();
|
||||
|
||||
@@ -38,7 +38,9 @@ class Show extends Component
|
||||
|
||||
public bool $is_shown_once = false;
|
||||
|
||||
public bool $is_buildtime_only = false;
|
||||
public bool $is_runtime = true;
|
||||
|
||||
public bool $is_buildtime = true;
|
||||
|
||||
public bool $is_required = false;
|
||||
|
||||
@@ -58,7 +60,8 @@ class Show extends Component
|
||||
'is_multiline' => 'required|boolean',
|
||||
'is_literal' => 'required|boolean',
|
||||
'is_shown_once' => 'required|boolean',
|
||||
'is_buildtime_only' => 'required|boolean',
|
||||
'is_runtime' => 'required|boolean',
|
||||
'is_buildtime' => 'required|boolean',
|
||||
'real_value' => 'nullable',
|
||||
'is_required' => 'required|boolean',
|
||||
];
|
||||
@@ -102,7 +105,8 @@ class Show extends Component
|
||||
} else {
|
||||
$this->validate();
|
||||
$this->env->is_required = $this->is_required;
|
||||
$this->env->is_buildtime_only = $this->is_buildtime_only;
|
||||
$this->env->is_runtime = $this->is_runtime;
|
||||
$this->env->is_buildtime = $this->is_buildtime;
|
||||
$this->env->is_shared = $this->is_shared;
|
||||
}
|
||||
$this->env->key = $this->key;
|
||||
@@ -117,7 +121,8 @@ class Show extends Component
|
||||
$this->is_multiline = $this->env->is_multiline;
|
||||
$this->is_literal = $this->env->is_literal;
|
||||
$this->is_shown_once = $this->env->is_shown_once;
|
||||
$this->is_buildtime_only = $this->env->is_buildtime_only ?? false;
|
||||
$this->is_runtime = $this->env->is_runtime ?? true;
|
||||
$this->is_buildtime = $this->env->is_buildtime ?? true;
|
||||
$this->is_required = $this->env->is_required ?? false;
|
||||
$this->is_really_required = $this->env->is_really_required ?? false;
|
||||
$this->is_shared = $this->env->is_shared ?? false;
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace App\Models;
|
||||
|
||||
use App\Enums\ApplicationDeploymentStatus;
|
||||
use App\Services\ConfigurationGenerator;
|
||||
use App\Traits\ClearsGlobalSearchCache;
|
||||
use App\Traits\HasConfiguration;
|
||||
use App\Traits\HasSafeStringAttribute;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
@@ -110,7 +111,7 @@ use Visus\Cuid2\Cuid2;
|
||||
|
||||
class Application extends BaseModel
|
||||
{
|
||||
use HasConfiguration, HasFactory, HasSafeStringAttribute, SoftDeletes;
|
||||
use ClearsGlobalSearchCache, HasConfiguration, HasFactory, HasSafeStringAttribute, SoftDeletes;
|
||||
|
||||
private static $parserVersion = '5';
|
||||
|
||||
@@ -123,66 +124,6 @@ class Application extends BaseModel
|
||||
'http_basic_auth_password' => 'encrypted',
|
||||
];
|
||||
|
||||
public function customNetworkAliases(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
set: function ($value) {
|
||||
if (is_null($value) || $value === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
// If it's already a JSON string, decode it
|
||||
if (is_string($value) && $this->isJson($value)) {
|
||||
$value = json_decode($value, true);
|
||||
}
|
||||
|
||||
// If it's a string but not JSON, treat it as a comma-separated list
|
||||
if (is_string($value) && ! is_array($value)) {
|
||||
$value = explode(',', $value);
|
||||
}
|
||||
|
||||
$value = collect($value)
|
||||
->map(function ($alias) {
|
||||
if (is_string($alias)) {
|
||||
return str_replace(' ', '-', trim($alias));
|
||||
}
|
||||
|
||||
return null;
|
||||
})
|
||||
->filter()
|
||||
->unique() // Remove duplicate values
|
||||
->values()
|
||||
->toArray();
|
||||
|
||||
return empty($value) ? null : json_encode($value);
|
||||
},
|
||||
get: function ($value) {
|
||||
if (is_null($value)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (is_string($value) && $this->isJson($value)) {
|
||||
return json_decode($value, true);
|
||||
}
|
||||
|
||||
return is_array($value) ? $value : [];
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a string is a valid JSON
|
||||
*/
|
||||
private function isJson($string)
|
||||
{
|
||||
if (! is_string($string)) {
|
||||
return false;
|
||||
}
|
||||
json_decode($string);
|
||||
|
||||
return json_last_error() === JSON_ERROR_NONE;
|
||||
}
|
||||
|
||||
protected static function booted()
|
||||
{
|
||||
static::addGlobalScope('withRelations', function ($builder) {
|
||||
@@ -250,6 +191,66 @@ class Application extends BaseModel
|
||||
});
|
||||
}
|
||||
|
||||
public function customNetworkAliases(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
set: function ($value) {
|
||||
if (is_null($value) || $value === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
// If it's already a JSON string, decode it
|
||||
if (is_string($value) && $this->isJson($value)) {
|
||||
$value = json_decode($value, true);
|
||||
}
|
||||
|
||||
// If it's a string but not JSON, treat it as a comma-separated list
|
||||
if (is_string($value) && ! is_array($value)) {
|
||||
$value = explode(',', $value);
|
||||
}
|
||||
|
||||
$value = collect($value)
|
||||
->map(function ($alias) {
|
||||
if (is_string($alias)) {
|
||||
return str_replace(' ', '-', trim($alias));
|
||||
}
|
||||
|
||||
return null;
|
||||
})
|
||||
->filter()
|
||||
->unique() // Remove duplicate values
|
||||
->values()
|
||||
->toArray();
|
||||
|
||||
return empty($value) ? null : json_encode($value);
|
||||
},
|
||||
get: function ($value) {
|
||||
if (is_null($value)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (is_string($value) && $this->isJson($value)) {
|
||||
return json_decode($value, true);
|
||||
}
|
||||
|
||||
return is_array($value) ? $value : [];
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a string is a valid JSON
|
||||
*/
|
||||
private function isJson($string)
|
||||
{
|
||||
if (! is_string($string)) {
|
||||
return false;
|
||||
}
|
||||
json_decode($string);
|
||||
|
||||
return json_last_error() === JSON_ERROR_NONE;
|
||||
}
|
||||
|
||||
public static function ownedByCurrentTeamAPI(int $teamId)
|
||||
{
|
||||
return Application::whereRelation('environment.project.team', 'id', $teamId)->orderBy('name');
|
||||
@@ -932,11 +933,11 @@ class Application extends BaseModel
|
||||
|
||||
public function isConfigurationChanged(bool $save = false)
|
||||
{
|
||||
$newConfigHash = base64_encode($this->fqdn.$this->git_repository.$this->git_branch.$this->git_commit_sha.$this->build_pack.$this->static_image.$this->install_command.$this->build_command.$this->start_command.$this->ports_exposes.$this->ports_mappings.$this->base_directory.$this->publish_directory.$this->dockerfile.$this->dockerfile_location.$this->custom_labels.$this->custom_docker_run_options.$this->dockerfile_target_build.$this->redirect.$this->custom_nginx_configuration.$this->custom_labels);
|
||||
$newConfigHash = base64_encode($this->fqdn.$this->git_repository.$this->git_branch.$this->git_commit_sha.$this->build_pack.$this->static_image.$this->install_command.$this->build_command.$this->start_command.$this->ports_exposes.$this->ports_mappings.$this->base_directory.$this->publish_directory.$this->dockerfile.$this->dockerfile_location.$this->custom_labels.$this->custom_docker_run_options.$this->dockerfile_target_build.$this->redirect.$this->custom_nginx_configuration.$this->custom_labels.$this->settings->use_build_secrets);
|
||||
if ($this->pull_request_id === 0 || $this->pull_request_id === null) {
|
||||
$newConfigHash .= json_encode($this->environment_variables()->get(['value', 'is_multiline', 'is_literal'])->sort());
|
||||
$newConfigHash .= json_encode($this->environment_variables()->get(['value', 'is_multiline', 'is_literal', 'is_buildtime', 'is_runtime'])->sort());
|
||||
} else {
|
||||
$newConfigHash .= json_encode($this->environment_variables_preview->get(['value', 'is_multiline', 'is_literal'])->sort());
|
||||
$newConfigHash .= json_encode($this->environment_variables_preview->get(['value', 'is_multiline', 'is_literal', 'is_buildtime', 'is_runtime'])->sort());
|
||||
}
|
||||
$newConfigHash = md5($newConfigHash);
|
||||
$oldConfigHash = data_get($this, 'config_hash');
|
||||
|
||||
@@ -85,6 +85,47 @@ class ApplicationDeploymentQueue extends Model
|
||||
return str($this->commit_message)->value();
|
||||
}
|
||||
|
||||
private function redactSensitiveInfo($text)
|
||||
{
|
||||
$text = remove_iip($text);
|
||||
|
||||
$app = $this->application;
|
||||
if (! $app) {
|
||||
return $text;
|
||||
}
|
||||
|
||||
$lockedVars = collect([]);
|
||||
|
||||
if ($app->environment_variables) {
|
||||
$lockedVars = $lockedVars->merge(
|
||||
$app->environment_variables
|
||||
->where('is_shown_once', true)
|
||||
->pluck('real_value', 'key')
|
||||
->filter()
|
||||
);
|
||||
}
|
||||
|
||||
if ($this->pull_request_id !== 0 && $app->environment_variables_preview) {
|
||||
$lockedVars = $lockedVars->merge(
|
||||
$app->environment_variables_preview
|
||||
->where('is_shown_once', true)
|
||||
->pluck('real_value', 'key')
|
||||
->filter()
|
||||
);
|
||||
}
|
||||
|
||||
foreach ($lockedVars as $key => $value) {
|
||||
$escapedValue = preg_quote($value, '/');
|
||||
$text = preg_replace(
|
||||
'/'.$escapedValue.'/',
|
||||
REDACTED,
|
||||
$text
|
||||
);
|
||||
}
|
||||
|
||||
return $text;
|
||||
}
|
||||
|
||||
public function addLogEntry(string $message, string $type = 'stdout', bool $hidden = false)
|
||||
{
|
||||
if ($type === 'error') {
|
||||
@@ -96,7 +137,7 @@ class ApplicationDeploymentQueue extends Model
|
||||
}
|
||||
$newLogEntry = [
|
||||
'command' => null,
|
||||
'output' => remove_iip($message),
|
||||
'output' => $this->redactSensitiveInfo($message),
|
||||
'type' => $type,
|
||||
'timestamp' => Carbon::now('UTC'),
|
||||
'hidden' => $hidden,
|
||||
|
||||
@@ -17,7 +17,8 @@ use OpenApi\Attributes as OA;
|
||||
'is_literal' => ['type' => 'boolean'],
|
||||
'is_multiline' => ['type' => 'boolean'],
|
||||
'is_preview' => ['type' => 'boolean'],
|
||||
'is_buildtime_only' => ['type' => 'boolean'],
|
||||
'is_runtime' => ['type' => 'boolean'],
|
||||
'is_buildtime' => ['type' => 'boolean'],
|
||||
'is_shared' => ['type' => 'boolean'],
|
||||
'is_shown_once' => ['type' => 'boolean'],
|
||||
'key' => ['type' => 'string'],
|
||||
@@ -37,13 +38,14 @@ class EnvironmentVariable extends BaseModel
|
||||
'value' => 'encrypted',
|
||||
'is_multiline' => 'boolean',
|
||||
'is_preview' => 'boolean',
|
||||
'is_buildtime_only' => 'boolean',
|
||||
'is_runtime' => 'boolean',
|
||||
'is_buildtime' => 'boolean',
|
||||
'version' => 'string',
|
||||
'resourceable_type' => 'string',
|
||||
'resourceable_id' => 'integer',
|
||||
];
|
||||
|
||||
protected $appends = ['real_value', 'is_shared', 'is_really_required'];
|
||||
protected $appends = ['real_value', 'is_shared', 'is_really_required', 'is_nixpacks', 'is_coolify'];
|
||||
|
||||
protected static function booted()
|
||||
{
|
||||
@@ -137,6 +139,32 @@ class EnvironmentVariable extends BaseModel
|
||||
);
|
||||
}
|
||||
|
||||
protected function isNixpacks(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: function () {
|
||||
if (str($this->key)->startsWith('NIXPACKS_')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
protected function isCoolify(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: function () {
|
||||
if (str($this->key)->startsWith('SERVICE_')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
protected function isShared(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
|
||||
@@ -13,6 +13,7 @@ use App\Jobs\RegenerateSslCertJob;
|
||||
use App\Notifications\Server\Reachable;
|
||||
use App\Notifications\Server\Unreachable;
|
||||
use App\Services\ConfigurationRepository;
|
||||
use App\Traits\ClearsGlobalSearchCache;
|
||||
use App\Traits\HasSafeStringAttribute;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
@@ -55,7 +56,7 @@ use Visus\Cuid2\Cuid2;
|
||||
|
||||
class Server extends BaseModel
|
||||
{
|
||||
use HasFactory, SchemalessAttributesTrait, SoftDeletes;
|
||||
use ClearsGlobalSearchCache, HasFactory, SchemalessAttributesTrait, SoftDeletes;
|
||||
|
||||
public static $batch_counter = 0;
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Models;
|
||||
|
||||
use App\Enums\ProcessStatus;
|
||||
use App\Traits\ClearsGlobalSearchCache;
|
||||
use App\Traits\HasSafeStringAttribute;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
@@ -41,7 +42,7 @@ use Visus\Cuid2\Cuid2;
|
||||
)]
|
||||
class Service extends BaseModel
|
||||
{
|
||||
use HasFactory, HasSafeStringAttribute, SoftDeletes;
|
||||
use ClearsGlobalSearchCache, HasFactory, HasSafeStringAttribute, SoftDeletes;
|
||||
|
||||
private static $parserVersion = '5';
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Traits\ClearsGlobalSearchCache;
|
||||
use App\Traits\HasSafeStringAttribute;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
@@ -9,7 +10,7 @@ use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class StandaloneClickhouse extends BaseModel
|
||||
{
|
||||
use HasFactory, HasSafeStringAttribute, SoftDeletes;
|
||||
use ClearsGlobalSearchCache, HasFactory, HasSafeStringAttribute, SoftDeletes;
|
||||
|
||||
protected $guarded = [];
|
||||
|
||||
@@ -43,6 +44,11 @@ class StandaloneClickhouse extends BaseModel
|
||||
});
|
||||
}
|
||||
|
||||
public static function ownedByCurrentTeam()
|
||||
{
|
||||
return StandaloneClickhouse::whereRelation('environment.project.team', 'id', currentTeam()->id)->orderBy('name');
|
||||
}
|
||||
|
||||
protected function serverStatus(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Traits\ClearsGlobalSearchCache;
|
||||
use App\Traits\HasSafeStringAttribute;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
@@ -9,7 +10,7 @@ use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class StandaloneDragonfly extends BaseModel
|
||||
{
|
||||
use HasFactory, HasSafeStringAttribute, SoftDeletes;
|
||||
use ClearsGlobalSearchCache, HasFactory, HasSafeStringAttribute, SoftDeletes;
|
||||
|
||||
protected $guarded = [];
|
||||
|
||||
@@ -43,6 +44,11 @@ class StandaloneDragonfly extends BaseModel
|
||||
});
|
||||
}
|
||||
|
||||
public static function ownedByCurrentTeam()
|
||||
{
|
||||
return StandaloneDragonfly::whereRelation('environment.project.team', 'id', currentTeam()->id)->orderBy('name');
|
||||
}
|
||||
|
||||
protected function serverStatus(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Traits\ClearsGlobalSearchCache;
|
||||
use App\Traits\HasSafeStringAttribute;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
@@ -9,7 +10,7 @@ use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class StandaloneKeydb extends BaseModel
|
||||
{
|
||||
use HasFactory, HasSafeStringAttribute, SoftDeletes;
|
||||
use ClearsGlobalSearchCache, HasFactory, HasSafeStringAttribute, SoftDeletes;
|
||||
|
||||
protected $guarded = [];
|
||||
|
||||
@@ -43,6 +44,11 @@ class StandaloneKeydb extends BaseModel
|
||||
});
|
||||
}
|
||||
|
||||
public static function ownedByCurrentTeam()
|
||||
{
|
||||
return StandaloneKeydb::whereRelation('environment.project.team', 'id', currentTeam()->id)->orderBy('name');
|
||||
}
|
||||
|
||||
protected function serverStatus(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Traits\ClearsGlobalSearchCache;
|
||||
use App\Traits\HasSafeStringAttribute;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
@@ -10,7 +11,7 @@ use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class StandaloneMariadb extends BaseModel
|
||||
{
|
||||
use HasFactory, HasSafeStringAttribute, SoftDeletes;
|
||||
use ClearsGlobalSearchCache, HasFactory, HasSafeStringAttribute, SoftDeletes;
|
||||
|
||||
protected $guarded = [];
|
||||
|
||||
@@ -44,6 +45,11 @@ class StandaloneMariadb extends BaseModel
|
||||
});
|
||||
}
|
||||
|
||||
public static function ownedByCurrentTeam()
|
||||
{
|
||||
return StandaloneMariadb::whereRelation('environment.project.team', 'id', currentTeam()->id)->orderBy('name');
|
||||
}
|
||||
|
||||
protected function serverStatus(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Traits\ClearsGlobalSearchCache;
|
||||
use App\Traits\HasSafeStringAttribute;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
@@ -9,7 +10,7 @@ use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class StandaloneMongodb extends BaseModel
|
||||
{
|
||||
use HasFactory, HasSafeStringAttribute, SoftDeletes;
|
||||
use ClearsGlobalSearchCache, HasFactory, HasSafeStringAttribute, SoftDeletes;
|
||||
|
||||
protected $guarded = [];
|
||||
|
||||
@@ -46,6 +47,11 @@ class StandaloneMongodb extends BaseModel
|
||||
});
|
||||
}
|
||||
|
||||
public static function ownedByCurrentTeam()
|
||||
{
|
||||
return StandaloneMongodb::whereRelation('environment.project.team', 'id', currentTeam()->id)->orderBy('name');
|
||||
}
|
||||
|
||||
protected function serverStatus(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Traits\ClearsGlobalSearchCache;
|
||||
use App\Traits\HasSafeStringAttribute;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
@@ -9,7 +10,7 @@ use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class StandaloneMysql extends BaseModel
|
||||
{
|
||||
use HasFactory, HasSafeStringAttribute, SoftDeletes;
|
||||
use ClearsGlobalSearchCache, HasFactory, HasSafeStringAttribute, SoftDeletes;
|
||||
|
||||
protected $guarded = [];
|
||||
|
||||
@@ -44,6 +45,11 @@ class StandaloneMysql extends BaseModel
|
||||
});
|
||||
}
|
||||
|
||||
public static function ownedByCurrentTeam()
|
||||
{
|
||||
return StandaloneMysql::whereRelation('environment.project.team', 'id', currentTeam()->id)->orderBy('name');
|
||||
}
|
||||
|
||||
protected function serverStatus(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Traits\ClearsGlobalSearchCache;
|
||||
use App\Traits\HasSafeStringAttribute;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
@@ -9,7 +10,7 @@ use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class StandalonePostgresql extends BaseModel
|
||||
{
|
||||
use HasFactory, HasSafeStringAttribute, SoftDeletes;
|
||||
use ClearsGlobalSearchCache, HasFactory, HasSafeStringAttribute, SoftDeletes;
|
||||
|
||||
protected $guarded = [];
|
||||
|
||||
@@ -44,6 +45,11 @@ class StandalonePostgresql extends BaseModel
|
||||
});
|
||||
}
|
||||
|
||||
public static function ownedByCurrentTeam()
|
||||
{
|
||||
return StandalonePostgresql::whereRelation('environment.project.team', 'id', currentTeam()->id)->orderBy('name');
|
||||
}
|
||||
|
||||
public function workdir()
|
||||
{
|
||||
return database_configuration_dir()."/{$this->uuid}";
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Traits\ClearsGlobalSearchCache;
|
||||
use App\Traits\HasSafeStringAttribute;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
@@ -9,7 +10,7 @@ use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class StandaloneRedis extends BaseModel
|
||||
{
|
||||
use HasFactory, HasSafeStringAttribute, SoftDeletes;
|
||||
use ClearsGlobalSearchCache, HasFactory, HasSafeStringAttribute, SoftDeletes;
|
||||
|
||||
protected $guarded = [];
|
||||
|
||||
@@ -45,6 +46,11 @@ class StandaloneRedis extends BaseModel
|
||||
});
|
||||
}
|
||||
|
||||
public static function ownedByCurrentTeam()
|
||||
{
|
||||
return StandaloneRedis::whereRelation('environment.project.team', 'id', currentTeam()->id)->orderBy('name');
|
||||
}
|
||||
|
||||
protected function serverStatus(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
|
||||
81
app/Traits/ClearsGlobalSearchCache.php
Normal file
81
app/Traits/ClearsGlobalSearchCache.php
Normal file
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
namespace App\Traits;
|
||||
|
||||
use App\Livewire\GlobalSearch;
|
||||
|
||||
trait ClearsGlobalSearchCache
|
||||
{
|
||||
protected static function bootClearsGlobalSearchCache()
|
||||
{
|
||||
static::saving(function ($model) {
|
||||
// Only clear cache if searchable fields are being changed
|
||||
if ($model->hasSearchableChanges()) {
|
||||
$teamId = $model->getTeamIdForCache();
|
||||
if (filled($teamId)) {
|
||||
GlobalSearch::clearTeamCache($teamId);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
static::created(function ($model) {
|
||||
// Always clear cache when model is created
|
||||
$teamId = $model->getTeamIdForCache();
|
||||
if (filled($teamId)) {
|
||||
GlobalSearch::clearTeamCache($teamId);
|
||||
}
|
||||
});
|
||||
|
||||
static::deleted(function ($model) {
|
||||
// Always clear cache when model is deleted
|
||||
$teamId = $model->getTeamIdForCache();
|
||||
if (filled($teamId)) {
|
||||
GlobalSearch::clearTeamCache($teamId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private function hasSearchableChanges(): bool
|
||||
{
|
||||
// Define searchable fields based on model type
|
||||
$searchableFields = ['name', 'description'];
|
||||
|
||||
// Add model-specific searchable fields
|
||||
if ($this instanceof \App\Models\Application) {
|
||||
$searchableFields[] = 'fqdn';
|
||||
$searchableFields[] = 'docker_compose_domains';
|
||||
} elseif ($this instanceof \App\Models\Server) {
|
||||
$searchableFields[] = 'ip';
|
||||
} elseif ($this instanceof \App\Models\Service) {
|
||||
// Services don't have direct fqdn, but name and description are covered
|
||||
}
|
||||
// Database models only have name and description as searchable
|
||||
|
||||
// Check if any searchable field is dirty
|
||||
foreach ($searchableFields as $field) {
|
||||
if ($this->isDirty($field)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function getTeamIdForCache()
|
||||
{
|
||||
// For database models, team is accessed through environment.project.team
|
||||
if (method_exists($this, 'team')) {
|
||||
$team = $this->team();
|
||||
if (filled($team)) {
|
||||
return is_object($team) ? $team->id : null;
|
||||
}
|
||||
}
|
||||
|
||||
// For models with direct team_id property
|
||||
if (property_exists($this, 'team_id') || isset($this->team_id)) {
|
||||
return $this->team_id;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,46 @@ trait ExecuteRemoteCommand
|
||||
|
||||
public static int $batch_counter = 0;
|
||||
|
||||
private function redact_sensitive_info($text)
|
||||
{
|
||||
$text = remove_iip($text);
|
||||
|
||||
if (! isset($this->application)) {
|
||||
return $text;
|
||||
}
|
||||
|
||||
$lockedVars = collect([]);
|
||||
|
||||
if (isset($this->application->environment_variables)) {
|
||||
$lockedVars = $lockedVars->merge(
|
||||
$this->application->environment_variables
|
||||
->where('is_shown_once', true)
|
||||
->pluck('real_value', 'key')
|
||||
->filter()
|
||||
);
|
||||
}
|
||||
|
||||
if (isset($this->pull_request_id) && $this->pull_request_id !== 0 && isset($this->application->environment_variables_preview)) {
|
||||
$lockedVars = $lockedVars->merge(
|
||||
$this->application->environment_variables_preview
|
||||
->where('is_shown_once', true)
|
||||
->pluck('real_value', 'key')
|
||||
->filter()
|
||||
);
|
||||
}
|
||||
|
||||
foreach ($lockedVars as $key => $value) {
|
||||
$escapedValue = preg_quote($value, '/');
|
||||
$text = preg_replace(
|
||||
'/'.$escapedValue.'/',
|
||||
REDACTED,
|
||||
$text
|
||||
);
|
||||
}
|
||||
|
||||
return $text;
|
||||
}
|
||||
|
||||
public function execute_remote_command(...$commands)
|
||||
{
|
||||
static::$batch_counter++;
|
||||
@@ -74,7 +114,7 @@ trait ExecuteRemoteCommand
|
||||
// Track SSH retry event in Sentry
|
||||
$this->trackSshRetryEvent($attempt, $maxRetries, $delay, $errorMessage, [
|
||||
'server' => $this->server->name ?? $this->server->ip ?? 'unknown',
|
||||
'command' => remove_iip($command),
|
||||
'command' => $this->redact_sensitive_info($command),
|
||||
'trait' => 'ExecuteRemoteCommand',
|
||||
]);
|
||||
|
||||
@@ -125,8 +165,8 @@ trait ExecuteRemoteCommand
|
||||
$sanitized_output = sanitize_utf8_text($output);
|
||||
|
||||
$new_log_entry = [
|
||||
'command' => remove_iip($command),
|
||||
'output' => remove_iip($sanitized_output),
|
||||
'command' => $this->redact_sensitive_info($command),
|
||||
'output' => $this->redact_sensitive_info($sanitized_output),
|
||||
'type' => $customType ?? $type === 'err' ? 'stderr' : 'stdout',
|
||||
'timestamp' => Carbon::now('UTC'),
|
||||
'hidden' => $hidden,
|
||||
@@ -194,7 +234,7 @@ trait ExecuteRemoteCommand
|
||||
$retryMessage = "SSH connection failed. Retrying... (Attempt {$attempt}/{$maxRetries}, waiting {$delay}s)\nError: {$errorMessage}";
|
||||
|
||||
$new_log_entry = [
|
||||
'output' => remove_iip($retryMessage),
|
||||
'output' => $this->redact_sensitive_info($retryMessage),
|
||||
'type' => 'stdout',
|
||||
'timestamp' => Carbon::now('UTC'),
|
||||
'hidden' => false,
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
<?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
|
||||
{
|
||||
Schema::table('environment_variables', function (Blueprint $table) {
|
||||
// Add new boolean fields with defaults
|
||||
$table->boolean('is_runtime')->default(true)->after('is_buildtime_only');
|
||||
$table->boolean('is_buildtime')->default(true)->after('is_runtime');
|
||||
});
|
||||
|
||||
// Migrate existing data from is_buildtime_only to new fields
|
||||
DB::table('environment_variables')
|
||||
->where('is_buildtime_only', true)
|
||||
->update([
|
||||
'is_runtime' => false,
|
||||
'is_buildtime' => true,
|
||||
]);
|
||||
|
||||
DB::table('environment_variables')
|
||||
->where('is_buildtime_only', false)
|
||||
->update([
|
||||
'is_runtime' => true,
|
||||
'is_buildtime' => true,
|
||||
]);
|
||||
|
||||
// Remove the old is_buildtime_only column
|
||||
Schema::table('environment_variables', function (Blueprint $table) {
|
||||
$table->dropColumn('is_buildtime_only');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('environment_variables', function (Blueprint $table) {
|
||||
// Re-add the is_buildtime_only column
|
||||
$table->boolean('is_buildtime_only')->default(false)->after('is_preview');
|
||||
});
|
||||
|
||||
// Restore data to is_buildtime_only based on new fields
|
||||
DB::table('environment_variables')
|
||||
->where('is_runtime', false)
|
||||
->where('is_buildtime', true)
|
||||
->update(['is_buildtime_only' => true]);
|
||||
|
||||
DB::table('environment_variables')
|
||||
->where('is_runtime', true)
|
||||
->update(['is_buildtime_only' => false]);
|
||||
|
||||
// Remove new columns
|
||||
Schema::table('environment_variables', function (Blueprint $table) {
|
||||
$table->dropColumn(['is_runtime', 'is_buildtime']);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -8360,7 +8360,10 @@
|
||||
"is_preview": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"is_buildtime_only": {
|
||||
"is_runtime": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"is_buildtime": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"is_shared": {
|
||||
|
||||
@@ -5411,7 +5411,9 @@ components:
|
||||
type: boolean
|
||||
is_preview:
|
||||
type: boolean
|
||||
is_buildtime_only:
|
||||
is_runtime:
|
||||
type: boolean
|
||||
is_buildtime:
|
||||
type: boolean
|
||||
is_shared:
|
||||
type: boolean
|
||||
|
||||
@@ -59,20 +59,20 @@
|
||||
if (this.zoom === '90') {
|
||||
const style = document.createElement('style');
|
||||
style.textContent = `
|
||||
html {
|
||||
font-size: 93.75%;
|
||||
}
|
||||
|
||||
:root {
|
||||
--vh: 1vh;
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
html {
|
||||
font-size: 87.5%;
|
||||
font-size: 93.75%;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
:root {
|
||||
--vh: 1vh;
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
html {
|
||||
font-size: 87.5%;
|
||||
}
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
}
|
||||
@@ -82,6 +82,9 @@
|
||||
<div class="text-2xl font-bold tracking-wide dark:text-white">Coolify</div>
|
||||
<x-version />
|
||||
</div>
|
||||
<div>
|
||||
<livewire:global-search />
|
||||
</div>
|
||||
<livewire:settings-dropdown />
|
||||
</div>
|
||||
<div class="px-2 pt-2 pb-7">
|
||||
|
||||
236
resources/views/livewire/global-search.blade.php
Normal file
236
resources/views/livewire/global-search.blade.php
Normal file
@@ -0,0 +1,236 @@
|
||||
<div x-data="{
|
||||
modalOpen: false,
|
||||
selectedIndex: -1,
|
||||
openModal() {
|
||||
this.modalOpen = true;
|
||||
this.selectedIndex = -1;
|
||||
@this.openSearchModal();
|
||||
},
|
||||
closeModal() {
|
||||
this.modalOpen = false;
|
||||
this.selectedIndex = -1;
|
||||
@this.closeSearchModal();
|
||||
},
|
||||
navigateResults(direction) {
|
||||
const results = document.querySelectorAll('.search-result-item');
|
||||
if (results.length === 0) return;
|
||||
|
||||
if (direction === 'down') {
|
||||
this.selectedIndex = Math.min(this.selectedIndex + 1, results.length - 1);
|
||||
} else if (direction === 'up') {
|
||||
this.selectedIndex = Math.max(this.selectedIndex - 1, -1);
|
||||
}
|
||||
|
||||
if (this.selectedIndex >= 0 && this.selectedIndex < results.length) {
|
||||
results[this.selectedIndex].focus();
|
||||
results[this.selectedIndex].scrollIntoView({ block: 'nearest' });
|
||||
} else if (this.selectedIndex === -1) {
|
||||
this.$refs.searchInput?.focus();
|
||||
}
|
||||
},
|
||||
init() {
|
||||
// Listen for / key press globally
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === '/' && !['INPUT', 'TEXTAREA'].includes(e.target.tagName) && !this.modalOpen) {
|
||||
e.preventDefault();
|
||||
this.openModal();
|
||||
}
|
||||
});
|
||||
|
||||
// Listen for Cmd+K or Ctrl+K globally
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
|
||||
e.preventDefault();
|
||||
if (this.modalOpen) {
|
||||
this.closeModal();
|
||||
} else {
|
||||
this.openModal();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Listen for Escape key to close modal
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape' && this.modalOpen) {
|
||||
this.closeModal();
|
||||
}
|
||||
});
|
||||
|
||||
// Listen for arrow keys when modal is open
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (!this.modalOpen) return;
|
||||
if (e.key === 'ArrowDown') {
|
||||
e.preventDefault();
|
||||
this.navigateResults('down');
|
||||
} else if (e.key === 'ArrowUp') {
|
||||
e.preventDefault();
|
||||
this.navigateResults('up');
|
||||
}
|
||||
});
|
||||
}
|
||||
}">
|
||||
<!-- Search bar in navbar -->
|
||||
<div class="flex justify-center">
|
||||
<button @click="openModal()" type="button" title="Search (Press / or ⌘K)"
|
||||
class="flex items-center gap-1.5 px-2.5 py-1.5 bg-neutral-100 dark:bg-coolgray-100 border border-neutral-300 dark:border-coolgray-200 rounded-md hover:bg-neutral-200 dark:hover:bg-coolgray-200 transition-colors">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-neutral-500 dark:text-neutral-400" fill="none"
|
||||
viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
||||
</svg>
|
||||
<kbd
|
||||
class="px-1 py-0.5 text-xs font-semibold text-neutral-500 dark:text-neutral-400 bg-neutral-200 dark:bg-coolgray-200 rounded">/</kbd>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Modal overlay -->
|
||||
<template x-teleport="body">
|
||||
<div x-show="modalOpen" x-cloak
|
||||
class="fixed top-0 lg:pt-10 left-0 z-99 flex items-start justify-center w-screen h-screen">
|
||||
<div @click="closeModal()" class="absolute inset-0 w-full h-full bg-black/20 backdrop-blur-xs">
|
||||
</div>
|
||||
<div x-show="modalOpen" x-trap.inert.noscroll="modalOpen" x-transition:enter="ease-out duration-100"
|
||||
x-transition:enter-start="opacity-0 -translate-y-2 sm:scale-95"
|
||||
x-transition:enter-end="opacity-100 translate-y-0 sm:scale-100"
|
||||
x-transition:leave="ease-in duration-100"
|
||||
x-transition:leave-start="opacity-100 translate-y-0 sm:scale-100"
|
||||
x-transition:leave-end="opacity-0 -translate-y-2 sm:scale-95"
|
||||
class="relative w-full py-6 border rounded-sm min-w-full lg:min-w-[36rem] max-w-[48rem] bg-neutral-100 border-neutral-400 dark:bg-base px-7 dark:border-coolgray-300"
|
||||
@click.stop>
|
||||
|
||||
<div class="flex justify-between items-center pb-3">
|
||||
<h3 class="pr-8 text-2xl font-bold">Search</h3>
|
||||
<button @click="closeModal()"
|
||||
class="flex absolute top-2 right-2 justify-center items-center w-8 h-8 rounded-full dark:text-white hover:bg-coolgray-300">
|
||||
<svg class="w-6 h-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
|
||||
stroke-width="1.5" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="relative w-auto">
|
||||
<input type="text" wire:model.live.debounce.500ms="searchQuery"
|
||||
placeholder="Type to search for applications, services, databases, and servers..."
|
||||
x-ref="searchInput" x-init="$watch('modalOpen', value => { if (value) setTimeout(() => $refs.searchInput.focus(), 100) })" class="w-full input mb-4" />
|
||||
|
||||
<!-- Search results -->
|
||||
<div class="relative min-h-[330px] max-h-[400px] overflow-y-auto scrollbar">
|
||||
<!-- Loading indicator -->
|
||||
<div wire:loading.flex wire:target="searchQuery"
|
||||
class="min-h-[330px] items-center justify-center">
|
||||
<div class="text-center">
|
||||
<svg class="animate-spin mx-auto h-8 w-8 text-neutral-400"
|
||||
xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10"
|
||||
stroke="currentColor" stroke-width="4"></circle>
|
||||
<path class="opacity-75" fill="currentColor"
|
||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z">
|
||||
</path>
|
||||
</svg>
|
||||
<p class="mt-2 text-sm text-neutral-600 dark:text-neutral-400">
|
||||
Searching...
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Results content - hidden while loading -->
|
||||
<div wire:loading.remove wire:target="searchQuery">
|
||||
@if (strlen($searchQuery) >= 2 && count($searchResults) > 0)
|
||||
<div class="space-y-1 my-4 pb-4">
|
||||
@foreach ($searchResults as $index => $result)
|
||||
<a href="{{ $result['link'] ?? '#' }}"
|
||||
class="search-result-item block p-3 mx-1 hover:bg-neutral-200 dark:hover:bg-coolgray-200 transition-colors focus:outline-none focus:ring-1 focus:ring-coollabs focus:bg-neutral-100 dark:focus:bg-coolgray-200 ">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex-1">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="font-medium text-neutral-900 dark:text-white">
|
||||
{{ $result['name'] }}
|
||||
</span>
|
||||
@if ($result['type'] === 'server')
|
||||
<span
|
||||
class="px-2 py-0.5 text-xs rounded bg-coolgray-100 text-white">
|
||||
Server
|
||||
</span>
|
||||
@endif
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
@if (!empty($result['project']) && !empty($result['environment']))
|
||||
<span
|
||||
class="text-xs text-neutral-500 dark:text-neutral-400">
|
||||
{{ $result['project'] }} / {{ $result['environment'] }}
|
||||
</span>
|
||||
@endif
|
||||
@if ($result['type'] === 'application')
|
||||
<span
|
||||
class="px-2 py-0.5 text-xs rounded bg-coolgray-100 text-white">
|
||||
Application
|
||||
</span>
|
||||
@elseif ($result['type'] === 'service')
|
||||
<span
|
||||
class="px-2 py-0.5 text-xs rounded bg-coolgray-100 text-white">
|
||||
Service
|
||||
</span>
|
||||
@elseif ($result['type'] === 'database')
|
||||
<span
|
||||
class="px-2 py-0.5 text-xs rounded bg-coolgray-100 text-white">
|
||||
{{ ucfirst($result['subtype'] ?? 'Database') }}
|
||||
</span>
|
||||
@endif
|
||||
</div>
|
||||
@if (!empty($result['description']))
|
||||
<div
|
||||
class="text-sm text-neutral-600 dark:text-neutral-400 mt-0.5">
|
||||
{{ Str::limit($result['description'], 100) }}
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
class="shrink-0 ml-2 h-4 w-4 text-neutral-400" fill="none"
|
||||
viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round"
|
||||
stroke-width="2" d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
</div>
|
||||
</a>
|
||||
@endforeach
|
||||
</div>
|
||||
@elseif (strlen($searchQuery) >= 2 && count($searchResults) === 0)
|
||||
<div class="flex items-center justify-center min-h-[330px]">
|
||||
<div class="text-center">
|
||||
<p class="text-sm text-neutral-600 dark:text-neutral-400">
|
||||
No results found for "<strong>{{ $searchQuery }}</strong>"
|
||||
</p>
|
||||
<p class="text-xs text-neutral-500 dark:text-neutral-500 mt-2">
|
||||
Try different keywords or check the spelling
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@elseif (strlen($searchQuery) > 0 && strlen($searchQuery) < 2)
|
||||
<div class="flex items-center justify-center min-h-[330px]">
|
||||
<div class="text-center">
|
||||
<p class="text-sm text-neutral-600 dark:text-neutral-400">
|
||||
Type at least 2 characters to search
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@else
|
||||
<div class="flex items-center justify-center min-h-[330px]">
|
||||
<div class="text-center">
|
||||
<p class="text-sm text-neutral-600 dark:text-neutral-400">
|
||||
Start typing to search
|
||||
</p>
|
||||
<p class="text-xs text-neutral-500 dark:text-neutral-500 mt-2">
|
||||
Search for applications, services, databases, and servers
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
@@ -3,15 +3,19 @@
|
||||
<x-forms.textarea x-show="$wire.is_multiline === true" x-cloak id="value" label="Value" required />
|
||||
<x-forms.input x-show="$wire.is_multiline === false" x-cloak placeholder="production" id="value"
|
||||
x-bind:label="$wire.is_multiline === false && 'Value'" required />
|
||||
<x-forms.checkbox id="is_multiline" label="Is Multiline?" />
|
||||
@if (!$shared)
|
||||
<x-forms.checkbox id="is_buildtime_only"
|
||||
helper="This variable will ONLY be available during build and not in the running container. Useful for build secrets that shouldn't persist at runtime."
|
||||
label="Buildtime Only?" />
|
||||
@if (!$shared || $isNixpacks)
|
||||
<x-forms.checkbox id="is_buildtime"
|
||||
helper="Make this variable available during Docker build process. Useful for build secrets and dependencies."
|
||||
label="Available at Buildtime" />
|
||||
<x-forms.checkbox id="is_runtime"
|
||||
helper="Make this variable available in the running container at runtime."
|
||||
label="Available at Runtime" />
|
||||
<x-forms.checkbox id="is_literal"
|
||||
helper="This means that when you use $VARIABLES in a value, it should be interpreted as the actual characters '$VARIABLES' and not as the value of a variable named VARIABLE.<br><br>Useful if you have $ sign in your value and there are some characters after it, but you would not like to interpolate it from another value. In this case, you should set this to true."
|
||||
label="Is Literal?" />
|
||||
@endif
|
||||
|
||||
<x-forms.checkbox id="is_multiline" label="Is Multiline?" />
|
||||
<x-forms.button type="submit" @click="slideOverOpen=false">
|
||||
Save
|
||||
</x-forms.button>
|
||||
|
||||
@@ -19,11 +19,11 @@
|
||||
<div class="w-64">
|
||||
@can('manageEnvironment', $resource)
|
||||
<x-forms.checkbox id="is_env_sorting_enabled" label="Sort alphabetically"
|
||||
helper="Turn this off if one environment is dependent on an other. It will be sorted by creation order (like you pasted them or in the order you created them)."
|
||||
helper="Turn this off if one environment is dependent on another. It will be sorted by creation order (like you pasted them or in the order you created them)."
|
||||
instantSave></x-forms.checkbox>
|
||||
@else
|
||||
<x-forms.checkbox id="is_env_sorting_enabled" label="Sort alphabetically"
|
||||
helper="Turn this off if one environment is dependent on an other. It will be sorted by creation order (like you pasted them or in the order you created them)."
|
||||
helper="Turn this off if one environment is dependent on another. It will be sorted by creation order (like you pasted them or in the order you created them)."
|
||||
disabled></x-forms.checkbox>
|
||||
@endcan
|
||||
</div>
|
||||
|
||||
@@ -21,6 +21,95 @@
|
||||
step2ButtonText="Permanently Delete" />
|
||||
@endcan
|
||||
</div>
|
||||
@can('update', $this->env)
|
||||
<div class="flex flex-col w-full gap-3">
|
||||
<div class="flex w-full items-center gap-4 overflow-x-auto whitespace-nowrap">
|
||||
@if (!$is_redis_credential)
|
||||
@if ($type === 'service')
|
||||
<x-forms.checkbox instantSave id="is_buildtime"
|
||||
helper="Make this variable available during Docker build process. Useful for build secrets and dependencies."
|
||||
label="Available at Buildtime" />
|
||||
<x-forms.checkbox instantSave id="is_runtime"
|
||||
helper="Make this variable available in the running container at runtime."
|
||||
label="Available at Runtime" />
|
||||
<x-forms.checkbox instantSave id="is_multiline" label="Is Multiline?" />
|
||||
<x-forms.checkbox instantSave id="is_literal"
|
||||
helper="This means that when you use $VARIABLES in a value, it should be interpreted as the actual characters '$VARIABLES' and not as the value of a variable named VARIABLE.<br><br>Useful if you have $ sign in your value and there are some characters after it, but you would not like to interpolate it from another value. In this case, you should set this to true."
|
||||
label="Is Literal?" />
|
||||
@else
|
||||
@if ($is_shared)
|
||||
<x-forms.checkbox instantSave id="is_literal"
|
||||
helper="This means that when you use $VARIABLES in a value, it should be interpreted as the actual characters '$VARIABLES' and not as the value of a variable named VARIABLE.<br><br>Useful if you have $ sign in your value and there are some characters after it, but you would not like to interpolate it from another value. In this case, you should set this to true."
|
||||
label="Is Literal?" />
|
||||
@else
|
||||
@if ($isSharedVariable)
|
||||
<x-forms.checkbox instantSave id="is_multiline" label="Is Multiline?" />
|
||||
@else
|
||||
@if (!$env->is_nixpacks)
|
||||
<x-forms.checkbox instantSave id="is_buildtime"
|
||||
helper="Make this variable available during Docker build process. Useful for build secrets and dependencies."
|
||||
label="Available at Buildtime" />
|
||||
@endif
|
||||
<x-forms.checkbox instantSave id="is_runtime"
|
||||
helper="Make this variable available in the running container at runtime."
|
||||
label="Available at Runtime" />
|
||||
@if (!$env->is_nixpacks)
|
||||
<x-forms.checkbox instantSave id="is_multiline" label="Is Multiline?" />
|
||||
@if ($is_multiline === false)
|
||||
<x-forms.checkbox instantSave id="is_literal"
|
||||
helper="This means that when you use $VARIABLES in a value, it should be interpreted as the actual characters '$VARIABLES' and not as the value of a variable named VARIABLE.<br><br>Useful if you have $ sign in your value and there are some characters after it, but you would not like to interpolate it from another value. In this case, you should set this to true."
|
||||
label="Is Literal?" />
|
||||
@endif
|
||||
@endif
|
||||
@endif
|
||||
@endif
|
||||
@endif
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@else
|
||||
<div class="flex flex-col w-full gap-3">
|
||||
<div class="flex w-full items-center gap-4 overflow-x-auto whitespace-nowrap">
|
||||
@if (!$is_redis_credential)
|
||||
@if ($type === 'service')
|
||||
<x-forms.checkbox disabled id="is_buildtime"
|
||||
helper="Make this variable available during Docker build process. Useful for build secrets and dependencies."
|
||||
label="Available at Buildtime" />
|
||||
<x-forms.checkbox disabled id="is_runtime"
|
||||
helper="Make this variable available in the running container at runtime."
|
||||
label="Available at Runtime" />
|
||||
<x-forms.checkbox disabled id="is_multiline" label="Is Multiline?" />
|
||||
<x-forms.checkbox disabled id="is_literal"
|
||||
helper="This means that when you use $VARIABLES in a value, it should be interpreted as the actual characters '$VARIABLES' and not as the value of a variable named VARIABLE.<br><br>Useful if you have $ sign in your value and there are some characters after it, but you would not like to interpolate it from another value. In this case, you should set this to true."
|
||||
label="Is Literal?" />
|
||||
@else
|
||||
@if ($is_shared)
|
||||
<x-forms.checkbox disabled id="is_literal"
|
||||
helper="This means that when you use $VARIABLES in a value, it should be interpreted as the actual characters '$VARIABLES' and not as the value of a variable named VARIABLE.<br><br>Useful if you have $ sign in your value and there are some characters after it, but you would not like to interpolate it from another value. In this case, you should set this to true."
|
||||
label="Is Literal?" />
|
||||
@else
|
||||
@if ($isSharedVariable)
|
||||
<x-forms.checkbox disabled id="is_multiline" label="Is Multiline?" />
|
||||
@else
|
||||
<x-forms.checkbox disabled id="is_buildtime"
|
||||
helper="Make this variable available during Docker build process. Useful for build secrets and dependencies."
|
||||
label="Available at Buildtime" />
|
||||
<x-forms.checkbox disabled id="is_runtime"
|
||||
helper="Make this variable available in the running container at runtime."
|
||||
label="Available at Runtime" />
|
||||
<x-forms.checkbox disabled id="is_multiline" label="Is Multiline?" />
|
||||
@if ($is_multiline === false)
|
||||
<x-forms.checkbox disabled id="is_literal"
|
||||
helper="This means that when you use $VARIABLES in a value, it should be interpreted as the actual characters '$VARIABLES' and not as the value of a variable named VARIABLE.<br><br>Useful if you have $ sign in your value and there are some characters after it, but you would not like to interpolate it from another value. In this case, you should set this to true."
|
||||
label="Is Literal?" />
|
||||
@endif
|
||||
@endif
|
||||
@endif
|
||||
@endif
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@endcan
|
||||
@else
|
||||
@can('update', $this->env)
|
||||
@if ($isDisabled)
|
||||
@@ -55,101 +144,113 @@
|
||||
</div>
|
||||
@endcan
|
||||
@can('update', $this->env)
|
||||
<div class="flex flex-col w-full gap-2 lg:flex-row">
|
||||
@if (!$is_redis_credential)
|
||||
@if ($type === 'service')
|
||||
<x-forms.checkbox instantSave id="is_buildtime_only"
|
||||
helper="This variable will ONLY be available during build and not in the running container. Useful for build secrets that shouldn't persist at runtime."
|
||||
label="Buildtime Only?" />
|
||||
<x-forms.checkbox instantSave id="is_multiline" label="Is Multiline?" />
|
||||
<x-forms.checkbox instantSave id="is_literal"
|
||||
helper="This means that when you use $VARIABLES in a value, it should be interpreted as the actual characters '$VARIABLES' and not as the value of a variable named VARIABLE.<br><br>Useful if you have $ sign in your value and there are some characters after it, but you would not like to interpolate it from another value. In this case, you should set this to true."
|
||||
label="Is Literal?" />
|
||||
@else
|
||||
@if ($is_shared)
|
||||
<div class="flex flex-col w-full gap-3">
|
||||
<div class="flex w-full items-center gap-4 overflow-x-auto whitespace-nowrap">
|
||||
@if (!$is_redis_credential)
|
||||
@if ($type === 'service')
|
||||
<x-forms.checkbox instantSave id="is_buildtime"
|
||||
helper="Make this variable available during Docker build process. Useful for build secrets and dependencies."
|
||||
label="Available at Buildtime" />
|
||||
<x-forms.checkbox instantSave id="is_runtime"
|
||||
helper="Make this variable available in the running container at runtime."
|
||||
label="Available at Runtime" />
|
||||
<x-forms.checkbox instantSave id="is_multiline" label="Is Multiline?" />
|
||||
<x-forms.checkbox instantSave id="is_literal"
|
||||
helper="This means that when you use $VARIABLES in a value, it should be interpreted as the actual characters '$VARIABLES' and not as the value of a variable named VARIABLE.<br><br>Useful if you have $ sign in your value and there are some characters after it, but you would not like to interpolate it from another value. In this case, you should set this to true."
|
||||
label="Is Literal?" />
|
||||
@else
|
||||
@if ($isSharedVariable)
|
||||
<x-forms.checkbox instantSave id="is_multiline" label="Is Multiline?" />
|
||||
@if ($is_shared)
|
||||
<x-forms.checkbox instantSave id="is_literal"
|
||||
helper="This means that when you use $VARIABLES in a value, it should be interpreted as the actual characters '$VARIABLES' and not as the value of a variable named VARIABLE.<br><br>Useful if you have $ sign in your value and there are some characters after it, but you would not like to interpolate it from another value. In this case, you should set this to true."
|
||||
label="Is Literal?" />
|
||||
@else
|
||||
<x-forms.checkbox instantSave id="is_buildtime_only"
|
||||
helper="This variable will ONLY be available during build and not in the running container. Useful for build secrets that shouldn't persist at runtime."
|
||||
label="Buildtime Only?" />
|
||||
<x-forms.checkbox instantSave id="is_multiline" label="Is Multiline?" />
|
||||
@if ($is_multiline === false)
|
||||
<x-forms.checkbox instantSave id="is_literal"
|
||||
helper="This means that when you use $VARIABLES in a value, it should be interpreted as the actual characters '$VARIABLES' and not as the value of a variable named VARIABLE.<br><br>Useful if you have $ sign in your value and there are some characters after it, but you would not like to interpolate it from another value. In this case, you should set this to true."
|
||||
label="Is Literal?" />
|
||||
@if ($isSharedVariable)
|
||||
<x-forms.checkbox instantSave id="is_multiline" label="Is Multiline?" />
|
||||
@else
|
||||
@if (!$env->is_nixpacks)
|
||||
<x-forms.checkbox instantSave id="is_buildtime"
|
||||
helper="Make this variable available during Docker build process. Useful for build secrets and dependencies."
|
||||
label="Available at Buildtime" />
|
||||
@endif
|
||||
<x-forms.checkbox instantSave id="is_runtime"
|
||||
helper="Make this variable available in the running container at runtime."
|
||||
label="Available at Runtime" />
|
||||
@if (!$env->is_nixpacks)
|
||||
<x-forms.checkbox instantSave id="is_multiline" label="Is Multiline?" />
|
||||
@if ($is_multiline === false)
|
||||
<x-forms.checkbox instantSave id="is_literal"
|
||||
helper="This means that when you use $VARIABLES in a value, it should be interpreted as the actual characters '$VARIABLES' and not as the value of a variable named VARIABLE.<br><br>Useful if you have $ sign in your value and there are some characters after it, but you would not like to interpolate it from another value. In this case, you should set this to true."
|
||||
label="Is Literal?" />
|
||||
@endif
|
||||
@endif
|
||||
@endif
|
||||
@endif
|
||||
@endif
|
||||
@endif
|
||||
@endif
|
||||
<div class="flex-1"></div>
|
||||
@if ($isDisabled)
|
||||
<x-forms.button disabled type="submit">
|
||||
Update
|
||||
</x-forms.button>
|
||||
<x-forms.button wire:click='lock'>
|
||||
Lock
|
||||
</x-forms.button>
|
||||
<x-modal-confirmation title="Confirm Environment Variable Deletion?" isErrorButton
|
||||
buttonTitle="Delete" submitAction="delete" :actions="['The selected environment variable will be permanently deleted.']"
|
||||
confirmationText="{{ $key }}" buttonFullWidth="true"
|
||||
confirmationLabel="Please confirm the execution of the actions by entering the Environment Variable Name below"
|
||||
shortConfirmationLabel="Environment Variable Name" :confirmWithPassword="false"
|
||||
step2ButtonText="Permanently Delete" />
|
||||
@else
|
||||
<x-forms.button type="submit">
|
||||
Update
|
||||
</x-forms.button>
|
||||
<x-forms.button wire:click='lock'>
|
||||
Lock
|
||||
</x-forms.button>
|
||||
<x-modal-confirmation title="Confirm Environment Variable Deletion?" isErrorButton
|
||||
buttonTitle="Delete" submitAction="delete" :actions="['The selected environment variable will be permanently deleted.']"
|
||||
confirmationText="{{ $key }}" buttonFullWidth="true"
|
||||
confirmationLabel="Please confirm the execution of the actions by entering the Environment Variable Name below"
|
||||
shortConfirmationLabel="Environment Variable Name" :confirmWithPassword="false"
|
||||
step2ButtonText="Permanently Delete" />
|
||||
@endif
|
||||
</div>
|
||||
<div class="flex w-full justify-end gap-2">
|
||||
@if ($isDisabled)
|
||||
<x-forms.button disabled type="submit">Update</x-forms.button>
|
||||
<x-forms.button wire:click='lock'>Lock</x-forms.button>
|
||||
<x-modal-confirmation title="Confirm Environment Variable Deletion?" isErrorButton
|
||||
buttonTitle="Delete" submitAction="delete" :actions="['The selected environment variable will be permanently deleted.']"
|
||||
confirmationText="{{ $key }}" buttonFullWidth="true"
|
||||
confirmationLabel="Please confirm the execution of the actions by entering the Environment Variable Name below"
|
||||
shortConfirmationLabel="Environment Variable Name" :confirmWithPassword="false"
|
||||
step2ButtonText="Permanently Delete" />
|
||||
@else
|
||||
<x-forms.button type="submit">Update</x-forms.button>
|
||||
<x-forms.button wire:click='lock'>Lock</x-forms.button>
|
||||
<x-modal-confirmation title="Confirm Environment Variable Deletion?" isErrorButton
|
||||
buttonTitle="Delete" submitAction="delete" :actions="['The selected environment variable will be permanently deleted.']"
|
||||
confirmationText="{{ $key }}" buttonFullWidth="true"
|
||||
confirmationLabel="Please confirm the execution of the actions by entering the Environment Variable Name below"
|
||||
shortConfirmationLabel="Environment Variable Name" :confirmWithPassword="false"
|
||||
step2ButtonText="Permanently Delete" />
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@else
|
||||
<div class="flex flex-col w-full gap-2 flex-wrap lg:flex-row">
|
||||
@if (!$is_redis_credential)
|
||||
@if ($type === 'service')
|
||||
<x-forms.checkbox disabled id="is_buildtime_only"
|
||||
helper="This variable will ONLY be available during build and not in the running container. Useful for build secrets that shouldn't persist at runtime."
|
||||
label="Buildtime Only?" />
|
||||
<x-forms.checkbox disabled id="is_multiline" label="Is Multiline?" />
|
||||
<x-forms.checkbox disabled id="is_literal"
|
||||
helper="This means that when you use $VARIABLES in a value, it should be interpreted as the actual characters '$VARIABLES' and not as the value of a variable named VARIABLE.<br><br>Useful if you have $ sign in your value and there are some characters after it, but you would not like to interpolate it from another value. In this case, you should set this to true."
|
||||
label="Is Literal?" />
|
||||
@else
|
||||
@if ($is_shared)
|
||||
<div class="flex flex-col w-full gap-3">
|
||||
<div class="flex w-full items-center gap-4 overflow-x-auto whitespace-nowrap">
|
||||
@if (!$is_redis_credential)
|
||||
@if ($type === 'service')
|
||||
<x-forms.checkbox disabled id="is_buildtime"
|
||||
helper="Make this variable available during Docker build process. Useful for build secrets and dependencies."
|
||||
label="Available at Buildtime" />
|
||||
<x-forms.checkbox disabled id="is_runtime"
|
||||
helper="Make this variable available in the running container at runtime."
|
||||
label="Available at Runtime" />
|
||||
<x-forms.checkbox disabled id="is_multiline" label="Is Multiline?" />
|
||||
<x-forms.checkbox disabled id="is_literal"
|
||||
helper="This means that when you use $VARIABLES in a value, it should be interpreted as the actual characters '$VARIABLES' and not as the value of a variable named VARIABLE.<br><br>Useful if you have $ sign in your value and there are some characters after it, but you would not like to interpolate it from another value. In this case, you should set this to true."
|
||||
label="Is Literal?" />
|
||||
@else
|
||||
@if ($isSharedVariable)
|
||||
<x-forms.checkbox disabled id="is_multiline" label="Is Multiline?" />
|
||||
@if ($is_shared)
|
||||
<x-forms.checkbox disabled id="is_literal"
|
||||
helper="This means that when you use $VARIABLES in a value, it should be interpreted as the actual characters '$VARIABLES' and not as the value of a variable named VARIABLE.<br><br>Useful if you have $ sign in your value and there are some characters after it, but you would not like to interpolate it from another value. In this case, you should set this to true."
|
||||
label="Is Literal?" />
|
||||
@else
|
||||
<x-forms.checkbox disabled id="is_buildtime_only"
|
||||
helper="This variable will ONLY be available during build and not in the running container. Useful for build secrets that shouldn't persist at runtime."
|
||||
label="Buildtime Only?" />
|
||||
<x-forms.checkbox disabled id="is_multiline" label="Is Multiline?" />
|
||||
@if ($is_multiline === false)
|
||||
<x-forms.checkbox disabled id="is_literal"
|
||||
helper="This means that when you use $VARIABLES in a value, it should be interpreted as the actual characters '$VARIABLES' and not as the value of a variable named VARIABLE.<br><br>Useful if you have $ sign in your value and there are some characters after it, but you would not like to interpolate it from another value. In this case, you should set this to true."
|
||||
label="Is Literal?" />
|
||||
@if ($isSharedVariable)
|
||||
<x-forms.checkbox disabled id="is_multiline" label="Is Multiline?" />
|
||||
@else
|
||||
<x-forms.checkbox disabled id="is_buildtime"
|
||||
helper="Make this variable available during Docker build process. Useful for build secrets and dependencies."
|
||||
label="Available at Buildtime" />
|
||||
<x-forms.checkbox disabled id="is_runtime"
|
||||
helper="Make this variable available in the running container at runtime."
|
||||
label="Available at Runtime" />
|
||||
<x-forms.checkbox disabled id="is_multiline" label="Is Multiline?" />
|
||||
@if ($is_multiline === false)
|
||||
<x-forms.checkbox disabled id="is_literal"
|
||||
helper="This means that when you use $VARIABLES in a value, it should be interpreted as the actual characters '$VARIABLES' and not as the value of a variable named VARIABLE.<br><br>Useful if you have $ sign in your value and there are some characters after it, but you would not like to interpolate it from another value. In this case, you should set this to true."
|
||||
label="Is Literal?" />
|
||||
@endif
|
||||
@endif
|
||||
@endif
|
||||
@endif
|
||||
@endif
|
||||
@endif
|
||||
<div class="flex-1"></div>
|
||||
</div>
|
||||
</div>
|
||||
@endcan
|
||||
@endif
|
||||
|
||||
Reference in New Issue
Block a user