Merge pull request #137 from vitodeploy/phpmyadmin

add phpmyadmin
This commit is contained in:
Saeed Vaziry 2024-03-27 13:34:40 +01:00 committed by GitHub
commit d01d406d3d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
58 changed files with 432 additions and 95 deletions

View File

@ -54,3 +54,4 @@ ## Credits
- Prettier - Prettier
- Postcss - Postcss
- Flowbite - Flowbite
- svgrepo.com

View File

@ -0,0 +1,55 @@
<?php
namespace App\Actions\Service;
use App\Enums\ServiceStatus;
use App\Models\Server;
use App\Models\Service;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
class Create
{
public function create(Server $server, array $input): Service
{
$this->validate($server, $input);
$service = new Service([
'name' => $input['type'],
'type' => $input['type'],
'version' => $input['version'],
'status' => ServiceStatus::INSTALLING,
]);
Validator::make($input, $service->handler()->creationRules($input))->validate();
$service->type_data = $service->handler()->creationData($input);
$service->save();
$service->handler()->create();
dispatch(function () use ($service) {
$service->handler()->install();
$service->status = ServiceStatus::READY;
$service->save();
})->catch(function () use ($service) {
$service->handler()->delete();
$service->delete();
})->onConnection('ssh');
return $service;
}
private function validate(Server $server, array $input): void
{
Validator::make($input, [
'type' => [
'required',
Rule::in(config('core.add_on_services')),
Rule::unique('services', 'type')->where('server_id', $server->id),
],
'version' => 'required',
])->validate();
}
}

View File

@ -10,7 +10,9 @@ final class Database
const MYSQL80 = 'mysql80'; const MYSQL80 = 'mysql80';
const MARIADB = 'mariadb'; const MARIADB103 = 'mariadb103';
const MARIADB104 = 'mariadb104';
const POSTGRESQL12 = 'postgresql12'; const POSTGRESQL12 = 'postgresql12';

View File

@ -11,4 +11,6 @@ final class SiteType
const LARAVEL = 'laravel'; const LARAVEL = 'laravel';
const WORDPRESS = 'wordpress'; const WORDPRESS = 'wordpress';
const PHPMYADMIN = 'phpmyadmin';
} }

View File

@ -2,11 +2,14 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Actions\Service\Create;
use App\Facades\Toast; use App\Facades\Toast;
use App\Helpers\HtmxResponse;
use App\Models\Server; use App\Models\Server;
use App\Models\Service; use App\Models\Service;
use Illuminate\Contracts\View\View; use Illuminate\Contracts\View\View;
use Illuminate\Http\RedirectResponse; use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
class ServiceController extends Controller class ServiceController extends Controller
{ {
@ -62,4 +65,13 @@ public function disable(Server $server, Service $service): RedirectResponse
return back(); return back();
} }
public function install(Server $server, Request $request): HtmxResponse
{
app(Create::class)->create($server, $request->input());
Toast::success('Service is being uninstalled!');
return htmx()->back();
}
} }

View File

@ -4,6 +4,7 @@
use App\Actions\Service\Manage; use App\Actions\Service\Manage;
use App\Exceptions\ServiceInstallationFailed; use App\Exceptions\ServiceInstallationFailed;
use App\SSH\Services\AddOnServices\AbstractAddOnService;
use App\SSH\Services\Database\Database as DatabaseHandler; use App\SSH\Services\Database\Database as DatabaseHandler;
use App\SSH\Services\Firewall\Firewall as FirewallHandler; use App\SSH\Services\Firewall\Firewall as FirewallHandler;
use App\SSH\Services\PHP\PHP as PHPHandler; use App\SSH\Services\PHP\PHP as PHPHandler;
@ -53,7 +54,9 @@ public static function boot(): void
parent::boot(); parent::boot();
static::creating(function (Service $service) { static::creating(function (Service $service) {
if (array_key_exists($service->name, config('core.service_units'))) {
$service->unit = config('core.service_units')[$service->name][$service->server->os][$service->version]; $service->unit = config('core.service_units')[$service->name][$service->server->os][$service->version];
}
}); });
} }
@ -63,7 +66,7 @@ public function server(): BelongsTo
} }
public function handler( public function handler(
): PHPHandler|WebserverHandler|DatabaseHandler|FirewallHandler|ProcessManagerHandler|RedisHandler { ): PHPHandler|WebserverHandler|DatabaseHandler|FirewallHandler|ProcessManagerHandler|RedisHandler|AbstractAddOnService {
$handler = config('core.service_handlers')[$this->name]; $handler = config('core.service_handlers')[$this->name];
return new $handler($this); return new $handler($this);
@ -81,26 +84,26 @@ public function validateInstall($result): void
public function start(): void public function start(): void
{ {
app(Manage::class)->start($this); $this->unit && app(Manage::class)->start($this);
} }
public function stop(): void public function stop(): void
{ {
app(Manage::class)->stop($this); $this->unit && app(Manage::class)->stop($this);
} }
public function restart(): void public function restart(): void
{ {
app(Manage::class)->restart($this); $this->unit && app(Manage::class)->restart($this);
} }
public function enable(): void public function enable(): void
{ {
app(Manage::class)->enable($this); $this->unit && app(Manage::class)->enable($this);
} }
public function disable(): void public function disable(): void
{ {
app(Manage::class)->disable($this); $this->unit && app(Manage::class)->disable($this);
} }
} }

