Merge pull request #2155 from coollabsio/next

v4.0.0-beta.275
This commit is contained in:
Andras Bacsai 2024-05-06 14:03:15 +02:00 committed by GitHub
commit 17b73aaf91
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
43 changed files with 424 additions and 391 deletions

View File

@ -3,7 +3,7 @@ tasks:
# Fix because of https://github.com/gitpod-io/gitpod/issues/16614
before: sudo curl -o /usr/local/bin/docker-compose -fsSL https://github.com/docker/compose/releases/download/v2.16.0/docker-compose-linux-$(uname -m)
init: |
cp .env.example .env &&
cp .env.development.example .env &&
sed -i "s#APP_URL=http://localhost#APP_URL=$(gp url 8000)#g" .env
sed -i "s#USERID=#USERID=33333#g" .env
sed -i "s#GROUPID=#GROUPID=33333#g" .env
@ -20,7 +20,7 @@ tasks:
echo "Waiting for Sail environment to boot up."
gp sync-await spin-is-ready
./vendor/bin/spin exec vite npm install
./vendor/bin/spin exec vite npm run dev
./vendor/bin/spin exec vite npm run dev -- --host
- name: Laravel Queue Worker, listening to code changes
command: |

View File

@ -37,7 +37,7 @@ public function verify() {
public function email_verify(EmailVerificationRequest $request) {
$request->fulfill();
$name = request()->user()?->name;
send_internal_notification("User {$name} verified their email address.");
// send_internal_notification("User {$name} verified their email address.");
return redirect(RouteServiceProvider::HOME);
}
public function forgot_password(Request $request) {

View File

@ -869,7 +869,7 @@ private function rolling_update()
$this->write_deployment_configurations();
$this->server = $this->original_server;
}
if (count($this->application->ports_mappings_array) > 0 || (bool) $this->application->settings->is_consistent_container_name_enabled || $this->pull_request_id !== 0 || str($this->application->custom_docker_run_options)->contains('--ip') || str($this->application->custom_docker_run_options)->contains('--ip6')) {
if (count($this->application->ports_mappings_array) > 0 || (bool) $this->application->settings->is_consistent_container_name_enabled || isset($this->application->settings->custom_internal_name) || $this->pull_request_id !== 0 || str($this->application->custom_docker_run_options)->contains('--ip') || str($this->application->custom_docker_run_options)->contains('--ip6')) {
$this->application_deployment_queue->addLogEntry("----------------------------------------");
if (count($this->application->ports_mappings_array) > 0) {
$this->application_deployment_queue->addLogEntry("Application has ports mapped to the host system, rolling update is not supported.");
@ -877,6 +877,9 @@ private function rolling_update()
if ((bool) $this->application->settings->is_consistent_container_name_enabled) {
$this->application_deployment_queue->addLogEntry("Consistent container name feature enabled, rolling update is not supported.");
}
if (isset($this->application->settings->custom_internal_name)) {
$this->application_deployment_queue->addLogEntry("Custom internal name is set, rolling update is not supported.");
}
if ($this->pull_request_id !== 0) {
$this->application->settings->is_consistent_container_name_enabled = true;
$this->application_deployment_queue->addLogEntry("Pull request deployment, rolling update is not supported.");
@ -1292,7 +1295,11 @@ private function generate_compose_file()
'restart' => RESTART_MODE,
'expose' => $ports,
'networks' => [
$this->destination->network,
$this->destination->network => [
'aliases' => [
$this->container_name
]
]
],
'mem_limit' => $this->application->limits_memory,
'memswap_limit' => $this->application->limits_memory_swap,
@ -1310,6 +1317,9 @@ private function generate_compose_file()
]
]
];
if (isset($this->application->settings->custom_internal_name)) {
$docker_compose['services'][$this->container_name]['networks'][$this->destination->network]['aliases'][] = $this->application->settings->custom_internal_name;
}
// if (str($this->saved_outputs->get('dotenv'))->isNotEmpty()) {
// if (data_get($docker_compose, "services.{$this->container_name}.env_file")) {
// $docker_compose['services'][$this->container_name]['env_file'][] = '.env';
@ -1519,95 +1529,8 @@ private function generate_local_persistent_volumes_only_volume_names()
return $local_persistent_volumes_names;
}
/*private function generate_environment_variables($ports)
{
$environment_variables = collect();
if ($this->pull_request_id === 0) {
foreach ($this->application->runtime_environment_variables as $env) {
// This is necessary because we have to escape the value of the environment variable
// but only if the environment variable is created after 4.0.0-beta.240
// when I implemented the escaping feature.
// Old environment variables are not escaped, because it could break the application
// as the application could expect the unescaped value.
if ($env->version === '4.0.0-beta.239') {
$real_value = $env->real_value;
} else {
$real_value = escapeEnvVariables($env->real_value);
}
if ($env->is_literal) {
$real_value = escapeDollarSign($real_value);
$environment_variables->push("$env->key='$real_value'");
} else {
$environment_variables->push("$env->key=$real_value");
}
}
foreach ($this->application->nixpacks_environment_variables as $env) {
if ($env->version === '4.0.0-beta.239') {
$real_value = $env->real_value;
} else {
$real_value = escapeEnvVariables($env->real_value);
}
if ($env->is_literal) {
$real_value = escapeDollarSign($real_value);
$environment_variables->push("$env->key='$real_value'");
} else {
$environment_variables->push("$env->key=$real_value");
}
}
} else {
foreach ($this->application->runtime_environment_variables_preview as $env) {
if ($env->version === '4.0.0-beta.239') {
$real_value = $env->real_value;
} else {
$real_value = escapeEnvVariables($env->real_value);
}
if ($env->is_literal) {
$real_value = escapeDollarSign($real_value);
$environment_variables->push("$env->key='$real_value'");
} else {
$environment_variables->push("$env->key=$real_value");
}
}
foreach ($this->application->nixpacks_environment_variables_preview as $env) {
if ($env->version === '4.0.0-beta.239') {
$real_value = $env->real_value;
} else {
$real_value = escapeEnvVariables($env->real_value);
}
if ($env->is_literal) {
$real_value = escapeDollarSign($real_value);
$environment_variables->push("$env->key='$real_value'");
} else {
$environment_variables->push("$env->key=$real_value");
}
}
}
// Add PORT if not exists, use the first port as default
if ($environment_variables->filter(fn ($env) => Str::of($env)->startsWith('PORT'))->isEmpty()) {
$environment_variables->push("PORT={$ports[0]}");
}
// Add HOST if not exists
if ($environment_variables->filter(fn ($env) => Str::of($env)->startsWith('HOST'))->isEmpty()) {
$environment_variables->push("HOST=0.0.0.0");
}
if ($environment_variables->filter(fn ($env) => Str::of($env)->startsWith('SOURCE_COMMIT'))->isEmpty()) {
if (!is_null($this->commit)) {
$environment_variables->push("SOURCE_COMMIT={$this->commit}");
} else {
$environment_variables->push("SOURCE_COMMIT=unknown");
}
}
ray($environment_variables->all());
return $environment_variables->all();
}*/
private function generate_healthcheck_commands()
{
// if ($this->application->dockerfile || $this->application->build_pack === 'dockerfile' || $this->application->build_pack === 'dockerimage') {
// // TODO: disabled HC because there are several ways to hc a simple docker image, hard to figure out a good way. Like some docker images (pocketbase) does not have curl.
// return 'exit 0';
// }
if (!$this->application->health_check_port) {
$health_check_port = $this->application->ports_exposes_array[0];
} else {
@ -1619,12 +1542,12 @@ private function generate_healthcheck_commands()
if ($this->application->health_check_path) {
$this->full_healthcheck_url = "{$this->application->health_check_method}: {$this->application->health_check_scheme}://{$this->application->health_check_host}:{$health_check_port}{$this->application->health_check_path}";
$generated_healthchecks_commands = [
"curl -s -X {$this->application->health_check_method} -f {$this->application->health_check_scheme}://{$this->application->health_check_host}:{$health_check_port}{$this->application->health_check_path} > /dev/null"
"curl -s -X {$this->application->health_check_method} -f {$this->application->health_check_scheme}://{$this->application->health_check_host}:{$health_check_port}{$this->application->health_check_path} > /dev/null || wget -q -O- {$this->application->health_check_scheme}://{$this->application->health_check_host}:{$health_check_port}{$this->application->health_check_path} > /dev/null || exit 1"
];
} else {
$this->full_healthcheck_url = "{$this->application->health_check_method}: {$this->application->health_check_scheme}://{$this->application->health_check_host}:{$health_check_port}/";
$generated_healthchecks_commands = [
"curl -s -X {$this->application->health_check_method} -f {$this->application->health_check_scheme}://{$this->application->health_check_host}:{$health_check_port}/"
"curl -s -X {$this->application->health_check_method} -f {$this->application->health_check_scheme}://{$this->application->health_check_host}:{$health_check_port}/ > /dev/null || wget -q -O- {$this->application->health_check_scheme}://{$this->application->health_check_host}:{$health_check_port}/ > /dev/null || exit 1"
];
}
return implode(' ', $generated_healthchecks_commands);
@ -1813,12 +1736,17 @@ private function stop_running_container(bool $force = false)
["docker rm -f $containerName >/dev/null 2>&1", "hidden" => true, "ignore_errors" => true],
);
});
if ($this->application->settings->is_consistent_container_name_enabled) {
if ($this->application->settings->is_consistent_container_name_enabled || isset($this->application->settings->custom_internal_name)) {
$this->execute_remote_command(
["docker rm -f $this->container_name >/dev/null 2>&1", "hidden" => true, "ignore_errors" => true],
);
}
} else {
if ($this->application->dockerfile || $this->application->build_pack === 'dockerfile' || $this->application->build_pack === 'dockerimage') {
$this->application_deployment_queue->addLogEntry("----------------------------------------");
$this->application_deployment_queue->addLogEntry("WARNING: Dockerfile or Docker Image based deployment detected. The healthcheck needs a curl or wget command to check the health of the application. Please make sure that it is available in the image or turn off healthcheck on Coolify's UI.");
$this->application_deployment_queue->addLogEntry("----------------------------------------");
}
$this->application_deployment_queue->addLogEntry("New container is not healthy, rolling back to the old container.");
$this->application_deployment_queue->update([
'status' => ApplicationDeploymentStatus::FAILED->value,

View File

@ -21,6 +21,7 @@ class Advanced extends Component
'application.settings.is_gpu_enabled' => 'boolean|required',
'application.settings.is_build_server_enabled' => 'boolean|required',
'application.settings.is_consistent_container_name_enabled' => 'boolean|required',
'application.settings.custom_internal_name' => 'string|nullable',
'application.settings.is_gzip_enabled' => 'boolean|required',
'application.settings.is_stripprefix_enabled' => 'boolean|required',
'application.settings.gpu_driver' => 'string|required',
@ -30,7 +31,8 @@ class Advanced extends Component
'application.settings.is_raw_compose_deployment_enabled' => 'boolean|required',
'application.settings.connect_to_docker_network' => 'boolean|required',
];
public function mount() {
public function mount()
{
$this->is_force_https_enabled = $this->application->isForceHttpsEnabled();
$this->is_gzip_enabled = $this->application->isGzipEnabled();
$this->is_stripprefix_enabled = $this->application->isStripprefixEnabled();
@ -65,7 +67,8 @@ public function instantSave()
$this->dispatch('success', 'Settings saved.');
$this->dispatch('configurationChanged');
}
public function submit() {
public function submit()
{
if ($this->application->settings->gpu_count && $this->application->settings->gpu_device_ids) {
$this->dispatch('error', 'You cannot set both GPU count and GPU device IDs.');
$this->application->settings->gpu_count = null;
@ -76,6 +79,16 @@ public function submit() {
$this->application->settings->save();
$this->dispatch('success', 'Settings saved.');
}
public function saveCustomName()
{
if (isset($this->application->settings->custom_internal_name)) {
$this->application->settings->custom_internal_name = str($this->application->settings->custom_internal_name)->slug()->value();
} else {
$this->application->settings->custom_internal_name = null;
}
$this->application->settings->save();
$this->dispatch('success', 'Custom name saved.');
}
public function render()
{
return view('livewire.project.application.advanced');

View File

@ -255,7 +255,6 @@ public function getWildcardDomain()
}
public function resetDefaultLabels()
{
ray('resetDefaultLabels');
$this->customLabels = str(implode("|", generateLabelsApplication($this->application)))->replace("|", "\n");
$this->ports_exposes = $this->application->ports_exposes;
@ -299,7 +298,10 @@ public function submit($showToaster = true)
}
if ($this->application->build_pack === 'dockercompose' && $this->initialDockerComposeLocation !== $this->application->docker_compose_location) {
$this->loadComposeFile();
$compose_return = $this->loadComposeFile();
if ($compose_return instanceof \Livewire\Features\SupportEvents\Event) {
return;
}
}
$this->validate();
if ($this->ports_exposes !== $this->application->ports_exposes) {

View File

@ -4,7 +4,6 @@
use App\Actions\Application\StopApplication;
use App\Events\ApplicationStatusChanged;
use App\Jobs\ComplexContainerStatusJob;
use App\Jobs\ContainerStatusJob;
use App\Jobs\ServerStatusJob;
use App\Models\Application;

View File

@ -98,7 +98,8 @@ public function load_branch()
(str($this->repository_url)->startsWith('https://') ||
str($this->repository_url)->startsWith('http://')) &&
!str($this->repository_url)->endsWith('.git') &&
!str($this->repository_url)->contains('github.com')
(!str($this->repository_url)->contains('github.com') ||
!str($this->repository_url)->contains('git.sr.ht'))
) {
$this->repository_url = $this->repository_url . '.git';
}

View File

@ -847,7 +847,7 @@ function loadComposeFile($isInit = false)
if (!$composeFileContent) {
$this->docker_compose_location = $initialDockerComposeLocation;
$this->save();
throw new \RuntimeException("Could not load base compose file from $workdir$composeFile");
throw new \RuntimeException("Docker Compose file not found at: $workdir$composeFile");
} else {
$this->docker_compose_raw = $composeFileContent;
$this->save();

View File

@ -1841,7 +1841,7 @@ function validate_dns_entry(string $fqdn, Server $server)
$dns_servers = data_get($settings, 'custom_dns_servers');
$dns_servers = str($dns_servers)->explode(',');
if ($server->id === 0) {
$ip = data_get($settings, 'public_ipv4') || data_get($settings, 'public_ipv6') || $server->ip;
$ip = data_get($settings, 'public_ipv4', data_get($settings, 'public_ipv6', $server->ip));
} else {
$ip = $server->ip;
}
@ -1920,7 +1920,6 @@ function check_domain_usage(ServiceApplication|Application|null $resource = null
$naked_domain = str($domain)->value();
if ($domains->contains($naked_domain)) {
if (data_get($resource, 'uuid')) {
ray($resource->uuid, $app->uuid);
if ($resource->uuid !== $app->uuid) {
throw new \RuntimeException("Domain $naked_domain is already in use by another resource called: <br><br>{$app->name}.");
}

View File

@ -7,7 +7,7 @@
// The release version of your application
// Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD'))
'release' => '4.0.0-beta.274',
'release' => '4.0.0-beta.275',
// When left empty or `null` the Laravel environment will be used
'environment' => config('app.env'),

View File

@ -1,3 +1,3 @@
<?php
return '4.0.0-beta.274';
return '4.0.0-beta.275';

View File

@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('application_settings', function (Blueprint $table) {
$table->string('custom_internal_name')->nullable();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('application_settings', function (Blueprint $table) {
$table->dropColumn('custom_internal_name');
});
}
};

30
lang/zh-cn.json Normal file
View File

@ -0,0 +1,30 @@
{
"auth.login": "登录",
"auth.login.azure": "使用 Microsoft 登录",
"auth.login.bitbucket": "使用 Bitbucket 登录",
"auth.login.github": "使用 GitHub 登录",
"auth.login.gitlab": "使用 Gitlab 登录",
"auth.login.google": "使用 Google 登录",
"auth.already_registered": "已经注册?",
"auth.confirm_password": "确认密码",
"auth.forgot_password": "忘记密码",
"auth.forgot_password_send_email": "发送密码重置邮件",
"auth.register_now": "注册",
"auth.logout": "退出登录",
"auth.register": "注册",
"auth.registration_disabled": "注册已禁用,请联系管理员",
"auth.reset_password": "重置密码",
"auth.failed": "这些凭据与我们的记录不符",
"auth.failed.callback": "处理第三方登录的回调时出错",
"auth.failed.password": "密码错误",
"auth.failed.email": "该账户未注册",
"auth.throttle": "登录次数过多,请在 :seconds 秒后重试",
"input.name": "用户名",
"input.email": "邮箱",
"input.password": "密码",
"input.password.again": "确认密码",
"input.code": "验证码",
"input.recovery_code": "恢复码",
"button.save": "保存",
"repository.url": "<span class='text-helper'>示例</span><br>对于公共代码仓库,请使用 <span class='text-helper'>https://...</span>。<br>对于私有代码仓库,请使用 <span class='text-helper'>git@...</span>。<br><br>https://github.com/coollabsio/coolify-examples <span class='text-helper'>main</span> 分支将被选择<br>https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify <span class='text-helper'>nodejs-fastify</span> 分支将被选择。<br>https://gitea.com/sedlav/expressjs.git <span class='text-helper'>main</span> 分支将被选择。<br>https://gitlab.com/andrasbacsai/nodejs-example.git <span class='text-helper'>main</span> 分支将被选择"
}

View File

@ -32,7 +32,7 @@ .select {
@apply block w-full py-1.5 rounded border-0 text-sm ring-1 ring-inset;
}
.input[type='password'] {
.input[type="password"] {
@apply pr-10;
}
@ -52,7 +52,6 @@ button[isHighlighted]:not(:disabled) {
@apply text-white bg-coollabs hover:bg-coollabs-100;
}
h1 {
@apply text-2xl font-bold dark:text-white;
}
@ -78,7 +77,7 @@ label {
}
table {
@apply min-w-full divide-y dark:divide-coolgray-200 divide-neutral-300 ;
@apply min-w-full divide-y dark:divide-coolgray-200 divide-neutral-300;
}
thead {
@ -117,7 +116,7 @@ .alert-error {
@apply flex items-center gap-2 text-error;
}
.tag {
@apply px-2 py-1 cursor-pointer box-description dark:bg-coolgray-100 dark:hover:bg-coolgray-300 bg-neutral-100 hover:bg-neutral-200
@apply px-2 py-1 cursor-pointer box-description dark:bg-coolgray-100 dark:hover:bg-coolgray-300 bg-neutral-100 hover:bg-neutral-200;
}
.add-tag {
@apply flex items-center px-2 text-xs cursor-pointer dark:text-neutral-500/20 text-neutral-500 group-hover:text-neutral-700 group-hover:dark:text-white dark:hover:bg-coolgray-300 hover:bg-neutral-200;
@ -135,7 +134,6 @@ .badge {
.badge-absolute {
@apply absolute top-0 right-0 w-2 h-2 border-none rounded-t-none rounded-r-none;
}
.badge-success {
@ -159,7 +157,7 @@ .menu {
}
.menu-item {
@apply flex items-center w-full gap-3 py-1 pl-2 dark:hover:bg-coolgray-100 dark:hover:text-white hover:bg-neutral-300;
@apply flex items-center w-full gap-3 px-2 py-1 text-sm sm:pr-0 dark:hover:bg-coolgray-100 dark:hover:text-white hover:bg-neutral-300 min-w-fit sm:min-w-64;
}
.menu-item-active {
@ -174,7 +172,6 @@ .icon {
@apply w-6 h-6 dark:hover:text-white;
}
.scrollbar {
@apply scrollbar-thumb-coollabs-100 dark:scrollbar-track-coolgray-200 scrollbar-track-neutral-200 scrollbar-w-2;
}
@ -188,7 +185,7 @@ .custom-modal {
}
.navbar-main {
@apply flex items-center h-10 gap-6 pb-2 border-b-2 border-solid dark:border-coolgray-200;
@apply flex flex-col gap-4 pb-2 border-b-2 border-solid h-fit md:flex-row justify-items-start sm:justify-between dark:border-coolgray-200 md:items-center;
}
.loading {
@ -203,20 +200,19 @@ .box {
@apply relative flex lg:flex-row flex-col p-2 transition-colors cursor-pointer min-h-[4rem] dark:bg-coolgray-100 bg-white border text-black dark:text-white hover:text-black border-neutral-200 dark:border-black hover:bg-neutral-100 dark:hover:bg-coollabs-100 dark:hover:text-white hover:no-underline;
}
.box-boarding {
@apply flex lg:flex-row flex-col p-2 transition-colors cursor-pointer min-h-[4rem] dark:bg-coolgray-100 dark:text-white bg-neutral-50 border border-neutral-200 dark:border-black hover:bg-neutral-100 dark:hover:bg-coollabs-100 dark:hover:text-white hover:text-black hover:no-underline text-black ;
@apply flex lg:flex-row flex-col p-2 transition-colors cursor-pointer min-h-[4rem] dark:bg-coolgray-100 dark:text-white bg-neutral-50 border border-neutral-200 dark:border-black hover:bg-neutral-100 dark:hover:bg-coollabs-100 dark:hover:text-white hover:text-black hover:no-underline text-black;
}
.box-without-bg {
@apply flex p-2 transition-colors dark:hover:text-white hover:no-underline min-h-[4rem] border border-neutral-200 dark:border-black;
}
.box-without-bg-without-border {
@apply flex p-2 transition-colors dark:hover:text-white hover:no-underline min-h-[4rem] ;
@apply flex p-2 transition-colors dark:hover:text-white hover:no-underline min-h-[4rem];
}
.on-box {
@apply rounded hover:bg-neutral-300 dark:hover:bg-coolgray-500/20;
}
.box-title {
@apply font-bold text-black dark:text-white group-hover:dark:text-white;
}

View File

@ -4,8 +4,7 @@
<a class="flex items-center mb-6 text-5xl font-extrabold tracking-tight text-gray-900 dark:text-white">
Coolify
</a>
<div
class="w-full bg-white rounded-lg shadow md:mt-0 sm:max-w-md xl:p-0 dark:bg-base">
<div class="w-full bg-white rounded-lg shadow md:mt-0 sm:max-w-md xl:p-0 dark:bg-base">
<div class="p-6 space-y-4 md:space-y-6 sm:p-8">
<h1 class="text-xl font-bold leading-tight tracking-tight text-gray-900 md:text-2xl dark:text-white">
Create an account

View File

@ -84,7 +84,7 @@
<livewire:switch-team />
</div>
<ul role="list" class="flex flex-col flex-1 gap-y-7">
<li class="flex-1 ">
<li class="flex-1 overflow-x-hidden">
<ul role="list" class="flex flex-col h-full space-y-1.5">
@if (isSubscribed() || !isCloud())
<li>

View File

@ -1,18 +1,20 @@
<div class="pb-6">
<h1>Notifications</h1>
<div class="subtitle">Get notified about your infrastructure.</div>
<nav class="navbar-main">
<a class="{{ request()->routeIs('notifications.email') ? 'dark:text-white' : '' }}"
href="{{ route('notifications.email') }}">
<button>Email</button>
</a>
<a class="{{ request()->routeIs('notifications.telegram') ? 'dark:text-white' : '' }}"
href="{{ route('notifications.telegram') }}">
<button>Telegram</button>
</a>
<a class="{{ request()->routeIs('notifications.discord') ? 'dark:text-white' : '' }}"
href="{{ route('notifications.discord') }}">
<button>Discord</button>
</a>
</nav>
<div class="navbar-main">
<nav class="flex items-center gap-6 min-h-10">
<a class="{{ request()->routeIs('notifications.email') ? 'dark:text-white' : '' }}"
href="{{ route('notifications.email') }}">
<button>Email</button>
</a>
<a class="{{ request()->routeIs('notifications.telegram') ? 'dark:text-white' : '' }}"
href="{{ route('notifications.telegram') }}">
<button>Telegram</button>
</a>
<a class="{{ request()->routeIs('notifications.discord') ? 'dark:text-white' : '' }}"
href="{{ route('notifications.discord') }}">
<button>Discord</button>
</a>
</nav>
</div>
</div>

View File

@ -8,7 +8,7 @@
x-transition:leave-end="translate-y-full" x-init="setTimeout(() => { bannerVisible = true }, bannerVisibleAfter);"
class="fixed bottom-0 right-0 h-auto duration-300 ease-out px-5 pb-5 max-w-[46rem] z-[999]" x-cloak>
<div
class="flex flex-col items-center justify-between w-full h-full max-w-4xl p-6 mx-auto bg-white border shadow-lg lg:border-t dark:border-coolgray-300 dark:bg-coolgray-100/40 hover:dark:bg-coolgray-100/100 lg:p-8 lg:flex-row sm:rounded">
class="flex flex-col items-center justify-between w-full h-full max-w-4xl p-6 mx-auto bg-white border shadow-lg lg:border-t dark:border-coolgray-300 dark:bg-coolgray-100 hover:dark:bg-coolgray-100 lg:p-8 lg:flex-row sm:rounded">
<div
class="flex flex-col items-start h-full pb-0 text-xs lg:items-center lg:flex-row lg:pr-6 lg:space-x-5 dark:text-neutral-300 ">
@if (isset($icon))

View File

@ -1,35 +1,34 @@
<nav class="flex pt-2 pb-10">
<ol class="flex items-center">
<ol class="flex items-center flex-wrap gap-y-1">
<li class="inline-flex items-center">
<a wire:navigate class="text-xs truncate lg:text-sm"
href="{{ route('project.show', ['project_uuid' => $this->parameters['project_uuid']]) }}">
{{ data_get($resource, 'environment.project.name', 'Undefined Name') }}</a>
</li>
<li>
<div class="flex items-center">
<svg aria-hidden="true" class="w-4 h-4 mx-1 font-bold dark:text-warning" fill="currentColor" viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd"
d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"
clip-rule="evenodd"></path>
</svg>
<a class="text-xs truncate lg:text-sm"
href="{{ route('project.resource.index', ['environment_name' => $this->parameters['environment_name'], 'project_uuid' => $this->parameters['project_uuid']]) }}">{{ $this->parameters['environment_name'] }}</a>
</div>
</li>
<li>
<div class="flex items-center">
<a wire:navigate class="text-xs truncate lg:text-sm"
href="{{ route('project.show', ['project_uuid' => $this->parameters['project_uuid']]) }}">
{{ data_get($resource, 'environment.project.name', 'Undefined Name') }}</a>
<svg aria-hidden="true" class="w-4 h-4 mx-1 font-bold dark:text-warning" fill="currentColor"
viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd"
d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"
clip-rule="evenodd"></path>
</svg>
<span class="text-xs truncate lg:text-sm">{{ data_get($resource, 'name') }}</span>
</div>
</li>
<li>
<div class="flex items-center">
<a class="text-xs truncate lg:text-sm"
href="{{ route('project.resource.index', ['environment_name' => $this->parameters['environment_name'], 'project_uuid' => $this->parameters['project_uuid']]) }}">{{ $this->parameters['environment_name'] }}</a>
<svg aria-hidden="true" class="w-4 h-4 mx-1 font-bold dark:text-warning" fill="currentColor"
viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd"
d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"
clip-rule="evenodd"></path>
</svg>
</div>
</li>
<li>
<div class="flex items-center">
<span class="text-xs truncate lg:text-sm">{{ data_get($resource, 'name') }}</span>
<svg aria-hidden="true" class="w-4 h-4 mx-1 font-bold dark:text-warning" fill="currentColor"
viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd"

View File

@ -1,12 +1,14 @@
<div class="pb-6">
<h1>Security</h1>
<div class="subtitle">Security related settings.</div>
<nav class="navbar-main">
<a href="{{ route('security.private-key.index') }}">
<button>Private Keys</button>
</a>
<a href="{{ route('security.api-tokens') }}">
<button>API tokens</button>
</a>
</nav>
<div class="navbar-main">
<nav class="flex items-center gap-6 scrollbar min-h-10">
<a href="{{ route('security.private-key.index') }}">
<button>Private Keys</button>
</a>
<a href="{{ route('security.api-tokens') }}">
<button>API tokens</button>
</a>
</nav>
</div>
</div>

View File

@ -5,47 +5,49 @@
<livewire:server.proxy.status :server="$server" />
</div>
<div class="subtitle">{{ data_get($server, 'name') }}.</div>
<nav class="navbar-main">
<a class="{{ request()->routeIs('server.show') ? 'dark:text-white' : '' }}"
href="{{ route('server.show', [
'server_uuid' => data_get($parameters, 'server_uuid'),
]) }}">
<button>General</button>
</a>
<a class="{{ request()->routeIs('server.private-key') ? 'dark:text-white' : '' }}"
href="{{ route('server.private-key', [
'server_uuid' => data_get($parameters, 'server_uuid'),
]) }}">
<button>Private Key</button>
</a>
<a class="{{ request()->routeIs('server.resources') ? 'dark:text-white' : '' }}"
href="{{ route('server.resources', [
'server_uuid' => data_get($parameters, 'server_uuid'),
]) }}">
<button>Resources</button>
</a>
@if (!$server->isSwarmWorker() && !$server->settings->is_build_server)
<a class="{{ request()->routeIs('server.proxy') ? 'dark:text-white' : '' }}"
href="{{ route('server.proxy', [
<div class="navbar-main">
<nav class="flex items-center gap-6 overflow-x-scroll sm:overflow-x-hidden scrollbar min-h-10 whitespace-nowrap">
<a class="{{ request()->routeIs('server.show') ? 'dark:text-white' : '' }}"
href="{{ route('server.show', [
'server_uuid' => data_get($parameters, 'server_uuid'),
]) }}">
<button>Proxy</button>
<button>General</button>
</a>
<a class="{{ request()->routeIs('server.destinations') ? 'dark:text-white' : '' }}"
href="{{ route('server.destinations', [
<a class="{{ request()->routeIs('server.private-key') ? 'dark:text-white' : '' }}"
href="{{ route('server.private-key', [
'server_uuid' => data_get($parameters, 'server_uuid'),
]) }}">
<button>Destinations</button>
<button>Private Key</button>
</a>
<a class="{{ request()->routeIs('server.log-drains') ? 'dark:text-white' : '' }}"
href="{{ route('server.log-drains', [
<a class="{{ request()->routeIs('server.resources') ? 'dark:text-white' : '' }}"
href="{{ route('server.resources', [
'server_uuid' => data_get($parameters, 'server_uuid'),
]) }}">
<button>Log Drains</button>
<button>Resources</button>
</a>
@endif
<div class="flex-1"></div>
<livewire:server.proxy.deploy :server="$server" />
</nav>
@if (!$server->isSwarmWorker() && !$server->settings->is_build_server)
<a class="{{ request()->routeIs('server.proxy') ? 'dark:text-white' : '' }}"
href="{{ route('server.proxy', [
'server_uuid' => data_get($parameters, 'server_uuid'),
]) }}">
<button>Proxy</button>
</a>
<a class="{{ request()->routeIs('server.destinations') ? 'dark:text-white' : '' }}"
href="{{ route('server.destinations', [
'server_uuid' => data_get($parameters, 'server_uuid'),
]) }}">
<button>Destinations</button>
</a>
<a class="{{ request()->routeIs('server.log-drains') ? 'dark:text-white' : '' }}"
href="{{ route('server.log-drains', [
'server_uuid' => data_get($parameters, 'server_uuid'),
]) }}">
<button>Log Drains</button>
</a>
@endif
</nav>
<div class="order-first sm:order-last">
<livewire:server.proxy.deploy :server="$server" />
</div>
</div>
</div>

