Merge pull request #1066 from coollabsio/patricio-proxy-config-screen

Proxy config screen
This commit is contained in:
Andras Bacsai 2023-05-15 14:23:41 +02:00 committed by GitHub
commit c47b0ddcc1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 477 additions and 208 deletions

View File

@ -7,6 +7,7 @@ USERID=
GROUPID=
PROJECT_PATH_ON_HOST=/Users/your-username-here/code/coollabsio/coolify
SERVEO_URL=<for receiving webhooks locally https://serveo.net/>
MUX_ENABLED=false
############################################################################################################
APP_NAME=Coolify

View File

@ -0,0 +1,33 @@
<?php
namespace App\Actions\Proxy;
use App\Enums\ProxyTypes;
use App\Models\Server;
use Illuminate\Support\Str;
class CheckProxySettingsInSync
{
public function __invoke(Server $server)
{
$proxy_path = config('coolify.proxy_config_path');
$output = instantRemoteProcess([
"cat $proxy_path/docker-compose.yml",
], $server, false);
if (is_null($output)) {
$final_output = Str::of(getProxyConfiguration($server))->trim();
} else {
$final_output = Str::of($output)->trim();
}
$docker_compose_yml_base64 = base64_encode($final_output);
$server->extra_attributes->last_saved_proxy_settings = Str::of($docker_compose_yml_base64)->pipe('md5')->value;
$server->save();
if (is_null($output)) {
instantRemoteProcess([
"mkdir -p $proxy_path",
"echo '$docker_compose_yml_base64' | base64 -d > $proxy_path/docker-compose.yml",
], $server);
}
return $final_output;
}
}

View File