View File

@ -131,4 +131,21 @@ public function runScript(string $path, string $script, ?int $siteId = null): Se
return $ssh->log; return $ssh->log;
} }
public function download(string $url, string $path): string
{
return $this->server->ssh()->exec(
$this->getScript('download.sh', [
'url' => $url,
'path' => $path,
])
);
}
public function unzip(string $path): string
{
return $this->server->ssh()->exec(
'unzip '.$path
);
}
} }

View File

@ -0,0 +1,3 @@
if ! wget __url__ -O __path__; then
echo 'VITO_SSH_ERROR' && exit 1
fi

View File

@ -0,0 +1,23 @@
<?php
namespace App\SSH\PHPMyAdmin;
use App\Models\Site;
use App\SSH\HasScripts;
class PHPMyAdmin
{
use HasScripts;
public function install(Site $site): void
{
$site->server->ssh()->exec(
$this->getScript('install.sh', [
'version' => $site->type_data['version'],
'path' => $site->path,
]),
'install-phpmyadmin',
$site->id
);
}
}

View File

@ -0,0 +1,25 @@
sudo rm -rf phpmyadmin
sudo rm -rf __path__
if ! wget https://files.phpmyadmin.net/phpMyAdmin/__version__/phpMyAdmin-__version__-all-languages.zip; then
echo 'VITO_SSH_ERROR' && exit 1
fi
if ! unzip phpMyAdmin-__version__-all-languages.zip; then
echo 'VITO_SSH_ERROR' && exit 1
fi
if ! rm -rf phpMyAdmin-__version__-all-languages.zip; then
echo 'VITO_SSH_ERROR' && exit 1
fi
if ! mv phpMyAdmin-__version__-all-languages __path__; then
echo 'VITO_SSH_ERROR' && exit 1
fi
if ! mv __path__/config.sample.inc.php __path__/config.inc.php; then
echo 'VITO_SSH_ERROR' && exit 1
fi
echo "PHPMyAdmin installed!"

View File

@ -0,0 +1,18 @@
<?php
namespace App\SSH\Services\AddOnServices;
use App\SSH\Services\ServiceInterface;
abstract class AbstractAddOnService implements ServiceInterface
{
abstract public function creationRules(array $input): array;
abstract public function creationData(array $input): array;
abstract public function create(): void;
abstract public function delete(): void;
abstract public function data(): array;
}

49
app/SiteTypes/PHPMyAdmin.php Executable file
View File

@ -0,0 +1,49 @@
<?php
namespace App\SiteTypes;
use Illuminate\Validation\Rule;
class PHPMyAdmin extends PHPSite
{
public function supportedFeatures(): array
{
return [
//
];
}
public function createRules(array $input): array
{
return [
'php_version' => [
'required',
Rule::in($this->site->server->installedPHPVersions()),
],
'version' => 'required|string',
];
}
public function createFields(array $input): array
{
return [
'web_directory' => '',
'php_version' => $input['php_version'] ?? '',
];
}
public function data(array $input): array
{
return [
'version' => $input['version'],
];
}
public function install(): void
{
$this->site->server->webserver()->handler()->createVHost($this->site);
$this->progress(30);
app(\App\SSH\PHPMyAdmin\PHPMyAdmin::class)->install($this->site);
$this->progress(65);
}
}

View File