View File

@ -1,17 +1,19 @@
<div class="pb-5">
<h1>Settings</h1>
<div class="subtitle">Instance wide settings for Coolify.</div>
<nav class="navbar-main">
<a class="{{ request()->routeIs('settings.index') ? 'dark:text-white' : '' }}"
href="{{ route('settings.index') }}">
<button>Configuration</button>
</a>
@if (isCloud())
<a class="{{ request()->routeIs('settings.license') ? 'dark:text-white' : '' }}"
href="{{ route('settings.license') }}">
<button>Resale License</button>
<div class="navbar-main">
<nav class="flex items-center gap-6 min-h-10 whitespace-nowrap">
<a class="{{ request()->routeIs('settings.index') ? 'dark:text-white' : '' }}"
href="{{ route('settings.index') }}">
<button>Configuration</button>
</a>
@endif
<div class="flex-1"></div>
</nav>
@if (isCloud())
<a class="{{ request()->routeIs('settings.license') ? 'dark:text-white' : '' }}"
href="{{ route('settings.license') }}">
<button>Resale License</button>
</a>
@endif
<div class="flex-1"></div>
</nav>
</div>
</div>

View File

@ -6,14 +6,16 @@
</x-modal-input>
</div>
<div class="subtitle">Team wide configurations.</div>
<nav class="navbar-main">
<a class="{{ request()->routeIs('team.index') ? 'dark:text-white' : '' }}" href="{{ route('team.index') }}">
<button>General</button>
</a>
<a class="{{ request()->routeIs('team.member.index') ? 'dark:text-white' : '' }}"
href="{{ route('team.member.index') }}">
<button>Members</button>
</a>
<div class="flex-1"></div>
</nav>
<div class="navbar-main">
<nav class="flex items-center gap-6 min-h-10">
<a class="{{ request()->routeIs('team.index') ? 'dark:text-white' : '' }}" href="{{ route('team.index') }}">
<button>General</button>
</a>
<a class="{{ request()->routeIs('team.member.index') ? 'dark:text-white' : '' }}"
href="{{ route('team.member.index') }}">
<button>Members</button>
</a>
<div class="flex-1"></div>
</nav>
</div>
</div>

