feat: able to add dynamic configurations from proxy dashboard
This commit is contained in:
parent
4d88638d4d
commit
154b1b05e4
28
app/Livewire/Server/Proxy/DynamicConfigurationNavbar.php
Normal file
28
app/Livewire/Server/Proxy/DynamicConfigurationNavbar.php
Normal file
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Server\Proxy;
|
||||
|
||||
use App\Models\Server;
|
||||
use Livewire\Component;
|
||||
|
||||
class DynamicConfigurationNavbar extends Component
|
||||
{
|
||||
public $server_id;
|
||||
public $fileName = '';
|
||||
public $value = '';
|
||||
public $newFile = false;
|
||||
public function delete(string $fileName)
|
||||
{
|
||||
$server = Server::ownedByCurrentTeam()->whereId($this->server_id)->first();
|
||||
$proxy_path = get_proxy_path();
|
||||
$file = str_replace('|', '.', $fileName);
|
||||
instant_remote_process(["rm -f {$proxy_path}/dynamic/{$file}"], $server);
|
||||
$this->dispatch('success', 'Success', 'File deleted.');
|
||||
$this->dispatch('loadDynamicConfigurations');
|
||||
$this->dispatch('refresh');
|
||||
}
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.server.proxy.dynamic-configuration-navbar');
|
||||
}
|
||||
}
|
49
app/Livewire/Server/Proxy/DynamicConfigurations.php
Normal file
49
app/Livewire/Server/Proxy/DynamicConfigurations.php
Normal file
@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Server\Proxy;
|
||||
|
||||
use App\Models\Server;
|
||||
use Illuminate\Support\Collection;
|
||||
use Livewire\Component;
|
||||
|
||||
class DynamicConfigurations extends Component
|
||||
{
|
||||
public ?Server $server = null;
|
||||
public $parameters = [];
|
||||
public Collection $contents;
|
||||
protected $listeners = ['loadDynamicConfigurations', 'refresh' => '$refresh'];
|
||||
protected $rules = [
|
||||
'contents.*' => 'nullable|string',
|
||||
];
|
||||
public function loadDynamicConfigurations()
|
||||
{
|
||||
$proxy_path = get_proxy_path();
|
||||
$files = instant_remote_process(["mkdir -p $proxy_path/dynamic && ls -1 {$proxy_path}/dynamic"], $this->server);
|
||||
$files = collect(explode("\n", $files))->filter(fn ($file) => !empty($file));
|
||||
$files = $files->map(fn ($file) => trim($file));
|
||||
$files = $files->sort();
|
||||
$files = $files->filter(fn ($file) => $file !== 'coolify.yaml')->prepend('coolify.yaml');
|
||||
$contents = collect([]);
|
||||
foreach ($files as $file) {
|
||||
$without_extension = str_replace('.', '|', $file);
|
||||
$contents[$without_extension] = instant_remote_process(["cat {$proxy_path}/dynamic/{$file}"], $this->server);
|
||||
}
|
||||
$this->contents = $contents;
|
||||
}
|
||||
public function mount()
|
||||
{
|
||||
$this->parameters = get_route_parameters();
|
||||
try {
|
||||
$this->server = Server::ownedByCurrentTeam()->whereUuid(request()->server_uuid)->first();
|
||||
if (is_null($this->server)) {
|
||||
return redirect()->route('server.index');
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.server.proxy.dynamic-configurations');
|
||||
}
|
||||
}
|
73
app/Livewire/Server/Proxy/NewDynamicConfiguration.php
Normal file
73
app/Livewire/Server/Proxy/NewDynamicConfiguration.php
Normal file
@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Server\Proxy;
|
||||
|
||||
use App\Models\Server;
|
||||
use Illuminate\Routing\Route;
|
||||
use Livewire\Component;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
class NewDynamicConfiguration extends Component
|
||||
{
|
||||
public string $fileName = '';
|
||||
public string $value = '';
|
||||
public bool $newFile = false;
|
||||
public Server $server;
|
||||
public $server_id;
|
||||
public $parameters = [];
|
||||
public function mount()
|
||||
{
|
||||
$this->parameters = get_route_parameters();
|
||||
if ($this->fileName !== '') {
|
||||
$this->fileName = str_replace('|', '.', $this->fileName);
|
||||
}
|
||||
}
|
||||
public function addDynamicConfiguration()
|
||||
{
|
||||
try {
|
||||
$this->validate([
|
||||
'fileName' => 'required',
|
||||
'value' => 'required',
|
||||
]);
|
||||
|
||||
if (data_get($this->parameters, 'server_uuid')) {
|
||||
$this->server = Server::ownedByCurrentTeam()->whereUuid(data_get($this->parameters, 'server_uuid'))->first();
|
||||
}
|
||||
if (!is_null($this->server_id)) {
|
||||
$this->server = Server::ownedByCurrentTeam()->whereId($this->server_id)->first();
|
||||
}
|
||||
if (is_null($this->server)) {
|
||||
return redirect()->route('server.index');
|
||||
}
|
||||
if (!str($this->fileName)->endsWith('.yaml') && !str($this->fileName)->endsWith('.yml')) {
|
||||
$this->fileName = "{$this->fileName}.yaml";
|
||||
}
|
||||
if ($this->fileName === 'coolify.yaml') {
|
||||
$this->dispatch('error', 'Error', 'File name is reserved.');
|
||||
return;
|
||||
}
|
||||
$proxy_path = get_proxy_path();
|
||||
$file = "{$proxy_path}/dynamic/{$this->fileName}";
|
||||
if ($this->newFile) {
|
||||
$exists = instant_remote_process(["test -f $file && echo 1 || echo 0"], $this->server);
|
||||
if ($exists == 1) {
|
||||
$this->dispatch('error', 'Error', 'File already exists');
|
||||
return;
|
||||
}
|
||||
}
|
||||
$yaml = Yaml::parse($this->value);
|
||||
$yaml = Yaml::dump($yaml, 10, 2);
|
||||
$this->value = $yaml;
|
||||
$base64_value = base64_encode($this->value);
|
||||
instant_remote_process(["echo '{$base64_value}' | base64 -d > {$file}"], $this->server);
|
||||
$this->dispatch('loadDynamicConfigurations');
|
||||
$this->dispatch('dynamic-configuration-added');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.server.proxy.new-dynamic-configuration');
|
||||
}
|
||||
}
|
@ -104,13 +104,13 @@ function handleError(?Throwable $error = null, ?Livewire\Component $livewire = n
|
||||
ray($error);
|
||||
if ($error instanceof TooManyRequestsException) {
|
||||
if (isset($livewire)) {
|
||||
return $livewire->dispatch('error', "Too many requests.", "Please try again in {$error->secondsUntilAvailable} seconds.");
|
||||
return $livewire->dispatch('error', "Error", "Too many requests. Please try again in {$error->secondsUntilAvailable} seconds.");
|
||||
}
|
||||
return "Too many requests. Please try again in {$error->secondsUntilAvailable} seconds.";
|
||||
}
|
||||
if ($error instanceof UniqueConstraintViolationException) {
|
||||
if (isset($livewire)) {
|
||||
return $livewire->dispatch('error', "Duplicate entry found.", "Please use a different name.");
|
||||
return $livewire->dispatch('error', "Error", "Duplicate entry found. Please use a different name.");
|
||||
}
|
||||
return "Duplicate entry found. Please use a different name.";
|
||||
}
|
||||
@ -126,7 +126,7 @@ function handleError(?Throwable $error = null, ?Livewire\Component $livewire = n
|
||||
|
||||
if (isset($livewire)) {
|
||||
if (str($message)->length() > 20) {
|
||||
return $livewire->dispatch('error', 'Error occured', $message);
|
||||
return $livewire->dispatch('error', 'Error', $message);
|
||||
}
|
||||
return $livewire->dispatch('error', $message);
|
||||
}
|
||||
|
@ -1,47 +0,0 @@
|
||||
@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>
|
@ -1,12 +1,16 @@
|
||||
<div>
|
||||
@if ($server->isFunctional())
|
||||
<div class="flex h-full pr-4">
|
||||
<div class="flex flex-col gap-4 min-w-fit">
|
||||
<div class="flex flex-col w-48 gap-4 min-w-fit">
|
||||
<a class="{{ request()->routeIs('server.proxy') ? 'text-white' : '' }}"
|
||||
href="{{ route('server.proxy', $parameters) }}">
|
||||
<button>Configuration</button>
|
||||
</a>
|
||||
@if (data_get($server, 'proxy.type') !== 'NONE')
|
||||
<a class="{{ request()->routeIs('server.proxy.dynamic-confs') ? 'text-white' : '' }}"
|
||||
href="{{ route('server.proxy.dynamic-confs', $parameters) }}">
|
||||
<button>Dynamic Configurations</button>
|
||||
</a>
|
||||
<a class="{{ request()->routeIs('server.proxy.logs') ? 'text-white' : '' }}"
|
||||
href="{{ route('server.proxy.logs', $parameters) }}">
|
||||
<button>Logs</button>
|
||||
|
@ -18,14 +18,14 @@ class="fixed inset-0 bg-black bg-opacity-60"></div>
|
||||
x-transition:leave="transform transition ease-in-out duration-100 sm:duration-300"
|
||||
x-transition:leave-start="translate-x-0" x-transition:leave-end="translate-x-full"
|
||||
@class([
|
||||
'max-w-md w-screen' => !$fullScreen,
|
||||
'max-w-xl w-screen' => !$fullScreen,
|
||||
'max-w-4xl w-screen' => $fullScreen,
|
||||
])>
|
||||
<div
|
||||
class="flex flex-col h-full py-6 overflow-hidden border-l shadow-lg bg-base-100 border-neutral-800">
|
||||
<div class="px-4 pb-4 sm:px-5">
|
||||
<div class="flex items-start justify-between pb-1">
|
||||
<h2 class="text-3xl leading-6" id="slide-over-title">
|
||||
<h2 class="text-2xl leading-6" id="slide-over-title">
|
||||
{{ $title }}</h2>
|
||||
<div class="flex items-center h-auto ml-3">
|
||||
<button class="icon" @click="slideOverOpen=false"
|
||||
|
@ -1,7 +1,7 @@
|
||||
<div class="pb-6">
|
||||
<div class="flex items-end gap-2">
|
||||
<h1>Team</h1>
|
||||
<a href="/team/new"><x-forms.button>+ New Team</x-forms.button></a>
|
||||
<a href="/team/new"><x-forms.button>+ Add Team</x-forms.button></a>
|
||||
</div>
|
||||
<nav class="flex pt-2 pb-10">
|
||||
<ol class="inline-flex items-center">
|
||||
|
@ -47,7 +47,7 @@
|
||||
<div class="flex items-center">
|
||||
<a class="mx-4 rounded group-hover:text-white hover:no-underline"
|
||||
href="{{ route('project.resource.create', ['project_uuid' => data_get($project, 'uuid'), 'environment_name' => data_get($project, 'environments.0.name', 'production')]) }}">
|
||||
<span class="font-bold hover:text-warning">+ New Resource</span>
|
||||
<span class="font-bold hover:text-warning">+ Add Resource</span>
|
||||
</a>
|
||||
<a class="mx-4 rounded group-hover:text-white"
|
||||
href="{{ route('project.edit', ['project_uuid' => data_get($project, 'uuid')]) }}">
|
||||
|
@ -1,82 +1,87 @@
|
||||
<div x-init="$wire.getLogs">
|
||||
<div class="flex gap-2">
|
||||
<h4>Container: {{ $container }}</h4>
|
||||
@if ($streamLogs)
|
||||
<span wire:poll.2000ms='getLogs(true)' class="loading loading-xs text-warning loading-spinner"></span>
|
||||
@endif
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<x-forms.checkbox instantSave label="Stream Logs" id="streamLogs"></x-forms.checkbox>
|
||||
<x-forms.checkbox instantSave label="Include Timestamps" id="showTimeStamps"></x-forms.checkbox>
|
||||
</div>
|
||||
<form wire:submit='getLogs(true)' class="flex items-end gap-2">
|
||||
<x-forms.input label="Only Show Number of Lines" placeholder="1000" required id="numberOfLines"></x-forms.input>
|
||||
<x-forms.button type="submit">Refresh</x-forms.button>
|
||||
</form>
|
||||
<div id="screen" x-data="{ fullscreen: false, alwaysScroll: false, intervalId: null }" :class="fullscreen ? 'fullscreen' : 'w-full py-4 mx-auto'">
|
||||
<div class="relative flex flex-col-reverse w-full p-4 pt-6 overflow-y-auto text-white bg-coolgray-100 scrollbar border-coolgray-300"
|
||||
:class="fullscreen ? '' : 'max-h-[40rem] border border-solid rounded'">
|
||||
<button title="Minimize" x-show="fullscreen" class="fixed top-4 right-4" x-on:click="makeFullscreen"><svg
|
||||
class="icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
|
||||
stroke-width="2" d="M6 14h4m0 0v4m0-4l-6 6m14-10h-4m0 0V6m0 4l6-6" />
|
||||
</svg></button>
|
||||
<button title="Go Top" x-show="fullscreen" class="fixed top-4 right-28" x-on:click="goTop"> <svg
|
||||
class="icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
|
||||
stroke-width="2" d="M12 5v14m4-10l-4-4M8 9l4-4" />
|
||||
</svg></button>
|
||||
<button title="Follow Logs" x-show="fullscreen" :class="alwaysScroll ? 'text-warning' : ''"
|
||||
class="fixed top-4 right-16" x-on:click="toggleScroll"><svg class="icon" viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
|
||||
stroke-width="2" d="M12 5v14m4-4l-4 4m-4-4l4 4" />
|
||||
</svg></button>
|
||||
|
||||
<button title="Fullscreen" x-show="!fullscreen" class="absolute top-2 right-2"
|
||||
x-on:click="makeFullscreen"><svg class=" icon" viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="none">
|
||||
<path
|
||||
d="M24 0v24H0V0h24ZM12.593 23.258l-.011.002l-.071.035l-.02.004l-.014-.004l-.071-.035c-.01-.004-.019-.001-.024.005l-.004.01l-.017.428l.005.02l.01.013l.104.074l.015.004l.012-.004l.104-.074l.012-.016l.004-.017l-.017-.427c-.002-.01-.009-.017-.017-.018Zm.265-.113l-.013.002l-.185.093l-.01.01l-.003.011l.018.43l.005.012l.008.007l.201.093c.012.004.023 0 .029-.008l.004-.014l-.034-.614c-.003-.012-.01-.02-.02-.022Zm-.715.002a.023.023 0 0 0-.027.006l-.006.014l-.034.614c0 .012.007.02.017.024l.015-.002l.201-.093l.01-.008l.004-.011l.017-.43l-.003-.012l-.01-.01l-.184-.092Z" />
|
||||
<path fill="currentColor"
|
||||
d="M9.793 12.793a1 1 0 0 1 1.497 1.32l-.083.094L6.414 19H9a1 1 0 0 1 .117 1.993L9 21H4a1 1 0 0 1-.993-.883L3 20v-5a1 1 0 0 1 1.993-.117L5 15v2.586l4.793-4.793ZM20 3a1 1 0 0 1 .993.883L21 4v5a1 1 0 0 1-1.993.117L19 9V6.414l-4.793 4.793a1 1 0 0 1-1.497-1.32l.083-.094L17.586 5H15a1 1 0 0 1-.117-1.993L15 3h5Z" />
|
||||
</g>
|
||||
</svg></button>
|
||||
<pre id="logs" class="font-mono whitespace-pre-wrap">{{ $outputs }}</pre>
|
||||
<div>
|
||||
<div x-init="$wire.getLogs">
|
||||
<div class="flex gap-2">
|
||||
<h4>Container: {{ $container }}</h4>
|
||||
@if ($streamLogs)
|
||||
<span wire:poll.2000ms='getLogs(true)' class="loading loading-xs text-warning loading-spinner"></span>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
function makeFullscreen() {
|
||||
this.fullscreen = !this.fullscreen;
|
||||
if (this.fullscreen === false) {
|
||||
<div class="flex gap-2">
|
||||
<x-forms.checkbox instantSave label="Stream Logs" id="streamLogs"></x-forms.checkbox>
|
||||
<x-forms.checkbox instantSave label="Include Timestamps" id="showTimeStamps"></x-forms.checkbox>
|
||||
</div>
|
||||
<form wire:submit='getLogs(true)' class="flex items-end gap-2">
|
||||
<x-forms.input label="Only Show Number of Lines" placeholder="1000" required
|
||||
id="numberOfLines"></x-forms.input>
|
||||
<x-forms.button type="submit">Refresh</x-forms.button>
|
||||
</form>
|
||||
<div id="screen" x-data="{ fullscreen: false, alwaysScroll: false, intervalId: null }" :class="fullscreen ? 'fullscreen' : 'w-full py-4 mx-auto'">
|
||||
<div class="relative flex flex-col-reverse w-full p-4 pt-6 overflow-y-auto text-white bg-coolgray-100 scrollbar border-coolgray-300"
|
||||
:class="fullscreen ? '' : 'max-h-[40rem] border border-solid rounded'">
|
||||
<button title="Minimize" x-show="fullscreen" class="fixed top-4 right-4"
|
||||
x-on:click="makeFullscreen"><svg class="icon" viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
|
||||
stroke-width="2" d="M6 14h4m0 0v4m0-4l-6 6m14-10h-4m0 0V6m0 4l6-6" />
|
||||
</svg></button>
|
||||
<button title="Go Top" x-show="fullscreen" class="fixed top-4 right-28" x-on:click="goTop"> <svg
|
||||
class="icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
|
||||
stroke-width="2" d="M12 5v14m4-10l-4-4M8 9l4-4" />
|
||||
</svg></button>
|
||||
<button title="Follow Logs" x-show="fullscreen" :class="alwaysScroll ? 'text-warning' : ''"
|
||||
class="fixed top-4 right-16" x-on:click="toggleScroll"><svg class="icon" viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
|
||||
stroke-width="2" d="M12 5v14m4-4l-4 4m-4-4l4 4" />
|
||||
</svg></button>
|
||||
|
||||
<button title="Fullscreen" x-show="!fullscreen" class="absolute top-2 right-2"
|
||||
x-on:click="makeFullscreen"><svg class=" icon" viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="none">
|
||||
<path
|
||||
d="M24 0v24H0V0h24ZM12.593 23.258l-.011.002l-.071.035l-.02.004l-.014-.004l-.071-.035c-.01-.004-.019-.001-.024.005l-.004.01l-.017.428l.005.02l.01.013l.104.074l.015.004l.012-.004l.104-.074l.012-.016l.004-.017l-.017-.427c-.002-.01-.009-.017-.017-.018Zm.265-.113l-.013.002l-.185.093l-.01.01l-.003.011l.018.43l.005.012l.008.007l.201.093c.012.004.023 0 .029-.008l.004-.014l-.034-.614c-.003-.012-.01-.02-.02-.022Zm-.715.002a.023.023 0 0 0-.027.006l-.006.014l-.034.614c0 .012.007.02.017.024l.015-.002l.201-.093l.01-.008l.004-.011l.017-.43l-.003-.012l-.01-.01l-.184-.092Z" />
|
||||
<path fill="currentColor"
|
||||
d="M9.793 12.793a1 1 0 0 1 1.497 1.32l-.083.094L6.414 19H9a1 1 0 0 1 .117 1.993L9 21H4a1 1 0 0 1-.993-.883L3 20v-5a1 1 0 0 1 1.993-.117L5 15v2.586l4.793-4.793ZM20 3a1 1 0 0 1 .993.883L21 4v5a1 1 0 0 1-1.993.117L19 9V6.414l-4.793 4.793a1 1 0 0 1-1.497-1.32l.083-.094L17.586 5H15a1 1 0 0 1-.117-1.993L15 3h5Z" />
|
||||
</g>
|
||||
</svg></button>
|
||||
<pre id="logs" class="font-mono whitespace-pre-wrap">{{ $outputs }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
function makeFullscreen() {
|
||||
this.fullscreen = !this.fullscreen;
|
||||
if (this.fullscreen === false) {
|
||||
this.alwaysScroll = false;
|
||||
clearInterval(this.intervalId);
|
||||
}
|
||||
}
|
||||
|
||||
function toggleScroll() {
|
||||
this.alwaysScroll = !this.alwaysScroll;
|
||||
|
||||
if (this.alwaysScroll) {
|
||||
this.intervalId = setInterval(() => {
|
||||
const screen = document.getElementById('screen');
|
||||
const logs = document.getElementById('logs');
|
||||
if (screen.scrollTop !== logs.scrollHeight) {
|
||||
screen.scrollTop = logs.scrollHeight;
|
||||
}
|
||||
}, 100);
|
||||
} else {
|
||||
clearInterval(this.intervalId);
|
||||
this.intervalId = null;
|
||||
}
|
||||
}
|
||||
|
||||
function goTop() {
|
||||
this.alwaysScroll = false;
|
||||
clearInterval(this.intervalId);
|
||||
const screen = document.getElementById('screen');
|
||||
screen.scrollTop = 0;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</div>
|
||||
|
||||
function toggleScroll() {
|
||||
this.alwaysScroll = !this.alwaysScroll;
|
||||
|
||||
if (this.alwaysScroll) {
|
||||
this.intervalId = setInterval(() => {
|
||||
const screen = document.getElementById('screen');
|
||||
const logs = document.getElementById('logs');
|
||||
if (screen.scrollTop !== logs.scrollHeight) {
|
||||
screen.scrollTop = logs.scrollHeight;
|
||||
}
|
||||
}, 100);
|
||||
} else {
|
||||
clearInterval(this.intervalId);
|
||||
this.intervalId = null;
|
||||
}
|
||||
}
|
||||
|
||||
function goTop() {
|
||||
this.alwaysScroll = false;
|
||||
clearInterval(this.intervalId);
|
||||
const screen = document.getElementById('screen');
|
||||
screen.scrollTop = 0;
|
||||
}
|
||||
</script>
|
||||
</div>
|
||||
|
@ -0,0 +1,15 @@
|
||||
<div class="flex gap-2">
|
||||
<h3 class="text-white">File: {{ str_replace('|', '.', $fileName) }}</h3>
|
||||
<div class="flex gap-2">
|
||||
<x-slide-over>
|
||||
<x-slot:title>Edit Configuration</x-slot:title>
|
||||
<x-slot:content>
|
||||
<livewire:server.proxy.new-dynamic-configuration :server_id="$server_id" :fileName="$fileName" :value="$value"
|
||||
:newFile="$newFile" wire:key="{{ $fileName }}" />
|
||||
</x-slot:content>
|
||||
<button @click="slideOverOpen=true"
|
||||
class="font-normal text-white normal-case border-none rounded btn btn-primary btn-sm no-animation">Edit</button>
|
||||
</x-slide-over>
|
||||
</div>
|
||||
<x-forms.button isError wire:click="delete('{{ $fileName }}')">Delete</x-forms.button>
|
||||
</div>
|
@ -0,0 +1,53 @@
|
||||
<div>
|
||||
<x-server.navbar :server="$server" :parameters="$parameters" />
|
||||
<div class="flex gap-2">
|
||||
<x-server.sidebar :server="$server" :parameters="$parameters" />
|
||||
<div class="w-full">
|
||||
@if ($server->isFunctional())
|
||||
<div class="flex gap-2">
|
||||
<div>
|
||||
<div class="flex gap-2">
|
||||
<h2>Dynamic Configurations</h2>
|
||||
<x-forms.button wire:click='loadDynamicConfigurations'>Reload</x-forms.button>
|
||||
<x-slide-over>
|
||||
<x-slot:title>New Dynamic Configuration</x-slot:title>
|
||||
<x-slot:content>
|
||||
<livewire:server.proxy.new-dynamic-configuration />
|
||||
</x-slot:content>
|
||||
<button @click="slideOverOpen=true"
|
||||
class="font-normal text-white normal-case border-none rounded btn btn-primary btn-sm no-animation">+
|
||||
Add</button>
|
||||
</x-slide-over>
|
||||
</div>
|
||||
<div class='pb-4'>You can add dynamic Traefik configurations here.</div>
|
||||
</div>
|
||||
</div>
|
||||
<div wire:loading wire:target="loadDynamicConfigurations">
|
||||
<x-loading text="Loading dynamic configurations..." />
|
||||
</div>
|
||||
<div x-init="$wire.loadDynamicConfigurations" class="flex flex-col gap-4">
|
||||
@if ($contents?->isNotEmpty())
|
||||
@foreach ($contents as $fileName => $value)
|
||||
<div class="flex flex-col gap-2 py-2">
|
||||
@if (str_replace('|', '.', $fileName) === 'coolify.yaml')
|
||||
<div>
|
||||
<h3 class="text-white">File: {{ str_replace('|', '.', $fileName) }}</h3>
|
||||
</div>
|
||||
<x-forms.textarea disabled name="proxy_settings"
|
||||
wire:model="contents.{{ $fileName }}" rows="10" />
|
||||
@else
|
||||
<livewire:server.proxy.dynamic-configuration-navbar :server_id="$server->id"
|
||||
:fileName="$fileName" :value="$value" :newFile="false"
|
||||
wire:key="{{ $fileName }}-{{ $loop->index }}" />
|
||||
<x-forms.textarea disabled wire:model="contents.{{ $fileName }}"
|
||||
rows="10" />
|
||||
@endif
|
||||
</div>
|
||||
@endforeach
|
||||
@endif
|
||||
</div>
|
||||
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -3,6 +3,7 @@
|
||||
<div class="flex gap-2">
|
||||
<x-server.sidebar :server="$server" :parameters="$parameters" />
|
||||
<div class="w-full">
|
||||
<h2 class="pb-4">Logs</h2>
|
||||
<livewire:project.shared.get-logs :server="$server" container="coolify-proxy" />
|
||||
</div>
|
||||
</div>
|
||||
|
@ -0,0 +1,5 @@
|
||||
<form wire:submit.prevent="addDynamicConfiguration" class="flex flex-col gap-4">
|
||||
<x-forms.input id="fileName" label="Filename (.yaml or .yml)" required />
|
||||
<x-forms.textarea id="value" label="Configuration" required rows="20" />
|
||||
<x-forms.button type="submit" @click="slideOverOpen=false">Save</x-forms.button>
|
||||
</form>
|
@ -67,6 +67,7 @@
|
||||
use App\Livewire\Server\Destination\Show as DestinationShow;
|
||||
use App\Livewire\Server\LogDrains;
|
||||
use App\Livewire\Server\PrivateKey\Show as PrivateKeyShow;
|
||||
use App\Livewire\Server\Proxy\DynamicConfigurations as ProxyDynamicConfigurations;
|
||||
use App\Livewire\Server\Proxy\Show as ProxyShow;
|
||||
use App\Livewire\Server\Proxy\Logs as ProxyLogs;
|
||||
use App\Livewire\Source\Github\Change as GitHubChange;
|
||||
@ -177,6 +178,7 @@
|
||||
Route::get('/', ServerShow::class)->name('server.show');
|
||||
Route::get('/resources', ResourcesShow::class)->name('server.resources');
|
||||
Route::get('/proxy', ProxyShow::class)->name('server.proxy');
|
||||
Route::get('/proxy/dynamic', ProxyDynamicConfigurations::class)->name('server.proxy.dynamic-confs');
|
||||
Route::get('/proxy/logs', ProxyLogs::class)->name('server.proxy.logs');
|
||||
Route::get('/private-key', PrivateKeyShow::class)->name('server.private-key');
|
||||
Route::get('/destinations', DestinationShow::class)->name('server.destinations');
|
||||
|
Loading…
Reference in New Issue
Block a user