@ -13,6 +13,7 @@
use App\ServerProviders\Vultr; use App\ServerProviders\Vultr;
use App\SiteTypes\Laravel; use App\SiteTypes\Laravel;
use App\SiteTypes\PHPBlank; use App\SiteTypes\PHPBlank;
use App\SiteTypes\PHPMyAdmin;
use App\SiteTypes\PHPSite; use App\SiteTypes\PHPSite;
use App\SiteTypes\Wordpress; use App\SiteTypes\Wordpress;
use App\SourceControlProviders\Bitbucket; use App\SourceControlProviders\Bitbucket;
@ -177,6 +178,9 @@
'ufw' => Ufw::class, 'ufw' => Ufw::class,
'supervisor' => Supervisor::class, 'supervisor' => Supervisor::class,
], ],
'add_on_services' => [
// add-on services
],
'service_units' => [ 'service_units' => [
'nginx' => [ 'nginx' => [
'ubuntu_18' => [ 'ubuntu_18' => [
@ -320,12 +324,14 @@
\App\Enums\SiteType::PHP_BLANK, \App\Enums\SiteType::PHP_BLANK,
\App\Enums\SiteType::LARAVEL, \App\Enums\SiteType::LARAVEL,
\App\Enums\SiteType::WORDPRESS, \App\Enums\SiteType::WORDPRESS,
\App\Enums\SiteType::PHPMYADMIN,
], ],
'site_types_class' => [ 'site_types_class' => [
\App\Enums\SiteType::PHP => PHPSite::class, \App\Enums\SiteType::PHP => PHPSite::class,
\App\Enums\SiteType::PHP_BLANK => PHPBlank::class, \App\Enums\SiteType::PHP_BLANK => PHPBlank::class,
\App\Enums\SiteType::LARAVEL => Laravel::class, \App\Enums\SiteType::LARAVEL => Laravel::class,
\App\Enums\SiteType::WORDPRESS => Wordpress::class, \App\Enums\SiteType::WORDPRESS => Wordpress::class,
\App\Enums\SiteType::PHPMYADMIN => PHPMyAdmin::class,
], ],
/* /*

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,6 +1,6 @@
{ {
"resources/css/app.css": { "resources/css/app.css": {
"file": "assets/app-a972b4b2.css", "file": "assets/app-eff15392.css",
"isEntry": true, "isEntry": true,
"src": "resources/css/app.css" "src": "resources/css/app.css"
}, },

View File

@ -1 +1,9 @@
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="list" class="svg-inline--fa fa-list fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M80 368H16a16 16 0 0 0-16 16v64a16 16 0 0 0 16 16h64a16 16 0 0 0 16-16v-64a16 16 0 0 0-16-16zm0-320H16A16 16 0 0 0 0 64v64a16 16 0 0 0 16 16h64a16 16 0 0 0 16-16V64a16 16 0 0 0-16-16zm0 160H16a16 16 0 0 0-16 16v64a16 16 0 0 0 16 16h64a16 16 0 0 0 16-16v-64a16 16 0 0 0-16-16zm416 176H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16z"></path></svg> <?xml version="1.0" encoding="utf-8"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="48" height="48" fill="white" fill-opacity="0.01" />
<rect x="4" y="8" width="40" height="32" rx="2" fill="#2F88FF" stroke="#000000" stroke-width="4"
stroke-linejoin="round" />
<path d="M12 18L19 24L12 30" stroke="white" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" />
<path d="M23 32H36" stroke="white" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" />
</svg>

Before

Width:  |  Height:  |  Size: 786 B

After

Width:  |  Height:  |  Size: 654 B

View File

@ -1 +1,9 @@
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="fire" class="svg-inline--fa fa-fire fa-w-12" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path fill="currentColor" d="M216 23.86c0-23.8-30.65-32.77-44.15-13.04C48 191.85 224 200 224 288c0 35.63-29.11 64.46-64.85 63.99-35.17-.45-63.15-29.77-63.15-64.94v-85.51c0-21.7-26.47-32.23-41.43-16.5C27.8 213.16 0 261.33 0 320c0 105.87 86.13 192 192 192s192-86.13 192-192c0-170.29-168-193-168-296.14z"></path></svg> <?xml version="1.0" encoding="utf-8"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 14 14" role="img" focusable="false" aria-hidden="true"
xmlns="http://www.w3.org/2000/svg">
<path fill="olive"
d="M12.123424 3.09708C9.1750899 1.36849 7.0055019 1 7.0055019 1s-.0023.00035-.0055.00096c-.0033-.00061-.0055-.00096-.0055-.00096s-2.169706.36849-5.117921 2.09708c0 0-.390942 8.72672 5.117921 9.90292.0019-.00035.0036-.00096.0055-.001.0019.00035.0036.00096.0055.001 5.5087451-1.1762 5.1179221-9.90292 5.1179221-9.90292z" />
<path fill="#ff0"
d="M7.0058749 1.74098c.482647.10437 2.177431.53826 4.3878041 1.77816-.0096 1.61164-.285353 7.71006-4.3878041 8.73494V1.74098z" />
</svg>

Before

Width:  |  Height:  |  Size: 499 B

After

Width:  |  Height:  |  Size: 781 B

View File

@ -0,0 +1,6 @@
<div>
<x-simple-card class="flex items-center justify-between">
<span>PHPMyAdmin is installed and ready to use!</span>
<x-secondary-button :href="$site->getUrl()" target="_blank">Open</x-secondary-button>
</x-simple-card>
</div>

View File

@ -3,7 +3,7 @@
<span> <span>
{{ __("Your Wordpress site is installed and ready to use! ") }} {{ __("Your Wordpress site is installed and ready to use! ") }}
</span> </span>
<x-secondary-button :href="$site->url" target="_blank"> <x-secondary-button :href="$site->getUrl()" target="_blank">
{{ __("Open Website") }} {{ __("Open Website") }}
</x-secondary-button> </x-secondary-button>
</x-simple-card> </x-simple-card>

View File

@ -0,0 +1,14 @@
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
{{ $attributes }}
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M18.364 18.364A9 9 0 0 0 5.636 5.636m12.728 12.728A9 9 0 0 1 5.636 5.636m12.728 12.728L5.636 5.636"
/>
</svg>

After

Width:  |  Height:  |  Size: 355 B

View File

@ -1,5 +1,6 @@
@props([ @props([
"href", "href",
"disabled" => false,
]) ])
@php @php
@ -7,12 +8,12 @@
"inline-flex w-max items-center justify-center px-2 py-1 font-semibold capitalize outline-0 transition hover:opacity-50 focus:ring focus:ring-primary-200 disabled:opacity-25 dark:focus:ring-primary-700 dark:focus:ring-opacity-40"; "inline-flex w-max items-center justify-center px-2 py-1 font-semibold capitalize outline-0 transition hover:opacity-50 focus:ring focus:ring-primary-200 disabled:opacity-25 dark:focus:ring-primary-700 dark:focus:ring-opacity-40";
@endphp @endphp
@if (isset($href)) @if (isset($href) && ! $disabled)
<a href="{{ $href }}" {{ $attributes->merge(["class" => $class]) }}> <a href="{{ $href }}" {{ $attributes->merge(["class" => $class]) }}>
{{ $slot }} {{ $slot }}
</a> </a>
@else @else
<button {{ $attributes->merge(["type" => "submit", "class" => $class]) }}> <button {{ $attributes->merge(["type" => "submit", "class" => $class, "disabled" => $disabled]) }}>
{{ $slot }} {{ $slot }}
</button> </button>
@endif @endif

View File

@ -1,5 +1,5 @@
<div <div
class="flex h-20 items-center justify-between rounded-b-md rounded-t-md border border-gray-200 bg-white p-7 text-center dark:border-gray-700 dark:bg-gray-800" {{ $attributes->merge(["class" => "flex h-20 items-center justify-between rounded-b-md rounded-t-md border border-gray-200 bg-white p-7 text-center dark:border-gray-700 dark:bg-gray-800"]) }}
> >
{{ $slot }} {{ $slot }}
</div> </div>

View File

@ -5,16 +5,13 @@
]) ])
<div <div
id="{{ $id }}" {{ $attributes->merge(["interval" => $interval, "id" => $id, "hx-get" => request()->getUri(), "hx-trigger" => "every " . $interval, "hx-swap" => "outerHTML"]) }}
hx-get="{{ request()->getUri() }}"
hx-trigger="every {{ $interval }}"
@if ($target) @if ($target)
hx-target="{{ $target }}" hx-target="{{ $target }}"
hx-select="{{ $target }}" hx-select="{{ $target }}"
@else @else
hx-select="#{{ $id }}" hx-select="#{{ $id }}"
@endif @endif
hx-swap="outerHTML"
> >
{{ $slot }} {{ $slot }}
</div> </div>

View File

@ -7,7 +7,7 @@
@php @php
$class = $class =
"inline-flex h-9 min-w-max items-center rounded-md border border-gray-300 bg-white px-4 py-1 font-semibold text-gray-700 shadow-sm transition duration-150 ease-in-out hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-gray-500 focus:ring-offset-2 disabled:opacity-25 dark:border-gray-500 dark:bg-gray-800 dark:text-gray-300 dark:hover:bg-gray-700 dark:focus:ring-offset-gray-800"; "inline-flex h-9 w-max min-w-max items-center justify-center rounded-md border border-gray-300 bg-white px-4 py-1 font-semibold text-gray-700 shadow-sm transition duration-150 ease-in-out hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-gray-500 focus:ring-offset-2 disabled:opacity-25 dark:border-gray-500 dark:bg-gray-800 dark:text-gray-300 dark:hover:bg-gray-700 dark:focus:ring-offset-gray-800";
@endphp @endphp
@if (isset($href)) @if (isset($href))

View File

@ -2,4 +2,6 @@
<x-slot name="pageTitle">{{ __("Services") }}</x-slot> <x-slot name="pageTitle">{{ __("Services") }}</x-slot>
@include("services.partials.services-list") @include("services.partials.services-list")
{{-- @include("services.partials.add-ons") --}}
</x-server-layout> </x-server-layout>

View File

@ -0,0 +1 @@
@include("services.partials.unit-actions")

View File

@ -0,0 +1 @@
@include("services.partials.unit-actions")

View File

@ -0,0 +1 @@
@include("services.partials.unit-actions")

View File

@ -0,0 +1 @@
@include("services.partials.unit-actions")

View File

@ -0,0 +1 @@
@include("services.partials.unit-actions")

View File

@ -0,0 +1 @@
@include("services.partials.unit-actions")

View File

@ -0,0 +1 @@
@include("services.partials.unit-actions")

View File

@ -0,0 +1 @@
@include("services.partials.unit-actions")

View File

@ -0,0 +1,33 @@
<div>
<x-card-header>
<x-slot name="title">Supported Services</x-slot>
<x-slot name="description">Here you can find the supported services to install</x-slot>
<x-slot name="aside"></x-slot>
</x-card-header>
<div class="grid grid-cols-1 gap-5 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
@foreach (config("core.add_on_services") as $addOn)
<div
class="relative flex h-auto flex-col items-center justify-between space-y-3 rounded-b-md rounded-t-md border border-gray-200 bg-white text-center dark:border-gray-700 dark:bg-gray-800"
>
<div class="space-y-3 p-5">
<div class="flex items-center justify-center">
<img src="{{ asset("static/images/" . $addOn . ".svg") }}" class="h-20 w-20" alt="" />
</div>
<div class="flex flex-grow flex-col items-start justify-center">
<div class="flex items-center">
<div class="flex items-center text-lg">
{{ $addOn }}
</div>
</div>
</div>
</div>
<div
class="flex w-full items-center justify-between rounded-b-md border-t border-t-gray-200 bg-gray-50 p-2 dark:border-t-gray-600 dark:bg-gray-700"
>
@include("services.partials.add-on-installers." . $addOn)
</div>
</div>
@endforeach
</div>
</div>

View File

@ -1,80 +1,42 @@
<div> <div>
<x-card-header> <x-card-header>
<x-slot name="title">{{ __("Services") }}</x-slot> <x-slot name="title">Installed Services</x-slot>
<x-slot name="description"> <x-slot name="description">All services that we installed on your server are here</x-slot>
{{ __("All services that we installed on your server are here") }}
</x-slot>
<x-slot name="aside"></x-slot> <x-slot name="aside"></x-slot>
</x-card-header> </x-card-header>
<x-live id="live-services" interval="5s"> <x-live id="live-services">
<div class="space-y-3"> <div class="grid grid-cols-1 gap-5 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
@foreach ($services as $service) @foreach ($services as $service)
<x-item-card> <div
<div class="flex-none"> class="relative flex h-auto flex-col items-center justify-between space-y-3 rounded-b-md rounded-t-md border border-gray-200 bg-white text-center dark:border-gray-700 dark:bg-gray-800"
<img src="{{ asset("static/images/" . $service->name . ".svg") }}" class="h-10 w-10" alt="" /> >
</div> <div class="absolute right-3 top-3">
<div class="ml-3 flex flex-grow flex-col items-start justify-center">
<div class="flex items-center">
<div class="mr-2">{{ $service->name }}:{{ $service->version }}</div>
@include("services.partials.status", ["status" => $service->status]) @include("services.partials.status", ["status" => $service->status])
</div> </div>
<div class="space-y-3 p-5">
<div class="flex items-center justify-center">
<img
src="{{ asset("static/images/" . $service->name . ".svg") }}"
class="h-20 w-20"
alt=""
/>
</div> </div>
<div class="flex flex-grow flex-col items-start justify-center">
<div class="flex items-center"> <div class="flex items-center">
<x-dropdown> <div class="flex items-center text-lg">
<x-slot name="trigger"> {{ $service->name }}
<x-secondary-button> <x-status status="disabled" class="ml-1">{{ $service->version }}</x-status>
{{ __("Actions") }} </div>
</x-secondary-button> </div>
</x-slot> </div>
</div>
<x-slot name="content"> <div
@if ($service->unit) class="flex w-full items-center justify-between rounded-b-md border-t border-t-gray-200 bg-gray-50 p-2 dark:border-t-gray-600 dark:bg-gray-700"
@if ($service->status == \App\Enums\ServiceStatus::STOPPED) >
<x-dropdown-link @include("services.partials.actions." . $service->name)
class="cursor-pointer" </div>
href="{{ route('servers.services.start', ['server' => $server, 'service' => $service]) }}"
>
{{ __("Start") }}
</x-dropdown-link>
@endif
@if ($service->status == \App\Enums\ServiceStatus::READY)
<x-dropdown-link
class="cursor-pointer"
href="{{ route('servers.services.stop', ['server' => $server, 'service' => $service]) }}"
>
{{ __("Stop") }}
</x-dropdown-link>
@endif
<x-dropdown-link
class="cursor-pointer"
href="{{ route('servers.services.restart', ['server' => $server, 'service' => $service]) }}"
>
{{ __("Restart") }}
</x-dropdown-link>
@if ($service->status == \App\Enums\ServiceStatus::DISABLED)
<x-dropdown-link
class="cursor-pointer"
href="{{ route('servers.services.enable', ['server' => $server, 'service' => $service]) }}"
>
{{ __("Enable") }}
</x-dropdown-link>
@endif
<x-dropdown-link
class="cursor-pointer"
href="{{ route('servers.services.disable', ['server' => $server, 'service' => $service]) }}"
>
{{ __("Disable") }}
</x-dropdown-link>
@endif
</x-slot>
</x-dropdown>
</div> </div>
</x-item-card>
@endforeach @endforeach
</div> </div>
</x-live> </x-live>

View File

@ -0,0 +1,42 @@
<x-icon-button
data-tooltip="Restart Service"
class="cursor-pointer"
href="{{ route('servers.services.restart', ['server' => $server, 'service' => $service]) }}"
>
<x-heroicon name="o-arrow-path" class="h-5 w-5" />
</x-icon-button>
<x-icon-button
:disabled="$service->status != \App\Enums\ServiceStatus::STOPPED"
data-tooltip="Start Service"
class="cursor-pointer"
href="{{ route('servers.services.start', ['server' => $server, 'service' => $service]) }}"
>
<x-heroicon name="o-play" class="h-5 w-5 text-green-400" />
</x-icon-button>
<x-icon-button
data-tooltip="Stop Service"
:disabled="$service->status != \App\Enums\ServiceStatus::READY"
class="cursor-pointer"
href="{{ route('servers.services.stop', ['server' => $server, 'service' => $service]) }}"
>
<x-heroicon name="o-stop" class="h-5 w-5 text-red-400" />
</x-icon-button>
<x-icon-button
:disabled="$service->status != \App\Enums\ServiceStatus::DISABLED"
data-tooltip="Enable Service"
class="cursor-pointer"
href="{{ route('servers.services.enable', ['server' => $server, 'service' => $service]) }}"
>
<x-heroicon name="o-check" class="h-5 w-5" />
</x-icon-button>
<x-icon-button
data-tooltip="Disable Service"
class="cursor-pointer"
href="{{ route('servers.services.disable', ['server' => $server, 'service' => $service]) }}"
>
<x-heroicon name="o-no-symbol" class="h-5 w-5" />
</x-icon-button>

View File

@ -0,0 +1,13 @@
@include("sites.partials.create.fields.php-version")
<div>
<x-input-label for="version" :value="__('Version')" />
<x-select-input id="version" name="version" class="mt-1 w-full">
<option value="" selected>{{ __("Select") }}</option>
<option value="5.1.2" @if(old('version') == '5.1.2') selected @endif>PHPMyAdmin 5.1.2</option>
<option value="4.9.11" @if(old('version') == '4.9.11') selected @endif>PHPMyAdmin 4.9.11</option>
</x-select-input>
@error("version")
<x-input-error class="mt-2" :messages="$message" />
@enderror
</div>

View File

@ -124,6 +124,7 @@
Route::get('/{server}/services/{service}/restart', [ServiceController::class, 'restart'])->name('servers.services.restart'); Route::get('/{server}/services/{service}/restart', [ServiceController::class, 'restart'])->name('servers.services.restart');
Route::get('/{server}/services/{service}/enable', [ServiceController::class, 'enable'])->name('servers.services.enable'); Route::get('/{server}/services/{service}/enable', [ServiceController::class, 'enable'])->name('servers.services.enable');
Route::get('/{server}/services/{service}/disable', [ServiceController::class, 'disable'])->name('servers.services.disable'); Route::get('/{server}/services/{service}/disable', [ServiceController::class, 'disable'])->name('servers.services.disable');
Route::post('/{server}/services/install', [ServiceController::class, 'install'])->name('servers.services.install');
// console // console
Route::get('/{server}/console', [ConsoleController::class, 'index'])->name('servers.console'); Route::get('/{server}/console', [ConsoleController::class, 'index'])->name('servers.console');

View File

@ -23,7 +23,7 @@ public function test_visit_application()
'site' => $this->site, 'site' => $this->site,
]) ])
) )
->assertOk() ->assertSuccessful()
->assertSee($this->site->domain); ->assertSee($this->site->domain);
} }
@ -88,7 +88,7 @@ public function test_deploy(): void
'server' => $this->server, 'server' => $this->server,
'site' => $this->site, 'site' => $this->site,
])) ]))
->assertOk() ->assertSuccessful()
->assertSee('test commit message'); ->assertSee('test commit message');
$deployment = $this->site->deployments()->first(); $deployment = $this->site->deployments()->first();

View File

@ -15,6 +15,7 @@ public function test_see_console(): void
$this->actingAs($this->user); $this->actingAs($this->user);
$this->get(route('servers.console', $this->server)) $this->get(route('servers.console', $this->server))
->assertSuccessful()
->assertSeeText('Headless Console'); ->assertSeeText('Headless Console');
} }

View File

@ -22,6 +22,7 @@ public function test_see_cronjobs_list()
]); ]);
$this->get(route('servers.cronjobs', $this->server)) $this->get(route('servers.cronjobs', $this->server))
->assertSuccessful()
->assertSeeText($cronjob->frequencyLabel()); ->assertSeeText($cronjob->frequencyLabel());
} }

View File

@ -98,6 +98,7 @@ public function test_see_backups_list(): void
]); ]);
$this->get(route('servers.databases.backups', [$this->server, $backup])) $this->get(route('servers.databases.backups', [$this->server, $backup]))
->assertSuccessful()
->assertSee($backup->database->name); ->assertSee($backup->database->name);
} }

View File

@ -66,6 +66,7 @@ public function test_see_databases_list(): void
]); ]);
$this->get(route('servers.databases', $this->server)) $this->get(route('servers.databases', $this->server))
->assertSuccessful()
->assertSee($database->name); ->assertSee($database->name);
} }

View File

@ -58,6 +58,7 @@ public function test_see_database_users_list(): void
]); ]);
$this->get(route('servers.databases', $this->server)) $this->get(route('servers.databases', $this->server))
->assertSuccessful()
->assertSee($databaseUser->username); ->assertSee($databaseUser->username);
} }

View File

@ -41,6 +41,7 @@ public function test_see_firewall_rules(): void
]); ]);
$this->get(route('servers.firewall', $this->server)) $this->get(route('servers.firewall', $this->server))
->assertSuccessful()
->assertSee($rule->source) ->assertSee($rule->source)
->assertSee($rule->port); ->assertSee($rule->port);
} }

View File

@ -20,6 +20,7 @@ public function test_see_logs()
]); ]);
$this->get(route('servers.logs', $this->server)) $this->get(route('servers.logs', $this->server))
->assertSuccessful()
->assertSeeText($log->type); ->assertSeeText($log->type);
} }
} }

View File

@ -194,6 +194,7 @@ public function test_see_channels_list(): void
$channel = \App\Models\NotificationChannel::factory()->create(); $channel = \App\Models\NotificationChannel::factory()->create();
$this->get(route('notification-channels')) $this->get(route('notification-channels'))
->assertSuccessful()
->assertSee($channel->provider); ->assertSee($channel->provider);
} }

View File

@ -16,6 +16,7 @@ public function test_profile_page_is_displayed(): void
$this $this
->get(route('profile')) ->get(route('profile'))
->assertSuccessful()
->assertSee('Profile Information') ->assertSee('Profile Information')
->assertSee('Update Password') ->assertSee('Update Password')
->assertSee('Two Factor Authentication'); ->assertSee('Two Factor Authentication');

View File

@ -32,6 +32,7 @@ public function test_see_projects_list(): void
]); ]);
$this->get(route('projects')) $this->get(route('projects'))
->assertSuccessful()
->assertSee($project->name); ->assertSee($project->name);
} }

View File

@ -27,7 +27,7 @@ public function test_see_queues()
'site' => $this->site, 'site' => $this->site,
]) ])
) )
->assertOk() ->assertSuccessful()
->assertSee($queue->command); ->assertSee($queue->command);
} }

View File

@ -27,6 +27,7 @@ public function test_see_server_keys()
]); ]);
$this->get(route('servers.ssh-keys', $this->server)) $this->get(route('servers.ssh-keys', $this->server))
->assertSuccessful()
->assertSeeText('My first key'); ->assertSeeText('My first key');
} }

View File

@ -70,6 +70,7 @@ public function test_see_providers_list(): void
]); ]);
$this->get(route('server-providers')) $this->get(route('server-providers'))
->assertSuccessful()
->assertSee($provider->profile); ->assertSee($provider->profile);
} }

View File

@ -16,6 +16,8 @@ public function test_see_services_list(): void
$this->actingAs($this->user); $this->actingAs($this->user);
$this->get(route('servers.services', $this->server)) $this->get(route('servers.services', $this->server))
->assertSuccessful()
->assertSee('mysql')
->assertSee('nginx') ->assertSee('nginx')
->assertSee('php') ->assertSee('php')
->assertSee('supervisor') ->assertSee('supervisor')
@ -242,6 +244,7 @@ public static function data(): array
['redis'], ['redis'],
['ufw'], ['ufw'],
['php'], ['php'],
['mysql'],
]; ];
} }
} }

View File

@ -57,7 +57,7 @@ public function test_see_sites_list(): void
$this->get(route('servers.sites', [ $this->get(route('servers.sites', [
'server' => $this->server, 'server' => $this->server,
])) ]))
->assertOk() ->assertSuccessful()
->assertSee($site->domain); ->assertSee($site->domain);
} }
@ -158,6 +158,15 @@ public static function create_data(): array
'web_directory' => 'public', 'web_directory' => 'public',
], ],
], ],
[
[
'type' => SiteType::PHPMYADMIN,
'domain' => 'example.com',
'alias' => 'www.example.com',
'php_version' => '8.2',
'version' => '5.1.2',
],
],
]; ];
} }
@ -169,7 +178,7 @@ public function test_see_logs(): void
'server' => $this->server, 'server' => $this->server,
'site' => $this->site, 'site' => $this->site,
])) ]))
->assertOk() ->assertSuccessful()
->assertSee('Logs'); ->assertSee('Logs');
} }
} }

View File

@ -29,6 +29,7 @@ public function test_get_public_keys_list(): void
]); ]);
$this->get(route('ssh-keys')) $this->get(route('ssh-keys'))
->assertSuccessful()
->assertSee($key->name); ->assertSee($key->name);
} }

View File

@ -25,7 +25,7 @@ public function test_see_ssls_list()
'server' => $this->server, 'server' => $this->server,
'site' => $this->site, 'site' => $this->site,
])) ]))
->assertOk() ->assertSuccessful()
->assertSee($ssl->type); ->assertSee($ssl->type);
} }
@ -37,7 +37,7 @@ public function test_see_ssls_list_with_no_ssls()
'server' => $this->server, 'server' => $this->server,
'site' => $this->site, 'site' => $this->site,
])) ]))
->assertOk() ->assertSuccessful()
->assertSeeText(__("You don't have any SSL certificates yet!")); ->assertSeeText(__("You don't have any SSL certificates yet!"));
} }

View File

@ -41,6 +41,7 @@ public function test_see_providers_list(): void
]); ]);
$this->get(route('storage-providers')) $this->get(route('storage-providers'))
->assertSuccessful()
->assertSee($provider->profile); ->assertSee($provider->profile);
} }