View File

@ -1,10 +1,10 @@
<div>
<div class="flex flex-col">
<div class="flex flex-col md:w-96">
<div class="flex items-center gap-2">
<h2>Advanced</h2>
</div>
<div>Advanced configuration for your application.</div>
<div class="flex flex-col gap-1 pt-4 md:w-96">
<div class="flex flex-col gap-1 pt-4">
<h3>General</h3>
@if ($application->git_based())
<x-forms.checkbox helper="Automatically deploy new commits based on Git webhooks." instantSave
@ -16,10 +16,6 @@
<x-forms.checkbox
helper="Your application will be available only on https if your domain starts with https://..."
instantSave id="is_force_https_enabled" label="Force Https" />
<x-forms.checkbox
helper="The deployed container will have the same name ({{ $application->uuid }}). <span class='font-bold dark:text-warning'>You will lose the rolling update feature!</span>"
instantSave id="application.settings.is_consistent_container_name_enabled"
label="Consistent Container Names" />
<x-forms.checkbox label="Enable gzip compression"
helper="You can disable gzip compression if you want. Some services are compressing data by default. In this case, you do not need this."
instantSave id="is_gzip_enabled" />
@ -30,13 +26,22 @@
label="Raw Compose Deployment"
helper="WARNING: Advanced use cases only. Your docker compose file will be deployed as-is. Nothing is modified by Coolify. You need to configure the proxy parts. More info in the <a class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/compose#raw-docker-compose-deployment'>documentation.</a>" />
@endif
<h3>Container Names</h3>
<x-forms.checkbox
helper="The deployed container will have the same name ({{ $application->uuid }}). <span class='font-bold dark:text-warning'>You will lose the rolling update feature!</span>"
instantSave id="application.settings.is_consistent_container_name_enabled"
label="Consistent Container Names" />
<form class="flex items-end gap-2 pl-2" wire:submit.prevent='saveCustomName'>
<x-forms.input
helper="You can add a custom internal name for your container. This name will be used in the internal network. <br><br>The name will be converted to slug format when you save it. <span class='font-bold dark:text-warning'>You will lose the rolling update feature!</span>"
instantSave id="application.settings.custom_internal_name" label="Add Custom Internal Name" />
<x-forms.button type="submit">Save</x-forms.button>
</form>
@if ($application->build_pack === 'dockercompose')
<h3>Network</h3>
<div class="w-96">
<x-forms.checkbox instantSave id="application.settings.connect_to_docker_network"
label="Connect To Predefined Network"
helper="By default, you do not reach the Coolify defined networks.<br>Starting a docker compose based resource will have an internal network. <br>If you connect to a Coolify defined network, you maybe need to use different internal DNS names to connect to a resource.<br><br>For more information, check <a class='underline dark:text-white' target='_blank' href='https://coolify.io/docs/knowledge-base/docker/compose#connect-to-predefined-networks'>this</a>." />
</div>
<x-forms.checkbox instantSave id="application.settings.connect_to_docker_network"
label="Connect To Predefined Network"
helper="By default, you do not reach the Coolify defined networks.<br>Starting a docker compose based resource will have an internal network. <br>If you connect to a Coolify defined network, you maybe need to use different internal DNS names to connect to a resource.<br><br>For more information, check <a class='underline dark:text-white' target='_blank' href='https://coolify.io/docs/knowledge-base/docker/compose#connect-to-predefined-networks'>this</a>." />
@endif
@if (!$application->settings->is_raw_compose_deployment_enabled)
<h3>Logs</h3>
@ -60,16 +65,14 @@
@endif
<form wire:submit="submit">
@if ($application->build_pack !== 'dockercompose')
<div class="w-96">
<x-forms.checkbox
helper="Enable GPU usage for this application. More info <a href='https://docs.docker.com/compose/gpu-support/' class='underline dark:text-white' target='_blank'>here</a>."
instantSave id="application.settings.is_gpu_enabled" label="Attach GPU" />
@if ($application->settings->is_gpu_enabled)
<h5>GPU Settings</h5>
<x-forms.checkbox
helper="Enable GPU usage for this application. More info <a href='https://docs.docker.com/compose/gpu-support/' class='underline dark:text-white' target='_blank'>here</a>."
instantSave id="application.settings.is_gpu_enabled" label="Attach GPU" />
@if ($application->settings->is_gpu_enabled)
<h5>GPU Settings</h5>
<x-forms.button type="submit">Save</x-forms.button>
@endif
</div>
<x-forms.button type="submit">Save</x-forms.button>
@endif
@endif
@if ($application->settings->is_gpu_enabled)
<div class="flex flex-col w-full gap-2 p-2 xl:flex-row">