@ -3,100 +3,65 @@
namespace App\Actions\Proxy;
use App\Enums\ActivityTypes;
use App\Enums\ProxyTypes;
use App\Models\Server;
use Symfony\Component\Yaml\Yaml;
use Illuminate\Support\Collection;
use Spatie\Activitylog\Models\Activity;
use Illuminate\Support\Str;
class InstallProxy
{
public function __invoke(Server $server)
public Collection $networks;
public function __invoke(Server $server): Activity
{
$docker_compose_yml_base64 = base64_encode(
$this->getDockerComposeContents()
);
$proxy_path = config('coolify.proxy_config_path');
$networks = collect($server->standaloneDockers)->map(function ($docker) {
return $docker['network'];
})->unique();
if ($networks->count() === 0) {
$this->networks = collect(['coolify']);
}
$create_networks_command = $this->networks->map(function ($network) {
return "docker network ls --format '{{.Name}}' | grep '^$network$' >/dev/null 2>&1 || docker network create --attachable $network > /dev/null 2>&1";
});
$configuration = instantRemoteProcess([
"cat $proxy_path/docker-compose.yml",
], $server, false);
if (is_null($configuration)) {
$configuration = Str::of(getProxyConfiguration($server))->trim();
} else {
$configuration = Str::of($configuration)->trim();
}
$docker_compose_yml_base64 = base64_encode($configuration);
$server->extra_attributes->last_applied_proxy_settings = Str::of($docker_compose_yml_base64)->pipe('md5')->value;
$server->save();
$env_file_base64 = base64_encode(
$this->getEnvContents()
);
$activity = remoteProcess([
'mkdir -p projects',
'mkdir -p projects/proxy',
'mkdir -p projects/proxy/letsencrypt',
'cd projects/proxy',
"echo '$docker_compose_yml_base64' | base64 -d > docker-compose.yml",
"echo '$env_file_base64' | base64 -d > .env",
...$create_networks_command,
"echo 'Docker networks created...'",
"mkdir -p $proxy_path",
"cd $proxy_path",
"echo '$docker_compose_yml_base64' | base64 -d > $proxy_path/docker-compose.yml",
"echo '$env_file_base64' | base64 -d > $proxy_path/.env",
"echo 'Docker compose file created...'",
"echo 'Pulling docker image...'",
'docker compose pull -q',
"echo 'Stopping proxy...'",
'docker compose down -v --remove-orphans',
"echo 'Starting proxy...'",
'docker compose up -d --remove-orphans',
'docker ps',
"echo 'Proxy installed successfully...'"
], $server, ActivityTypes::INLINE->value);
return $activity;
}
protected function getDockerComposeContents()
{
return Yaml::dump($this->getComposeData());
}
/**
* @return array
*/
protected function getComposeData(): array
{
$cwd = config('app.env') === 'local'
? config('proxy.project_path_on_host') . '/_testing_hosts/host_2_proxy'
: '.';
ray($cwd);
return [
"version" => "3.7",
"networks" => [
"coolify" => [
"external" => true,
],
],
"services" => [
"traefik" => [
"container_name" => "coolify-proxy",
"image" => "traefik:v2.10",
"restart" => "always",
"extra_hosts" => [
"host.docker.internal:host-gateway",
],
"networks" => [
"coolify",
],
"ports" => [
"80:80",
"443:443",
"8080:8080",
],
"volumes" => [
"/var/run/docker.sock:/var/run/docker.sock:ro",
"{$cwd}/letsencrypt:/letsencrypt",
"{$cwd}/traefik.auth:/auth/traefik.auth",
],
"command" => [
"--api.dashboard=true",
"--api.insecure=true",
"--entrypoints.http.address=:80",
"--entrypoints.https.address=:443",
"--providers.docker=true",
"--providers.docker.exposedbydefault=false",
],
"labels" => [
"traefik.enable=true",
"traefik.http.routers.traefik.entrypoints=http",
'traefik.http.routers.traefik.rule=Host(`${TRAEFIK_DASHBOARD_HOST}`)',
"traefik.http.routers.traefik.service=api@internal",
"traefik.http.services.traefik.loadbalancer.server.port=8080",
"traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https",
],
],
],
];
}
protected function getEnvContents()
{
$data = [

View File

@ -4,6 +4,7 @@ namespace App\Console;
use App\Jobs\ContainerStatusJob;
use App\Jobs\DockerCleanupDanglingImagesJob;
use App\Jobs\ProxyCheckJob;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
@ -16,6 +17,7 @@ class Kernel extends ConsoleKernel
{
$schedule->job(new ContainerStatusJob)->everyMinute();
$schedule->job(new DockerCleanupDanglingImagesJob)->everyMinute();
// $schedule->job(new ProxyCheckJob)->everyMinute();
}
/**

View File

@ -32,7 +32,7 @@ class Form extends Component
$this->emit('newMonitorActivity', $activity->id);
}
public function checkServer()
public function validateServer()
{
try {
$this->uptime = instantRemoteProcess(['uptime'], $this->server, false);

View File

@ -2,8 +2,10 @@
namespace App\Http\Livewire\Server;
use App\Actions\Proxy\CheckProxySettingsInSync;
use App\Actions\Proxy\InstallProxy;
use App\Enums\ActivityTypes;
use App\Enums\ProxyTypes;
use Illuminate\Support\Str;
use App\Models\Server;
use Livewire\Component;
@ -11,22 +13,56 @@ class Proxy extends Component
{
public Server $server;
protected string $selectedProxy = '';
public ProxyTypes $selectedProxy = ProxyTypes::TRAEFIK_V2;
public $proxy_settings = null;
public function mount(Server $server)
{
$this->server = $server;
}
public function runInstallProxy()
public function installProxy()
{
$this->saveConfiguration($this->server);
$activity = resolve(InstallProxy::class)($this->server);
$this->emit('newMonitorActivity', $activity->id);
}
public function render()
public function proxyStatus()
{
return view('livewire.server.proxy');
$this->server->extra_attributes->proxy_status = checkContainerStatus(server: $this->server, container_id: 'coolify-proxy');
$this->server->save();
}
public function setProxy()
{
$this->server->extra_attributes->proxy_type = $this->selectedProxy->value;
$this->server->extra_attributes->proxy_status = 'exited';
$this->server->save();
}
public function stopProxy()
{
instantRemoteProcess([
"docker rm -f coolify-proxy",
], $this->server);
$this->server->extra_attributes->proxy_status = 'exited';
$this->server->save();
}
public function saveConfiguration()
{
try {
$proxy_path = config('coolify.proxy_config_path');
$this->proxy_settings = Str::of($this->proxy_settings)->trim();
$docker_compose_yml_base64 = base64_encode($this->proxy_settings);
$this->server->extra_attributes->last_saved_proxy_settings = Str::of($docker_compose_yml_base64)->pipe('md5')->value;
$this->server->save();
instantRemoteProcess([
"echo '$docker_compose_yml_base64' | base64 -d > $proxy_path/docker-compose.yml",
], $this->server);
} catch (\Exception $e) {
return generalErrorHandler($e);
}
}
public function checkProxySettingsInSync()
{
try {
$this->proxy_settings = resolve(CheckProxySettingsInSync::class)($this->server);
} catch (\Exception $e) {
return generalErrorHandler($e);
}
}
}

View File

@ -67,9 +67,7 @@ class ContainerStatusJob implements ShouldQueue
return;
}
if ($application->destination->server) {
$container = instantRemoteProcess(["docker inspect --format '{{json .State}}' {$this->container_id}"], $application->destination->server);
$container = formatDockerCmdOutputToJson($container);
$application->status = $container[0]['Status'];
$application->status = checkContainerStatus(server: $application->destination->server, container_id: $this->container_id);
$application->save();
}
}

View File

@ -69,7 +69,6 @@ class DeployApplicationJob implements ShouldQueue
protected function stopRunningContainer()
{
$this->executeNow([
"echo -n 'Removing old instance... '",
$this->execute_in_builder("docker rm -f {$this->application->uuid} >/dev/null 2>&1"),
"echo 'Done.'",

45
app/Jobs/ProxyCheckJob.php Executable file
View File

@ -0,0 +1,45 @@
<?php
namespace App\Jobs;
use App\Actions\Proxy\InstallProxy;
use App\Models\Server;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class ProxyCheckJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
/**
* Create a new job instance.
*/
public function __construct()
{
}
/**
* Execute the job.
*/
public function handle()
{
try {
$container_name = 'coolify-proxy';
$configuration_path = config('coolify.proxy_config_path');
$servers = Server::whereRelation('settings', 'is_validated', true)->get();
foreach ($servers as $server) {
$status = checkContainerStatus(server: $server, container_id: $container_name);
if ($status === 'running') {
continue;
}
resolve(InstallProxy::class)($server);
}
} catch (\Throwable $th) {
//throw $th;
}
}
}

View File

@ -40,7 +40,7 @@ if (!function_exists('generalErrorHandler')) {
'error' => $error->getMessage(),
]);
} else {
dump($error);
// dump($error);
}
}
}
@ -106,7 +106,7 @@ if (!function_exists('generateSshCommand')) {
$delimiter = 'EOF-COOLIFY-SSH';
Storage::disk('local')->makeDirectory('.ssh');
$ssh_command = "ssh ";
if ($isMux) {
if ($isMux && config('coolify.mux_enabled')) {
$ssh_command .= '-o ControlMaster=auto -o ControlPersist=1m -o ControlPath=/var/www/html/storage/app/.ssh/ssh_mux_%h_%p_%r ';
}
$ssh_command .= "-i {$private_key_location} "
@ -165,10 +165,9 @@ if (!function_exists('instantRemoteProcess')) {
$exitCode = $process->exitCode();
if ($exitCode !== 0) {
if (!$throwError) {
return false;
return null;
}
Log::error($process->errorOutput());
throw new \RuntimeException('There was an error running the command.');
throw new \RuntimeException($process->errorOutput());
}
return $output;
}
@ -196,6 +195,7 @@ use Lcobucci\JWT\Encoding\JoseEncoder;
use Lcobucci\JWT\Signer\Key\InMemory;
use Lcobucci\JWT\Signer\Rsa\Sha256;
use Lcobucci\JWT\Token\Builder;
use Symfony\Component\Yaml\Yaml;
if (!function_exists('generate_github_installation_token')) {
function generate_github_installation_token(GithubApp $source)
@ -244,3 +244,73 @@ if (!function_exists('getParameters')) {
return Route::current()->parameters();
}
}
if (!function_exists('checkContainerStatus')) {
function checkContainerStatus(Server $server, string $container_id, bool $throwError = false)
{
$container = instantRemoteProcess(["docker inspect --format '{{json .State}}' {$container_id}"], $server, $throwError);
if (!$container) {
return 'exited';
}
$container = formatDockerCmdOutputToJson($container);
return $container[0]['Status'];
}
}
if (!function_exists('getProxyConfiguration')) {
function getProxyConfiguration(Server $server)
{
$proxy_config_path = config('coolify.proxy_config_path');
$networks = collect($server->standaloneDockers)->map(function ($docker) {
return $docker['network'];
})->unique();
if ($networks->count() === 0) {
$networks = collect(['coolify']);
}
$array_of_networks = collect([]);
$networks->map(function ($network) use ($array_of_networks) {
$array_of_networks[$network] = [
"external" => true,
];
});
return Yaml::dump([
"version" => "3.8",
"networks" => $array_of_networks->toArray(),
"services" => [
"traefik" => [
"container_name" => "coolify-proxy", # Do not modify this! You will break everything!
"image" => "traefik:v2.10",
"restart" => "always",
"extra_hosts" => [
"host.docker.internal:host-gateway",
],
"networks" => $networks->toArray(), # Do not modify this! You will break everything!
"ports" => [
"80:80",
"443:443",
"8080:8080",
],
"volumes" => [
"/var/run/docker.sock:/var/run/docker.sock:ro",
"{$proxy_config_path}/letsencrypt:/letsencrypt", # Do not modify this! You will break everything!
"{$proxy_config_path}/traefik.auth:/auth/traefik.auth", # Do not modify this! You will break everything!
],
"command" => [
"--api.dashboard=true",
"--api.insecure=true",
"--entrypoints.http.address=:80",
"--entrypoints.https.address=:443",
"--providers.docker=true",
"--providers.docker.exposedbydefault=false",
],
"labels" => [
"traefik.enable=true", # Do not modify this! You will break everything!
"traefik.http.routers.traefik.entrypoints=http",
'traefik.http.routers.traefik.rule=Host(`${TRAEFIK_DASHBOARD_HOST}`)',
"traefik.http.routers.traefik.service=api@internal",
"traefik.http.services.traefik.loadbalancer.server.port=8080",
"traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https",
],
],
],
], 4, 2);
}
}

View File

@ -1,5 +1,8 @@
<?php
return [
'mux_enabled' => env('MUX_ENABLED', true),
'dev_webhook' => env('SERVEO_URL'),
'base_config_path' => env('BASE_CONFIG_PATH', '/data/coolify'),
'proxy_config_path' => env('BASE_CONFIG_PATH', '/data/coolify') . "/proxy",
];

View File

@ -2,7 +2,6 @@ version: '3.8'
x-testing-host:
&testing-host-base
image: coolify-testing-host
build:
dockerfile: Dockerfile
context: ./docker/testing-host

View File

@ -5,4 +5,9 @@ RUN apt-get -y autoremove \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* /usr/share/doc/*
RUN echo "alias ll='ls -al'" >> /etc/bash.bashrc
RUN echo "alias a='php artisan'" >> /etc/bash.bashrc
RUN echo "alias mfs='php artisan migrate:fresh --seed'" >> /etc/bash.bashrc
RUN echo "alias cda='composer dump-autoload'" >> /etc/bash.bashrc
# COPY --chmod=755 docker/dev-ssu/etc/s6-overlay/ /etc/s6-overlay/

View File

@ -1,14 +1,16 @@
FROM ubuntu:22.04
ARG TARGETPLATFORM
# https://download.docker.com/linux/static/stable/
ARG DOCKER_VERSION=20.10.18
ARG DOCKER_VERSION=23.0.5
# https://github.com/docker/compose/releases
# Reverted to 2.6.1 because of this https://github.com/docker/compose/issues/9704. 2.9.0 still has a bug.
ARG DOCKER_COMPOSE_VERSION=2.6.1
ARG DOCKER_COMPOSE_VERSION=2.17.3
# https://github.com/docker/buildx/releases
ARG DOCKER_BUILDX_VERSION=0.10.4
# https://github.com/buildpacks/pack/releases
ARG PACK_VERSION=v0.27.0
ARG PACK_VERSION=0.29.0
# https://github.com/railwayapp/nixpacks/releases
ARG NIXPACKS_VERSION=1.6.1
ENV DEBIAN_FRONTEND noninteractive
ENV TZ=UTC
@ -17,7 +19,7 @@ RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
RUN apt-get update \
&& apt-get install -y gnupg gosu curl ca-certificates zip unzip git git-lfs supervisor \
sqlite3 libcap2-bin libpng-dev python2 dnsutils openssh-server sudo \
sqlite3 libcap2-bin libpng-dev python2 dnsutils openssh-server sudo \
&& apt-get -y autoremove \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
@ -26,13 +28,14 @@ RUN apt-get update \
RUN ssh-keygen -A
RUN mkdir -p /run/sshd
# Install Docker CLI, Docker Compose, and Pack
RUN mkdir -p ~/.docker/cli-plugins
RUN curl -SL https://cdn.coollabs.io/bin/$TARGETPLATFORM/docker-$DOCKER_VERSION -o /usr/bin/docker
RUN curl -SL https://cdn.coollabs.io/bin/$TARGETPLATFORM/docker-compose-linux-$DOCKER_COMPOSE_VERSION -o ~/.docker/cli-plugins/docker-compose
RUN curl -SL https://cdn.coollabs.io/bin/$TARGETPLATFORM/pack-$PACK_VERSION -o /usr/local/bin/pack
RUN curl -sSL https://nixpacks.com/install.sh | bash
RUN chmod +x ~/.docker/cli-plugins/docker-compose /usr/bin/docker /usr/local/bin/pack
RUN if [[ ${TARGETPLATFORM} == 'linux/amd64' ]]; then \
curl -sSL https://github.com/docker/buildx/releases/download/v${DOCKER_BUILDX_VERSION}/buildx-v${DOCKER_BUILDX_VERSION}.linux-amd64 -o ~/.docker/cli-plugins/docker-buildx && \
curl -sSL https://github.com/docker/compose/releases/download/v${DOCKER_COMPOSE_VERSION}/docker-compose-linux-x86_64 -o ~/.docker/cli-plugins/docker-compose && \
(curl -sSL https://download.docker.com/linux/static/stable/x86_64/docker-${DOCKER_VERSION}.tgz | tar -C /usr/bin/ --no-same-owner -xzv --strip-components=1 docker/docker) && \
(curl -sSL https://github.com/buildpacks/pack/releases/download/v${PACK_VERSION}/pack-v${PACK_VERSION}-linux.tgz | tar -C /usr/local/bin/ --no-same-owner -xzv pack) && \
curl -sSL https://nixpacks.com/install.sh | bash && \
chmod +x ~/.docker/cli-plugins/docker-compose /usr/bin/docker /usr/local/bin/pack /root/.docker/cli-plugins/docker-buildx \
;fi
RUN groupadd docker
# Setup coolify user

View File

@ -6,8 +6,9 @@ body {
@apply bg-coolgray-100 text-white font-sans;
}
input, textarea {
@apply border-none p-2 bg-coolgray-200 text-white disabled:text-neutral-600 read-only:text-neutral-600 read-only:select-none outline-none ;
input,
textarea {
@apply border-none p-2 bg-coolgray-200 text-white disabled:text-neutral-600 read-only:text-neutral-600 read-only:select-none outline-none;
}
select {
@apply border-none p-2 bg-coolgray-200 text-white disabled:text-neutral-600 read-only:select-none outline-none;
@ -20,11 +21,11 @@ button {
@apply relative float-left;
}
.main-menu:after {
content: '/';
content: "/";
@apply absolute right-0 top-0 text-neutral-400 px-2 pt-[0.3rem];
}
.magic-input {
@apply w-[25rem] rounded shadow outline-none focus:bg-neutral-700 text-white;
@apply w-[25rem] rounded outline-none bg-coolgray-400 focus:bg-neutral-700 text-white;
}
.magic-items {
@apply absolute top-14 w-[25rem] bg-coolgray-200 border-b-2 border-r-2 border-l-2 border-solid border-coolgray-100 rounded-b;
@ -41,6 +42,9 @@ h1 {
h2 {
@apply text-xl font-bold py-4;
}
h3 {
@apply text-lg font-bold py-4;
}
.box {
@apply flex items-center justify-center text-sm rounded cursor-pointer h-14 bg-coolgray-200 hover:bg-coollabs p-2;
}

View File

@ -1,17 +1,21 @@
@props([
'isWarning' => null,
'isBold' => false,
'disabled' => null,
'defaultClass' => 'text-white hover:bg-coollabs h-8 rounded transition-colors',
'defaultWarningClass' => 'text-white bg-red-500 hover:bg-red-600 h-8 rounded',
'disabledClass' => 'text-coolgray-200 h-8 rounded',
'loadingClass' => 'text-black bg-green-500 h-8 rounded',
'defaultClass' => 'text-white hover:bg-coollabs h-10 rounded transition-colors',
'defaultWarningClass' => 'text-white bg-red-500 hover:bg-red-600 h-10 rounded',
'disabledClass' => 'text-neutral-400 h-10 rounded',
'loadingClass' => 'text-black bg-green-500 h-10 rounded',
'confirm' => null,
'confirmAction' => null,
])
<button {{ $attributes }} @class([
$defaultClass => !$confirm && !$isWarning && !$disabled,
$defaultClass => !$confirm && !$isWarning && !$disabled && !$isBold,
$defaultWarningClass => ($confirm || $isWarning) && !$disabled,
$disabledClass => $disabled,
$isBold => $isBold
? 'bg-coollabs text-white hover:bg-coollabs-100 h-10 rounded transition-colors'
: '',
]) @if ($attributes->whereStartsWith('wire:click') && !$disabled)
wire:target="{{ explode('(', $attributes->whereStartsWith('wire:click')->first())[0] }}"
wire:loading.delay.class="{{ $loadingClass }}" wire:loading.delay.attr="disabled"

View File

@ -1,4 +1,4 @@
<div x-data="magicsearchbar" @slash.window="mainMenu = true" class="pt-2">
<div x-data="magicsearchbar" @slash.window="mainMenu = true">
{{-- Main --}}
<template x-cloak x-if="isMainMenu">
<div>

View File

@ -1,31 +1,33 @@
<nav class="flex gap-2">
@auth
<div class="fixed flex gap-2 left-2 top-2">
<a href="/">
<x-inputs.button>Home</x-inputs.button>
</a>
<a href="/command-center">
<x-inputs.button>Command Center</x-inputs.button>
</a>
<a href="/profile">
<x-inputs.button>Profile</x-inputs.button>
</a>
@if (auth()->user()->isRoot())
<a href="/settings">
<x-inputs.button>Settings</x-inputs.button>
@auth
<nav class="bg-coolgray-200/75">
<div class="flex px-2 py-1">
<div class="flex gap-2">
<a href="/">
<x-inputs.button>Home</x-inputs.button>
</a>
@endif
<a href="/command-center">
<x-inputs.button>Command Center</x-inputs.button>
</a>
<a href="/profile">
<x-inputs.button>Profile</x-inputs.button>
</a>
@if (auth()->user()->isRoot())
<a href="/settings">
<x-inputs.button>Settings</x-inputs.button>
</a>
@endif
</div>
<div class="flex-1"></div>
<x-magic-bar />
<div class="flex-1"></div>
<div class="flex gap-2">
{{-- <livewire:check-update /> --}}
<livewire:force-upgrade />
<form action="/logout" method="POST">
@csrf
<x-inputs.button type="submit">Logout</x-inputs.button>
</form>
</div>
</div>
<div class="flex-1"></div>
<x-magic-bar />
<div class="flex-1"></div>
<div class="fixed flex gap-2 right-2 top-2">
{{-- <livewire:check-update /> --}}
<livewire:force-upgrade />
<form action="/logout" method="POST">
@csrf
<x-inputs.button type="submit">Logout</x-inputs.button>
</form>
</div>
@endauth
</nav>
</nav>
@endauth

View File

@ -0,0 +1,3 @@
<div>
Loading...
</div>

View File

@ -0,0 +1,47 @@
@props(['proxy_settings'])
<div class="mt-4">
<label>
<div>Edit config file</div>
<textarea cols="45" rows="6"></textarea>
</label>
</div>
<div class="mt-4">
<label>
Enable dashboard?
<input type="checkbox" />
(auto-save)
</label>
</div>
<div class="mt-4">
<a href="#">Visit Dashboard</a>
</div>
<div class="mt-4">
<label>
<div>Setup hostname for Dashboard</div>
<div class="mt-2"></div>
<label>
<div>Hostname <span class="text-xs"> Eg: dashboard.example.com </span></div>
<input type="text" />
</label>
<button>Update</button>
</label>
</div>
<div class="mt-4">
<label>
<div>Dashboard credentials</div>
<div class="mt-2"></div>
<label>
Username
<input type="text" />
</label>
<label>
Password
<input type="password" />
</label>
<button>Update</button>
</label>
</div>

View File

@ -1,5 +1,3 @@
@extends('errors::minimal')
@section('title', __('Not Found'))
@section('code', '404')
@section('message', __('Not Found'))
<div>
You are lost. <a href="{{ route('dashboard') }}">Go home</a>
</div>

View File

@ -1,10 +1,10 @@
<div>
<div
class="flex flex-col-reverse w-full overflow-y-auto border border-solid rounded border-coolgray-300 max-h-[32rem] p-4">
@if ($this->activity)
@if ($this->activity)
<div
class="flex flex-col-reverse w-full overflow-y-auto border border-solid rounded border-coolgray-300 max-h-[32rem] p-4">
<pre class="whitespace-pre-wrap" @if ($isPollingActive) wire:poll.750ms="polling" @endif>{{ \App\Actions\CoolifyTask\RunRemoteProcess::decodeOutput($this->activity) }}</pre>
@else
<pre class="whitespace-pre-wrap">Output will be here...</pre>
@endif
</div>
{{-- @else
<pre class="whitespace-pre-wrap">Output will be here...</pre> --}}
</div>
@endif
</div>

View File

@ -10,7 +10,7 @@
@endif
@endforeach
</select>
<x-inputs.button type="submit">Run</x-inputs.button>
<x-inputs.button isBold type="submit">Run</x-inputs.button>
</form>
<div class="container w-full pt-10 mx-auto">
<livewire:activity-monitor />

View File

@ -1,6 +1,23 @@
<div x-data="{ deleteServer: false }">
<x-naked-modal show="deleteServer" message='Are you sure you would like to delete this server?' />
<form wire:submit.prevent='submit' class="flex flex-col">
<div class="flex flex-col pb-4">
<div class="flex items-center gap-2">
<div class="text-3xl font-bold">Server</div>
<x-inputs.button isBold type="submit">Submit</x-inputs.button>
<x-inputs.button isWarning x-on:click.prevent="deleteServer = true">
Delete
</x-inputs.button>
</div>
<div>
@if ($server->settings->is_validated)
<div class="text-green-400/90">Validated</div>
@else
<div class="text-red-400/90">Not validated</div>
@endif
</div>
</div>
<div class="flex flex-col gap-2 xl:flex-row">
<div class="flex flex-col w-96">
<x-inputs.input id="server.name" label="Name" required />
@ -18,15 +35,11 @@
@endif
</div>
</div>
<div class="flex">
<x-inputs.button type="submit">Submit</x-inputs.button>
<x-inputs.button wire:click.prevent='checkServer'>Check Server</x-inputs.button>
<x-inputs.button wire:click.prevent='installDocker'>Install Docker</x-inputs.button>
<x-inputs.button isWarning x-on:click.prevent="deleteServer = true">
Delete
</x-inputs.button>
<div class="flex gap-2">
<x-inputs.button isBold wire:click.prevent='validateServer'>Validate Server</x-inputs.button>
<x-inputs.button isBold wire:click.prevent='installDocker'>Install Docker</x-inputs.button>
</div>
<x-inputs.input class="" disabled type="checkbox" id="server.settings.is_validated" label="Validated" />
</form>
@isset($uptime)

View File

@ -1,30 +1,52 @@
<div>
<h2> Proxy </h2>
@if($this->server->extra_attributes->proxy)
<div class="mt-12">
<div>
Proxy type: {{ $this->server->extra_attributes->proxy }}
</div>
<div class="mt-12"> Features in W11.</div>
<ul>
<li>Edit config file</li>
<li>Enable dashboard (blocking port by firewall)</li>
<li>Dashboard access - login/password</li>
<li>Setup host for Traefik Dashboard</li>
<li>Visit (nav to traefik dashboard)</li>
</ul>
</div>
@else
No proxy installed.
@if ($this->server->extra_attributes->proxy_status !== 'running')
<select wire:model="selectedProxy">
<option value="{{ \App\Enums\ProxyTypes::TRAEFIK_V2 }}">
{{ \App\Enums\ProxyTypes::TRAEFIK_V2 }}
</option>
</select>
<button wire:click="runInstallProxy">Install Proxy</button>
<x-inputs.button isBold wire:click="setProxy">Set Proxy</x-inputs.button>
@endif
@if ($this->server->extra_attributes->proxy_type)
<div wire:poll="proxyStatus">
@if (
$this->server->extra_attributes->last_applied_proxy_settings &&
$this->server->extra_attributes->last_saved_proxy_settings !==
$this->server->extra_attributes->last_applied_proxy_settings)
<div class="text-red-500">Configuration out of sync.</div>
@endif
@if ($this->server->extra_attributes->proxy_status !== 'running')
<x-inputs.button isBold wire:click="installProxy">
Install
</x-inputs.button>
@endif
<x-inputs.button isBold wire:click="stopProxy">Stop</x-inputs.button>
<span x-data="{ showConfiguration: false }">
<x-inputs.button isBold x-on:click="showConfiguration = !showConfiguration">Show Configuration
</x-inputs.button>
<div class="pt-4">
<livewire:activity-monitor />
</div>
<template x-if="showConfiguration">
<div x-init="$wire.checkProxySettingsInSync">
<h3>Configuration</h3>
<div wire:loading wire:target="checkProxySettingsInSync">
<x-proxy.loading />
</div>
@isset($this->proxy_settings)
<form wire:submit.prevent='saveConfiguration'>
<x-inputs.button isBold>Save</x-inputs.button>
<x-inputs.button x-on:click="showConfiguration = false" isBold
wire:click.prevent="installProxy">
Apply
</x-inputs.button>
<textarea wire:model.defer="proxy_settings" class="w-full" rows="30"></textarea>
</form>
@endisset
</div>
</template>
</span>
</div>
@endif
<livewire:activity-monitor />
</div>

View File

@ -1,20 +1,34 @@
<x-layout>
<h1>Server</h1>
<livewire:server.form :server_id="$server->id" />
<h2>Private Key <a href="{{ route('server.private-key', ['server_uuid' => $server->uuid]) }}">
<x-inputs.button>Change</x-inputs.button>
<div class="flex items-center gap-2">
<h2>Private Key</h2>
<a href="{{ route('server.private-key', ['server_uuid' => $server->uuid]) }}">
<x-inputs.button isBold>Change</x-inputs.button>
</a>
</h2>
</div>
<p>{{ $server->privateKey->name }}</p>
<h2>Destinations <a href="{{ route('destination.new', ['server_id' => $server->id]) }}">
<x-inputs.button>New</x-inputs.button>
</a></h2>
@if ($server->standaloneDockers)
<div class="flex items-center gap-2">
<h2>Destinations</h2>
<a href="{{ route('destination.new', ['server_id' => $server->id]) }}">
<x-inputs.button isBold>New</x-inputs.button>
</a>
</div>
@if ($server->standaloneDockers->count() > 0)
@foreach ($server->standaloneDockers as $docker)
<p>Network: {{ data_get($docker, 'network') }}</p>
@endforeach
@else
<p>No destinations found</p>
@endif
<div class="flex items-center gap-2">
<h2>Proxy</h2>
@if ($server->settings->is_validated)
<div>{{ $server->extra_attributes->proxy_status }}</div>
@endif
</div>
@if ($server->settings->is_validated)
<livewire:server.proxy :server="$server" />
@else
<p>Server is not validated. Validate first.</p>
@endif
<h1> {{ $server->name }}</h1>
<livewire:server.proxy :server="$server"/>
</x-layout>

View File

@ -34,9 +34,12 @@ function schedule-run {
function reset-db {
bash vendor/bin/spin exec -u webuser coolify php artisan migrate:fresh --seed
}
function mfs {
reset-db
}
function reset-db-production {
bash vendor/bin/spin exec -u webuser coolify php artisan migrate:fresh --force --seed --seeder=ProductionSeeder ||
php artisan migrate:fresh --force --seed --seeder=ProductionSeeder
php artisan migrate:fresh --force --seed --seeder=ProductionSeeder
}
function coolify {
bash vendor/bin/spin exec -u webuser coolify bash

View File

@ -7,9 +7,9 @@ export default defineConfig({
host: "0.0.0.0",
hmr: process.env.GITPOD_WORKSPACE_URL
? {
// Due to port fowarding, we have to replace
// Due to port forwarding, we have to replace
// 'https' with the forwarded port, as this
// is the URI created by Gitpod.
// is the URI created by GitPod.
host: process.env.GITPOD_WORKSPACE_URL.replace(
"https://",
"5173-"