View File

@ -2,8 +2,8 @@
<h1>Configuration</h1>
<livewire:project.shared.configuration-checker :resource="$application" />
<livewire:project.application.heading :application="$application" />
<div x-data="{ activeTab: window.location.hash ? window.location.hash.substring(1) : 'general' }" class="flex h-full pt-6">
<div class="flex flex-col gap-2 xl:w-48">
<div x-data="{ activeTab: window.location.hash ? window.location.hash.substring(1) : 'general' }" class="flex flex-col h-full gap-8 pt-6 sm:flex-row">
<div class="flex gap-4 overflow-x-scroll sm:gap-2 sm:flex-col sm:overflow-x-hidden scrollbar whitespace-nowrap">
<a class="menu-item" :class="activeTab === 'general' && 'menu-item-active'"
@click.prevent="activeTab = 'general'; window.location.hash = 'general'" href="#">General</a>
@if ($application->destination->server->isSwarm())
@ -78,7 +78,7 @@
@click.prevent="activeTab = 'danger'; window.location.hash = 'danger'" href="#">Danger Zone
</a>
</div>
<div class="w-full pl-8">
<div class="w-full">
<div x-cloak x-show="activeTab === 'general'" class="h-full">
<livewire:project.application.general :application="$application" />
</div>

View File

@ -116,7 +116,7 @@ class="underline" href="https://coolify.io/docs/knowledge-base/docker/registry"
@if ($application->build_pack !== 'dockerimage')
<h3 class="pt-8">Build</h3>
@if ($application->build_pack !== 'dockercompose')
<div class="w-96">
<div class="max-w-96">
<x-forms.checkbox
helper="Use a build server to build your application. You can configure your build server in the Server settings. This is experimental. For more info, check the <a href='https://coolify.io/docs/knowledge-base/server/build-server' class='underline' target='_blank'>documentation</a>."
instantSave id="application.settings.is_build_server_enabled"
@ -242,7 +242,10 @@ class="underline" href="https://coolify.io/docs/knowledge-base/docker/registry"
@endif
</div>
<x-forms.textarea label="Container Labels" rows="15" id="customLabels"></x-forms.textarea>
<x-forms.button wire:click="resetDefaultLabels">Reset to Coolify Generated Labels</x-forms.button>
<x-modal-confirmation buttonFullWidth action="resetDefaultLabels" buttonTitle="Reset to Coolify Generated Labels">
Are you sure you want to reset the labels to Coolify generated labels? <br>It could break the proxy configuration after you restart the container.
</x-modal-confirmation>
@endif
<h3 class="pt-8">Pre/Post Deployment Commands</h3>

View File

@ -1,30 +1,33 @@
<nav wire:poll.5000ms="check_status">
<x-resources.breadcrumbs :resource="$application" :parameters="$parameters" />
<div class="navbar-main">
<a href="{{ route('project.application.configuration', $parameters) }}">
Configuration
</a>
<a href="{{ route('project.application.deployment.index', $parameters) }}">
<button>Deployments</button>
</a>
<a href="{{ route('project.application.logs', $parameters) }}">
<button>Logs</button>
</a>
@if (!$application->destination->server->isSwarm())
<a href="{{ route('project.application.command', $parameters) }}">
<button>Command</button>
<nav class="flex items-center flex-shrink-0 gap-6 scrollbar min-h-10 whitespace-nowrap">
<a href="{{ route('project.application.configuration', $parameters) }}">
Configuration
</a>
@endif
<x-applications.links :application="$application" />
<div class="flex-1"></div>
<div class="flex items-center gap-2">
<a href="{{ route('project.application.deployment.index', $parameters) }}">
<button>Deployments</button>
</a>
<a href="{{ route('project.application.logs', $parameters) }}">
<button>Logs</button>
</a>
@if (!$application->destination->server->isSwarm())
<a href="{{ route('project.application.command', $parameters) }}">
<button>Command</button>
</a>
@endif
<x-applications.links :application="$application" />
</nav>
<div class="flex flex-wrap items-center gap-2">
@if ($application->build_pack === 'dockercompose' && is_null($application->docker_compose_raw))
<div>Please load a Compose file.</div>
@else
@if (!$application->destination->server->isSwarm())
<div>
<x-applications.advanced :application="$application" />
</div>
@endif
<div class="flex gap-2">
<div class="flex flex-wrap gap-2">
@if (!str($application->status)->startsWith('exited'))
@if (!$application->destination->server->isSwarm())
<x-forms.button title="With rolling update if possible" wire:click='deploy'>
@ -100,6 +103,7 @@
</div>
@endif
</div>
</div>
@script
<script>

View File

@ -2,53 +2,53 @@
<h1>Configuration</h1>
<livewire:project.shared.configuration-checker :resource="$database" />
<livewire:project.database.heading :database="$database" />
<div x-data="{ activeTab: window.location.hash ? window.location.hash.substring(1) : 'general' }" class="flex h-full pt-6">
<div class="flex flex-col gap-4 min-w-fit">
<a :class="activeTab === 'general' && 'dark:text-white'"
<div x-data="{ activeTab: window.location.hash ? window.location.hash.substring(1) : 'general' }" class="flex flex-col h-full gap-8 pt-6 sm:flex-row">
<div class="flex gap-4 overflow-x-scroll sm:gap-2 sm:flex-col sm:overflow-x-hidden scrollbar whitespace-nowrap">
<a class="menu-item" :class="activeTab === 'general' && 'menu-item-active'"
@click.prevent="activeTab = 'general';
window.location.hash = 'general'"
href="#">General</a>
<a :class="activeTab === 'environment-variables' && 'dark:text-white'"
<a class="menu-item" :class="activeTab === 'environment-variables' && 'menu-item-active'"
@click.prevent="activeTab = 'environment-variables'; window.location.hash = 'environment-variables'"
href="#">Environment
Variables</a>
<a :class="activeTab === 'servers' && 'dark:text-white'"
<a class="menu-item" :class="activeTab === 'servers' && 'menu-item-active'"
@click.prevent="activeTab = 'servers';
window.location.hash = 'servers'"
href="#">Servers
</a>
<a :class="activeTab === 'storages' && 'dark:text-white'"
<a class="menu-item" :class="activeTab === 'storages' && 'menu-item-active'"
@click.prevent="activeTab = 'storages';
window.location.hash = 'storages'"
href="#">Storages
</a>
<a :class="activeTab === 'import' && 'dark:text-white'"
<a class="menu-item" :class="activeTab === 'import' && 'menu-item-active'"
@click.prevent="activeTab = 'import';
window.location.hash = 'import'" href="#">Import
Backup
</a>
<a :class="activeTab === 'webhooks' && 'dark:text-white'"
<a class="menu-item" :class="activeTab === 'webhooks' && 'menu-item-active'"
@click.prevent="activeTab = 'webhooks'; window.location.hash = 'webhooks'" href="#">Webhooks
</a>
<a :class="activeTab === 'resource-limits' && 'dark:text-white'"
<a class="menu-item" :class="activeTab === 'resource-limits' && 'menu-item-active'"
@click.prevent="activeTab = 'resource-limits';
window.location.hash = 'resource-limits'"
href="#">Resource Limits
</a>
<a :class="activeTab === 'resource-operations' && 'dark:text-white'"
<a class="menu-item" :class="activeTab === 'resource-operations' && 'menu-item-active'"
@click.prevent="activeTab = 'resource-operations'; window.location.hash = 'resource-operations'"
href="#">Resource Operations
</a>
<a :class="activeTab === 'tags' && 'dark:text-white'"
<a class="menu-item" :class="activeTab === 'tags' && 'menu-item-active'"
@click.prevent="activeTab = 'tags'; window.location.hash = 'tags'" href="#">Tags
</a>
<a :class="activeTab === 'danger' && 'dark:text-white'"
<a class="menu-item" :class="activeTab === 'danger' && 'menu-item-active'"
@click.prevent="activeTab = 'danger';
window.location.hash = 'danger'"
href="#">Danger Zone
</a>
</div>
<div class="w-full pl-8">
<div class="w-full">
<div x-cloak x-show="activeTab === 'general'" class="h-full">
@if ($database->type() === 'standalone-postgresql')
<livewire:project.database.postgresql.general :database="$database" />

View File

@ -7,67 +7,70 @@
</x-slot:content>
</x-slide-over>
<div class="navbar-main">
<a class="{{ request()->routeIs('project.database.configuration') ? 'dark:text-white' : '' }}"
href="{{ route('project.database.configuration', $parameters) }}">
<button>Configuration</button>
</a>
<a class="{{ request()->routeIs('project.database.command') ? 'dark:text-white' : '' }}"
href="{{ route('project.database.command', $parameters) }}">
<button>Execute Command</button>
</a>
<a class="{{ request()->routeIs('project.database.logs') ? 'dark:text-white' : '' }}"
href="{{ route('project.database.logs', $parameters) }}">
<button>Logs</button>
</a>
@if (
$database->getMorphClass() === 'App\Models\StandalonePostgresql' ||
$database->getMorphClass() === 'App\Models\StandaloneMongodb' ||
$database->getMorphClass() === 'App\Models\StandaloneMysql' ||
$database->getMorphClass() === 'App\Models\StandaloneMariadb')
<a class="{{ request()->routeIs('project.database.backup.index') ? 'dark:text-white' : '' }}"
href="{{ route('project.database.backup.index', $parameters) }}">
<button>Backups</button>
<nav class="flex items-center flex-shrink-0 gap-6 overflow-x-scroll sm:overflow-x-hidden scrollbar min-h-10 whitespace-nowrap">
<a class="{{ request()->routeIs('project.database.configuration') ? 'dark:text-white' : '' }}"
href="{{ route('project.database.configuration', $parameters) }}">
<button>Configuration</button>
</a>
@endif
<div class="flex-1"></div>
@if (!str($database->status)->startsWith('exited'))
<x-modal-confirmation @click="$wire.dispatch('stopEvent')">
<x-slot:button-title>
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error" viewBox="0 0 24 24"
stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round"
<a class="{{ request()->routeIs('project.database.command') ? 'dark:text-white' : '' }}"
href="{{ route('project.database.command', $parameters) }}">
<button>Execute Command</button>
</a>
<a class="{{ request()->routeIs('project.database.logs') ? 'dark:text-white' : '' }}"
href="{{ route('project.database.logs', $parameters) }}">
<button>Logs</button>
</a>
@if (
$database->getMorphClass() === 'App\Models\StandalonePostgresql' ||
$database->getMorphClass() === 'App\Models\StandaloneMongodb' ||
$database->getMorphClass() === 'App\Models\StandaloneMysql' ||
$database->getMorphClass() === 'App\Models\StandaloneMariadb')
<a class="{{ request()->routeIs('project.database.backup.index') ? 'dark:text-white' : '' }}"
href="{{ route('project.database.backup.index', $parameters) }}">
<button>Backups</button>
</a>
@endif
</nav>
<div class="flex flex-wrap items-center gap-2">
@if (!str($database->status)->startsWith('exited'))
<x-modal-confirmation @click="$wire.dispatch('stopEvent')">
<x-slot:button-title>
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error" viewBox="0 0 24 24"
stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round"
stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M6 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z">
</path>
<path d="M14 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z">
</path>
</svg>
Stop
</x-slot:button-title>
This database will be stopped. <br>Please think again.
</x-modal-confirmation>
@else
<button @click="$wire.dispatch('startEvent')" class="gap-2 button">
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M6 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z">
</path>
<path d="M14 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z">
</path>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M7 4v16l13 -8z" />
</svg>
Stop
</x-slot:button-title>
This database will be stopped. <br>Please think again.
</x-modal-confirmation>
@else
<button @click="$wire.dispatch('startEvent')" class="gap-2 button">
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M7 4v16l13 -8z" />
</svg>
Start
</button>
@endif
@script
<script>
$wire.$on('startEvent', () => {
window.dispatchEvent(new CustomEvent('startdatabase'));
$wire.$call('start');
});
$wire.$on('stopEvent', () => {
$wire.$dispatch('info', 'Stopping database.');
$wire.$call('stop');
});
</script>
@endscript
Start
</button>
@endif
@script
<script>
$wire.$on('startEvent', () => {
window.dispatchEvent(new CustomEvent('startdatabase'));
$wire.$call('start');
});
$wire.$on('stopEvent', () => {
$wire.$dispatch('info', 'Stopping database.');
$wire.$call('stop');
});
</script>
@endscript
</div>
</div>
</nav>

View File

@ -20,7 +20,7 @@
Save
</x-forms.button>
</div>
<div class="flex gap-2">
<div class="flex gap-2 flex-wrap sm:flex-nowrap">
<x-forms.input label="Name" id="database.name" />
<x-forms.input label="Description" id="database.description" />
<x-forms.input label="Image" id="database.image" required
@ -28,7 +28,7 @@
</div>
@if ($database->started_at)
<div class="flex gap-2">
<div class="flex gap-2 flex-wrap sm:flex-nowrap">
<x-forms.input label="Initial Username" id="database.postgres_user" placeholder="If empty: postgres"
readonly helper="You can only change this in the database." />
<x-forms.input label="Initial Password" id="database.postgres_password" type="password" required

View File

@ -6,22 +6,30 @@
<livewire:project.delete-environment :disabled="!$environment->isEmpty()" :environment_id="$environment->id" />
</div>
<nav class="flex pt-2 pb-10">
<ol class="flex items-center">
<ol class="flex items-center flex-wrap gap-y-1">
<li class="inline-flex items-center">
<a class="text-xs truncate lg:text-sm"
href="{{ route('project.show', ['project_uuid' => data_get($parameters, 'project_uuid')]) }}">
{{ $project->name }}</a>
</li>
<li>
<div class="flex items-center">
<a class="text-xs truncate lg:text-sm"
href="{{ route('project.show', ['project_uuid' => data_get($parameters, 'project_uuid')]) }}">
{{ $project->name }}</a>
<svg aria-hidden="true" class="w-4 h-4 mx-1 font-bold dark:text-warning" fill="currentColor"
viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd"
d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"
clip-rule="evenodd"></path>
</svg>
</div>
</li>
<li>
<div class="flex items-center">
<a class="text-xs truncate lg:text-sm"
href="{{ route('project.resource.index', ['environment_name' => data_get($parameters, 'environment_name'), 'project_uuid' => data_get($parameters, 'project_uuid')]) }}">{{ data_get($parameters, 'environment_name') }}</a>
<svg aria-hidden="true" class="w-4 h-4 mx-1 font-bold dark:text-warning" fill="currentColor"
viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd"
d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"
clip-rule="evenodd"></path>
</svg>
</div>
</li>
<li>

View File

@ -71,7 +71,8 @@ class="items-center justify-center box">+ Add New Resource</a>
<div class="max-w-full px-4 truncate box-description" x-text="item.fqdn"></div>
</div>
</a>
<div class="flex flex-wrap gap-1 pt-1 group-hover:dark:text-white group-hover:text-black group min-h-6">
<div
class="flex flex-wrap gap-1 pt-1 group-hover:dark:text-white group-hover:text-black group min-h-6">
<template x-for="tag in item.tags">
<div class="tag" @click.prevent="gotoTag(tag.name)" x-text="tag.name"></div>
</template>

View File

@ -1,45 +1,45 @@
<div x-data="{ activeTab: window.location.hash ? window.location.hash.substring(1) : 'service-stack' }" x-init="$wire.check_status" wire:poll.5000ms="check_status">
<livewire:project.service.navbar :service="$service" :parameters="$parameters" :query="$query" />
<div class="flex h-full pt-6">
<div class="flex flex-col items-start gap-4 min-w-fit">
<a target="_blank" href="{{ $service->documentation() }}">Documentation <x-external-link /></a>
<a :class="activeTab === 'service-stack' && 'dark:text-white'"
<div class="flex flex-col h-full gap-8 pt-6 sm:flex-row">
<div class="flex flex-col items-start gap-2 min-w-fit">
<a class="menu-item sm:min-w-fit" target="_blank" href="{{ $service->documentation() }}">Documentation <x-external-link /></a>
<a class="menu-item sm:min-w-fit"
@click.prevent="activeTab = 'service-stack';
window.location.hash = 'service-stack'"
href="#">Service Stack</a>
<a :class="activeTab === 'environment-variables' && 'dark:text-white'"
<a class="menu-item sm:min-w-fit"
@click.prevent="activeTab = 'environment-variables'; window.location.hash = 'environment-variables'"
href="#">Environment
Variables</a>
<a :class="activeTab === 'storages' && 'dark:text-white'"
<a class="menu-item sm:min-w-fit"
@click.prevent="activeTab = 'storages';
window.location.hash = 'storages'"
href="#">Storages</a>
<a :class="activeTab === 'execute-command' && 'dark:text-white'"
<a class="menu-item sm:min-w-fit"
@click.prevent="activeTab = 'execute-command';
window.location.hash = 'execute-command'"
href="#">Execute Command</a>
<a :class="activeTab === 'logs' && 'dark:text-white'"
<a class="menu-item sm:min-w-fit"
@click.prevent="activeTab = 'logs';
window.location.hash = 'logs'"
href="#">Logs</a>
<a :class="activeTab === 'webhooks' && 'dark:text-white'"
<a class="menu-item sm:min-w-fit"
@click.prevent="activeTab = 'webhooks'; window.location.hash = 'webhooks'" href="#">Webhooks
</a>
<a :class="activeTab === 'resource-operations' && 'dark:text-white'"
<a class="menu-item sm:min-w-fit"
@click.prevent="activeTab = 'resource-operations'; window.location.hash = 'resource-operations'"
href="#">Resource Operations
</a>
<a :class="activeTab === 'tags' && 'dark:text-white'"
<a class="menu-item sm:min-w-fit"
@click.prevent="activeTab = 'tags'; window.location.hash = 'tags'" href="#">Tags
</a>
<a :class="activeTab === 'danger' && 'dark:text-white'"
<a class="menu-item sm:min-w-fit"
@click.prevent="activeTab = 'danger';
window.location.hash = 'danger'"
href="#">Danger Zone
</a>
</div>
<div class="w-full pl-8">
<div class="w-full">
<div x-cloak x-show="activeTab === 'service-stack'">
<livewire:project.service.stack-form :service="$service" />
<h3>Services</h3>

View File

@ -1,30 +1,30 @@
<div x-data="{ activeTab: window.location.hash ? window.location.hash.substring(1) : 'general' }">
<livewire:project.service.navbar :service="$service" :parameters="$parameters" :query="$query" />
<div class="flex h-full pt-6">
<div class="flex flex-col gap-4 min-w-fit">
<a class="{{ request()->routeIs('project.service.configuration') ? 'dark:text-white' : '' }}"
<div class="flex h-full pt-6 sm:flex-row flex-col gap-8">
<div class="flex sm:flex-col gap-2 xl:w-48 overflow-x-scroll">
<a class="{{ request()->routeIs('project.service.configuration') ? 'menu-item-active' : '' }}"
href="{{ route('project.service.configuration', [...$parameters, 'stack_service_uuid' => null]) }}">
<button><- Back</button>
</a>
<a :class="activeTab === 'general' && 'dark:text-white'"
<a :class="activeTab === 'general' && 'menu-item-active'"
@click.prevent="activeTab = 'general'; window.location.hash = 'general'; if(window.location.search) window.location.search = ''"
href="#">General</a>
<a :class="activeTab === 'storages' && 'dark:text-white'"
<a :class="activeTab === 'storages' && 'menu-item-active'"
@click.prevent="activeTab = 'storages'; window.location.hash = 'storages'; if(window.location.search) window.location.search = ''"
href="#">Storages
</a>
<a :class="activeTab === 'scheduled-tasks' && 'dark:text-white'"
<a :class="activeTab === 'scheduled-tasks' && 'menu-item-active'"
@click.prevent="activeTab = 'scheduled-tasks'; window.location.hash = 'scheduled-tasks'"
href="#">Scheduled Tasks
</a>
@if (str($serviceDatabase?->databaseType())->contains('mysql') ||
str($serviceDatabase?->databaseType())->contains('postgres') ||
str($serviceDatabase?->databaseType())->contains('mariadb'))
<a :class="activeTab === 'backups' && 'dark:text-white'"
<a :class="activeTab === 'backups' && 'menu-item-active'"
@click.prevent="activeTab = 'backups'; window.location.hash = 'backups'" href="#">Backups</a>
@endif
</div>
<div class="w-full pl-8">
<div class="w-full">
@isset($serviceApplication)
<div x-cloak x-show="activeTab === 'general'" class="h-full">
<livewire:project.service.service-application-view :application="$serviceApplication" />

View File

@ -9,13 +9,14 @@
<h1>Configuration</h1>
<x-resources.breadcrumbs :resource="$service" :parameters="$parameters" />
<div class="navbar-main" x-data>
<a class="{{ request()->routeIs('project.service.configuration') ? 'dark:text-white' : '' }}"
href="{{ route('project.service.configuration', $parameters) }}">
<button>Configuration</button>
</a>
<x-services.links :service="$service" />
<div class="flex-1"></div>
<div class="flex gap-2">
<nav class="flex items-center flex-shrink-0 gap-6 scrollbar min-h-10 whitespace-nowrap">
<a class="{{ request()->routeIs('project.service.configuration') ? 'dark:text-white' : '' }}"
href="{{ route('project.service.configuration', $parameters) }}">
<button>Configuration</button>
</a>
<x-services.links :service="$service" />
</nav>
<div class="flex flex-wrap items-center order-first gap-2 sm:order-last">
@if (str($service->status())->contains('running'))
<button @click="$wire.dispatch('restartEvent')" class="gap-2 button">
<svg class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">

View File

@ -21,7 +21,7 @@ class="inline-block w-3 h-3 rounded cursor-pointer stroke-current hover:bg-red-5
<form wire:submit='submit' class="flex items-end gap-2 pt-4">
<div class="w-64">
<x-forms.input label="Create new or assign existing tags"
helper="You add more at once with space seperated list: web api something<br><br>If the tag does not exists, it will be created."
helper="You add more at once with space separated list: web api something<br><br>If the tag does not exists, it will be created."
wire:model="new_tag" />
</div>
<x-forms.button type="submit">Add</x-forms.button>

View File

@ -132,7 +132,7 @@ class="w-full mt-8 mb-4 font-bold box-without-bg bg-coollabs hover:bg-coollabs-1
@if ($server->isFunctional())
<h3 class="py-4">Settings</h3>
<div class="flex gap-2">
<div class="flex gap-2 flex-wrap sm:flex-nowrap">
<x-forms.input id="cleanup_after_percentage" label="Disk cleanup threshold (%)" required
helper="The disk cleanup task will run when the disk usage exceeds this threshold." />
<x-forms.input id="server.settings.concurrent_builds" label="Number of concurrent builds" required

View File

@ -3,11 +3,11 @@
<x-limit-reached name="servers" />
@else
<form class="flex flex-col w-full gap-2" wire:submit='submit'>
<div class="flex w-full gap-2">
<div class="flex w-full gap-2 flex-wrap sm:flex-nowrap">
<x-forms.input autofocus id="name" label="Name" required />
<x-forms.input id="description" label="Description" />
</div>
<div class="flex gap-2">
<div class="flex gap-2 flex-wrap sm:flex-nowrap">
<x-forms.input id="ip" label="IP Address/Domain" required
helper="An IP Address (127.0.0.1) or domain (example.com)." />
<x-forms.input type="number" id="port" label="Port" required />

View File

@ -1,13 +1,13 @@
<div>
<x-server.navbar :server="$server" :parameters="$parameters" />
<div x-data="{ activeTab: window.location.hash ? window.location.hash.substring(1) : 'managed' }" class="flex h-full">
<div class="flex flex-col gap-4">
<div x-data="{ activeTab: window.location.hash ? window.location.hash.substring(1) : 'managed' }" class="flex h-full flex-col md:flex-row gap-8">
<div class="flex md:flex-col gap-4 flex-row">
<a :class="activeTab === 'managed' && 'dark:text-white'"
@click.prevent="activeTab = 'managed'; window.location.hash = 'managed'" href="#">Managed</a>
<a :class="activeTab === 'unmanaged' && 'dark:text-white'"
@click.prevent="activeTab = 'unmanaged'; window.location.hash = 'unmanaged'" href="#">Unmanaged</a>
</div>
<div class="w-full pl-8">
<div class="w-full">
<div x-cloak x-show="activeTab === 'managed'" class="h-full">
<div class="flex flex-col">
<div class="flex gap-2">

View File

@ -9,12 +9,14 @@
<div>General configuration for your Coolify instance.</div>
<div class="flex flex-col gap-2 pt-4">
<div class="flex items-end gap-2">
<div class="flex items-end gap-2 flex-wrap">
<x-forms.input id="settings.fqdn" label="Instance's Domain" placeholder="https://coolify.io" />
<x-forms.input id="settings.custom_dns_servers" label="DNS Servers"
helper="DNS servers for validation FQDNS againts. A comma separated list of DNS servers."
helper="DNS servers for validation FQDNs againts. A comma separated list of DNS servers."
placeholder="1.1.1.1,8.8.8.8" />
<x-forms.checkbox instantSave id="is_dns_validation_enabled" label="Validate DNS settings?" />
<div class="md:w-96">
<x-forms.checkbox instantSave id="is_dns_validation_enabled" label="Validate DNS settings?" />
</div>
</div>
{{-- <div class="flex gap-2 ">
@ -24,12 +26,12 @@
</div>
</form>
<h2 class="pt-6">Advanced</h2>
<div class="text-right w-80">
<div class="text-right md:w-96">
@if (!is_null(env('AUTOUPDATE', null)))
<x-forms.checkbox instantSave helper="AUTOUPDATE is set in .env file, you need to modify it there." disabled
id="is_auto_update_enabled" label="Auto Update Coolify" />
@else
<x-forms.checkbox instantSave id="is_auto_update_enabled" label="Auto Update Coolify" />
<x-forms.checkbox instantSave id="is_auto_update_enabled" label="Auto Update Coolify" />
@endif
<x-forms.checkbox instantSave id="is_registration_enabled" label="Registration Allowed" />
<x-forms.checkbox instantSave id="do_not_track" label="Do Not Track" />
@ -38,7 +40,7 @@
id="next_channel" label="Enable pre-release (early) updates" />
@else
<x-forms.checkbox disabled instantSave
helper="Currently disabled. Do not recommended, only if you like to live on the edge." id="next_channel"
helper="Currently disabled. Not recommended. Only if you like to live on the edge." id="next_channel"
label="Enable pre-release (early) updates" />
@endif
</div>

View File

@ -1,18 +1,19 @@
<div>
<x-settings.navbar />
<div x-data="{ activeTab: window.location.hash ? window.location.hash.substring(1) : 'general' }" class="flex h-full pt-1">
<div class="flex flex-col gap-4 min-w-fit">
<a :class="activeTab === 'general' && 'dark:text-white'"
<div x-data="{ activeTab: window.location.hash ? window.location.hash.substring(1) : 'general' }" class="flex flex-col h-full gap-8 pt-1 sm:flex-row">
<div class="flex gap-6 overflow-x-scroll sm:gap-2 sm:overflow-x-hidden scrollbar sm:flex-col whitespace-nowrap">
<a class="menu-item" :class="activeTab === 'general' && 'menu-item-active'"
@click.prevent="activeTab = 'general'; window.location.hash = 'general'" href="#">General</a>
<a :class="activeTab === 'backup' && 'dark:text-white'"
<a class="menu-item" :class="activeTab === 'backup' && 'menu-item-active'"
@click.prevent="activeTab = 'backup'; window.location.hash = 'backup'" href="#">Instance Backup</a>
<a :class="activeTab === 'smtp' && 'dark:text-white'"
<a class="menu-item" :class="activeTab === 'smtp' && 'menu-item-active'"
@click.prevent="activeTab = 'smtp'; window.location.hash = 'smtp'" href="#">Transactional
Email</a>
<a :class="activeTab === 'auth' && 'dark:text-white'"
@click.prevent="activeTab = 'auth'; window.location.hash = 'auth'" href="#">Authentication (OAuth)</a>
<a class="menu-item" :class="activeTab === 'auth' && 'menu-item-active'"
@click.prevent="activeTab = 'auth'; window.location.hash = 'auth'" href="#">Authentication
(OAuth)</a>
</div>
<div class="w-full pl-8">
<div class="w-full">
<div x-cloak x-show="activeTab === 'general'" class="h-full">
<livewire:settings.configuration :settings="$settings" />
</div>

View File

@ -73,6 +73,9 @@ centos | fedora | rhel | ol | rocky | almalinux | amzn)
if [ "$OS_TYPE" = "amzn" ]; then
dnf install -y wget git jq >/dev/null 2>&1
else
if ! command -v dnf >/dev/null 2>&1; then
yum install -y dnf >/dev/null 2>&1
fi
dnf install -y curl wget git jq >/dev/null 2>&1
fi
;;

View File

@ -1,7 +1,7 @@
{
"coolify": {
"v4": {
"version": "4.0.0-beta.274"
"version": "4.0.0-beta.275"
}
}
}