mirror of
https://github.com/vitodeploy/vito.git
synced 2025-07-01 14:06:15 +00:00
Compare commits
32 Commits
Author | SHA1 | Date | |
---|---|---|---|
bce05d3171 | |||
929dd1dbaa | |||
2bcd145bea | |||
c0f903d4ca | |||
cca4ab7ae3 | |||
51e7325d3d | |||
ce085879c1 | |||
8a49003e9e | |||
dcc4276f09 | |||
f089779045 | |||
f1efb9a6c8 | |||
a7d472fb45 | |||
d01d406d3d | |||
c66c50835a | |||
b6179d6693 | |||
9244e69fd8 | |||
a7ba095919 | |||
807ae01646 | |||
cc896d82e9 | |||
d16d3c1385 | |||
3946cf6b34 | |||
165212fed2 | |||
f6b36dfefc | |||
33594f2dba | |||
f68d6c7ca2 | |||
d504588f95 | |||
ca0e33be2f | |||
4d051330d6 | |||
ab2d6f64f3 | |||
d9a56f95dd | |||
884f18db63 | |||
536df65fc6 |
@ -12,5 +12,5 @@ MAIL_PORT=
|
|||||||
MAIL_USERNAME=null
|
MAIL_USERNAME=null
|
||||||
MAIL_PASSWORD=null
|
MAIL_PASSWORD=null
|
||||||
MAIL_ENCRYPTION=null
|
MAIL_ENCRYPTION=null
|
||||||
MAIL_FROM_ADDRESS=null
|
MAIL_FROM_ADDRESS="noreply@${APP_NAME}"
|
||||||
MAIL_FROM_NAME="${APP_NAME}"
|
MAIL_FROM_NAME="${APP_NAME}"
|
||||||
|
@ -12,5 +12,5 @@ MAIL_PORT=
|
|||||||
MAIL_USERNAME=null
|
MAIL_USERNAME=null
|
||||||
MAIL_PASSWORD=null
|
MAIL_PASSWORD=null
|
||||||
MAIL_ENCRYPTION=null
|
MAIL_ENCRYPTION=null
|
||||||
MAIL_FROM_ADDRESS=null
|
MAIL_FROM_ADDRESS="noreply@${APP_NAME}"
|
||||||
MAIL_FROM_NAME="${APP_NAME}"
|
MAIL_FROM_NAME="${APP_NAME}"
|
||||||
|
@ -13,5 +13,5 @@ MAIL_PORT=
|
|||||||
MAIL_USERNAME=null
|
MAIL_USERNAME=null
|
||||||
MAIL_PASSWORD=null
|
MAIL_PASSWORD=null
|
||||||
MAIL_ENCRYPTION=null
|
MAIL_ENCRYPTION=null
|
||||||
MAIL_FROM_ADDRESS=null
|
MAIL_FROM_ADDRESS="noreply@${APP_NAME}"
|
||||||
MAIL_FROM_NAME="${APP_NAME}"
|
MAIL_FROM_NAME="${APP_NAME}"
|
||||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -7,6 +7,8 @@
|
|||||||
/storage/test-key
|
/storage/test-key
|
||||||
/storage/test-key.pub
|
/storage/test-key.pub
|
||||||
/vendor
|
/vendor
|
||||||
|
/storage/database.sqlite
|
||||||
|
/storage/database-test.sqlite
|
||||||
.env
|
.env
|
||||||
.env.backup
|
.env.backup
|
||||||
.env.production
|
.env.production
|
||||||
|
@ -1,23 +1,5 @@
|
|||||||
# Contributing
|
# Contributing
|
||||||
Thank you for your interest in contributing! There are a couple of contribution guidelines that make it easier to apply the incoming suggestions.
|
|
||||||
|
|
||||||
If you want to contribute please start with the issues. Issues labeled with "Bug" are the higher priorities.
|
Please read the contribution guide on the website
|
||||||
|
|
||||||
## Issues
|
https://vitodeploy.com/introduction/contribution-guide.html
|
||||||
1. Issues are the best place to propose a new feature.
|
|
||||||
2. If you are adding a feature that there is no issue for yet, please first open an issue and label it as "feature" and lets discuss it before you implement it.
|
|
||||||
3. Search the issues before proposing a feature to see if it is already under discussion. Referencing existing issues is a good way to increase the priority of your own.
|
|
||||||
4. We don't have an issue template yet, but the more detailed your explanation, the more quickly we'll be able to evaluate it.
|
|
||||||
5. Search for the issue that you also have. Give it a reaction (and comment, if you have something to add). We note that!
|
|
||||||
|
|
||||||
## Pull Requests
|
|
||||||
1. Open PRs represent issues that we're actively thinking about merging (at a pace we can manage). If we think a proposal needs more discussion, or that the existing code would require a lot of back-and-forth to merge, we might close it and suggest you make an issue.
|
|
||||||
2. All PRs should be made against the `main` branch. This can be changed in the future.
|
|
||||||
3. If you are making changes to the front-end layer, Please build the assets via `npm run build` and push it with the other changes.
|
|
||||||
4. Write tests for your code. Tests can be Unit or Feature.
|
|
||||||
5. Code refactors will be closed. For the architectural refactors open an issue first.
|
|
||||||
6. Use `./vendor/bin/pint` to style your code before opening a PR otherwise the actions will fail.
|
|
||||||
7. Typo fixes in documentation are welcome, but if it's at all debatable we might just close it.
|
|
||||||
|
|
||||||
## Misc
|
|
||||||
1. If you think we closed something incorrectly, feel free to (politely) tell us why! We're human and make mistakes.
|
|
||||||
|
@ -37,7 +37,7 @@ ## Useful Links
|
|||||||
- [Feedbacks](https://vitodeploy.featurebase.app)
|
- [Feedbacks](https://vitodeploy.featurebase.app)
|
||||||
- [Roadmap](https://vitodeploy.featurebase.app/roadmap)
|
- [Roadmap](https://vitodeploy.featurebase.app/roadmap)
|
||||||
- [Video Demo](https://youtu.be/rLRHIyEfON8)
|
- [Video Demo](https://youtu.be/rLRHIyEfON8)
|
||||||
- [Discord](https://discord.gg/dcUWA5DV)
|
- [Discord](https://discord.gg/uZeeHZZnm5)
|
||||||
- [Contribution](/CONTRIBUTING.md)
|
- [Contribution](/CONTRIBUTING.md)
|
||||||
- [Security](/SECURITY.md)
|
- [Security](/SECURITY.md)
|
||||||
|
|
||||||
@ -54,3 +54,4 @@ ## Credits
|
|||||||
- Prettier
|
- Prettier
|
||||||
- Postcss
|
- Postcss
|
||||||
- Flowbite
|
- Flowbite
|
||||||
|
- svgrepo.com
|
||||||
|
55
app/Actions/Service/Create.php
Normal file
55
app/Actions/Service/Create.php
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
@ -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';
|
||||||
|
|
||||||
|
@ -11,4 +11,6 @@ final class SiteType
|
|||||||
const LARAVEL = 'laravel';
|
const LARAVEL = 'laravel';
|
||||||
|
|
||||||
const WORDPRESS = 'wordpress';
|
const WORDPRESS = 'wordpress';
|
||||||
|
|
||||||
|
const PHPMYADMIN = 'phpmyadmin';
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
* @method static init(Server $server, string $asUser = null)
|
* @method static init(Server $server, string $asUser = null)
|
||||||
* @method static setLog(string $logType, int $siteId = null)
|
* @method static setLog(string $logType, int $siteId = null)
|
||||||
* @method static connect()
|
* @method static connect()
|
||||||
* @method static string exec(string $command, string $log = '', int $siteId = null)
|
* @method static string exec(string $command, string $log = '', int $siteId = null, ?bool $stream = false)
|
||||||
* @method static string assertExecuted(array|string $commands)
|
* @method static string assertExecuted(array|string $commands)
|
||||||
* @method static string assertExecutedContains(string $command)
|
* @method static string assertExecutedContains(string $command)
|
||||||
* @method static disconnect()
|
* @method static disconnect()
|
||||||
|
@ -96,7 +96,7 @@ public function connect(bool $sftp = false): void
|
|||||||
* @throws SSHCommandError
|
* @throws SSHCommandError
|
||||||
* @throws SSHConnectionError
|
* @throws SSHConnectionError
|
||||||
*/
|
*/
|
||||||
public function exec(string|array $commands, string $log = '', ?int $siteId = null): string
|
public function exec(string $command, string $log = '', ?int $siteId = null, ?bool $stream = false): string
|
||||||
{
|
{
|
||||||
if ($log) {
|
if ($log) {
|
||||||
$this->setLog($log, $siteId);
|
$this->setLog($log, $siteId);
|
||||||
@ -112,18 +112,34 @@ public function exec(string|array $commands, string $log = '', ?int $siteId = nu
|
|||||||
throw new SSHConnectionError($e->getMessage());
|
throw new SSHConnectionError($e->getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! is_array($commands)) {
|
|
||||||
$commands = [$commands];
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$result = '';
|
if ($this->asUser) {
|
||||||
foreach ($commands as $command) {
|
$command = 'sudo su - '.$this->asUser.' -c '.'"'.addslashes($command).'"';
|
||||||
$result .= $this->executeCommand($command);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $result;
|
$this->connection->setTimeout(0);
|
||||||
|
if ($stream) {
|
||||||
|
$this->connection->exec($command, function ($output) {
|
||||||
|
$this->log?->write($output);
|
||||||
|
echo $output;
|
||||||
|
ob_flush();
|
||||||
|
flush();
|
||||||
|
});
|
||||||
|
|
||||||
|
return '';
|
||||||
|
} else {
|
||||||
|
$output = $this->connection->exec($command);
|
||||||
|
|
||||||
|
$this->log?->write($output);
|
||||||
|
|
||||||
|
if (Str::contains($output, 'VITO_SSH_ERROR')) {
|
||||||
|
throw new Exception('SSH command failed with an error');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $output;
|
||||||
|
}
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
|
throw $e;
|
||||||
throw new SSHCommandError($e->getMessage());
|
throw new SSHCommandError($e->getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -141,28 +157,6 @@ public function upload(string $local, string $remote): void
|
|||||||
$this->connection->put($remote, $local, SFTP::SOURCE_LOCAL_FILE);
|
$this->connection->put($remote, $local, SFTP::SOURCE_LOCAL_FILE);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @throws Exception
|
|
||||||
*/
|
|
||||||
protected function executeCommand(string $command): string
|
|
||||||
{
|
|
||||||
if ($this->asUser) {
|
|
||||||
$command = 'sudo su - '.$this->asUser.' -c '.'"'.addslashes($command).'"';
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->connection->setTimeout(0);
|
|
||||||
|
|
||||||
$output = $this->connection->exec($command);
|
|
||||||
|
|
||||||
$this->log?->write($output);
|
|
||||||
|
|
||||||
if (Str::contains($output, 'VITO_SSH_ERROR')) {
|
|
||||||
throw new Exception('SSH command failed with an error');
|
|
||||||
}
|
|
||||||
|
|
||||||
return $output;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @throws Exception
|
* @throws Exception
|
||||||
*/
|
*/
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers\API;
|
||||||
|
|
||||||
use App\Actions\Site\Deploy;
|
use App\Actions\Site\Deploy;
|
||||||
use App\Exceptions\SourceControlIsNotConnected;
|
use App\Exceptions\SourceControlIsNotConnected;
|
||||||
use App\Facades\Notifier;
|
use App\Facades\Notifier;
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
use App\Models\GitHook;
|
use App\Models\GitHook;
|
||||||
use App\Models\ServerLog;
|
use App\Models\ServerLog;
|
||||||
use App\Notifications\SourceControlDisconnected;
|
use App\Notifications\SourceControlDisconnected;
|
16
app/Http/Controllers/API/HealthController.php
Normal file
16
app/Http/Controllers/API/HealthController.php
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\API;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
|
||||||
|
class HealthController extends Controller
|
||||||
|
{
|
||||||
|
public function __invoke()
|
||||||
|
{
|
||||||
|
return response()->json([
|
||||||
|
'success' => true,
|
||||||
|
'version' => vito_version(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
43
app/Http/Controllers/ConsoleController.php
Normal file
43
app/Http/Controllers/ConsoleController.php
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Models\Server;
|
||||||
|
use Illuminate\Contracts\View\View;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Validation\Rule;
|
||||||
|
|
||||||
|
class ConsoleController extends Controller
|
||||||
|
{
|
||||||
|
public function index(Server $server): View
|
||||||
|
{
|
||||||
|
return view('console.index', [
|
||||||
|
'server' => $server,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function run(Server $server, Request $request)
|
||||||
|
{
|
||||||
|
$this->validate($request, [
|
||||||
|
'user' => [
|
||||||
|
'required',
|
||||||
|
Rule::in(['root', $server->ssh_user]),
|
||||||
|
],
|
||||||
|
'command' => 'required|string',
|
||||||
|
]);
|
||||||
|
|
||||||
|
return response()->stream(
|
||||||
|
function () use ($server, $request) {
|
||||||
|
$ssh = $server->ssh($request->user);
|
||||||
|
$log = 'console-'.time();
|
||||||
|
$ssh->exec(command: $request->command, log: $log, stream: true);
|
||||||
|
},
|
||||||
|
200,
|
||||||
|
[
|
||||||
|
'Cache-Control' => 'no-cache',
|
||||||
|
'X-Accel-Buffering' => 'no',
|
||||||
|
'Content-Type' => 'text/event-stream',
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -71,7 +71,9 @@ public function updateIni(Server $server, Request $request): RedirectResponse
|
|||||||
|
|
||||||
Toast::success('PHP ini updated!');
|
Toast::success('PHP ini updated!');
|
||||||
|
|
||||||
return back();
|
return back()->with([
|
||||||
|
'ini' => $request->input('ini'),
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function uninstall(Server $server, Request $request): RedirectResponse
|
public function uninstall(Server $server, Request $request): RedirectResponse
|
||||||
|
@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,13 +7,14 @@
|
|||||||
use App\Facades\Toast;
|
use App\Facades\Toast;
|
||||||
use Closure;
|
use Closure;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Http\Response;
|
||||||
|
|
||||||
class HandleSSHErrors
|
class HandleSSHErrors
|
||||||
{
|
{
|
||||||
public function handle(Request $request, Closure $next)
|
public function handle(Request $request, Closure $next)
|
||||||
{
|
{
|
||||||
$res = $next($request);
|
$res = $next($request);
|
||||||
if ($res->exception) {
|
if ($res instanceof Response && $res->exception) {
|
||||||
if ($res->exception instanceof SSHConnectionError || $res->exception instanceof SSHCommandError) {
|
if ($res->exception instanceof SSHConnectionError || $res->exception instanceof SSHCommandError) {
|
||||||
Toast::error($res->exception->getMessage());
|
Toast::error($res->exception->getMessage());
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ class TrustProxies extends Middleware
|
|||||||
*
|
*
|
||||||
* @var array<int, string>|string|null
|
* @var array<int, string>|string|null
|
||||||
*/
|
*/
|
||||||
protected $proxies;
|
protected $proxies = '*';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The headers that should be used to detect proxies.
|
* The headers that should be used to detect proxies.
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,6 @@
|
|||||||
use App\Helpers\Toast;
|
use App\Helpers\Toast;
|
||||||
use Illuminate\Contracts\Container\BindingResolutionException;
|
use Illuminate\Contracts\Container\BindingResolutionException;
|
||||||
use Illuminate\Http\Resources\Json\ResourceCollection;
|
use Illuminate\Http\Resources\Json\ResourceCollection;
|
||||||
use Illuminate\Support\Facades\URL;
|
|
||||||
use Illuminate\Support\ServiceProvider;
|
use Illuminate\Support\ServiceProvider;
|
||||||
|
|
||||||
class AppServiceProvider extends ServiceProvider
|
class AppServiceProvider extends ServiceProvider
|
||||||
@ -37,9 +36,5 @@ public function boot(): void
|
|||||||
$this->app->bind('toast', function () {
|
$this->app->bind('toast', function () {
|
||||||
return new Toast;
|
return new Toast;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (str(config('app.url'))->startsWith('https://')) {
|
|
||||||
URL::forceScheme('https');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -98,12 +98,12 @@ public function reboot(): void
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function editFile(string $path, string $content): void
|
public function editFile(string $path, ?string $content = null): void
|
||||||
{
|
{
|
||||||
$this->server->ssh()->exec(
|
$this->server->ssh()->exec(
|
||||||
$this->getScript('edit-file.sh', [
|
$this->getScript('edit-file.sh', [
|
||||||
'path' => $path,
|
'path' => $path,
|
||||||
'content' => $content,
|
'content' => $content ?? '',
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -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
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
3
app/SSH/OS/scripts/download.sh
Normal file
3
app/SSH/OS/scripts/download.sh
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
if ! wget __url__ -O __path__; then
|
||||||
|
echo 'VITO_SSH_ERROR' && exit 1
|
||||||
|
fi
|
23
app/SSH/PHPMyAdmin/PHPMyAdmin.php
Normal file
23
app/SSH/PHPMyAdmin/PHPMyAdmin.php
Normal 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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
25
app/SSH/PHPMyAdmin/scripts/install.sh
Executable file
25
app/SSH/PHPMyAdmin/scripts/install.sh
Executable 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!"
|
18
app/SSH/Services/AddOnServices/AbstractAddOnService.php
Normal file
18
app/SSH/Services/AddOnServices/AbstractAddOnService.php
Normal 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
49
app/SiteTypes/PHPMyAdmin.php
Executable 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);
|
||||||
|
}
|
||||||
|
}
|
@ -34,7 +34,7 @@ public function connect(bool $sftp = false): void
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function exec(string|array $commands, string $log = '', ?int $siteId = null): string
|
public function exec(string $command, string $log = '', ?int $siteId = null, ?bool $stream = false): string
|
||||||
{
|
{
|
||||||
if ($log) {
|
if ($log) {
|
||||||
$this->setLog($log, $siteId);
|
$this->setLog($log, $siteId);
|
||||||
@ -42,21 +42,19 @@ public function exec(string|array $commands, string $log = '', ?int $siteId = nu
|
|||||||
$this->log = null;
|
$this->log = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! is_array($commands)) {
|
|
||||||
$commands = [$commands];
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($commands as $command) {
|
|
||||||
if (is_string($command)) {
|
|
||||||
$this->commands[] = $command;
|
$this->commands[] = $command;
|
||||||
} else {
|
|
||||||
$this->commands[] = get_class($command);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$output = $this->output ?? 'fake output';
|
$output = $this->output ?? 'fake output';
|
||||||
$this->log?->write($output);
|
$this->log?->write($output);
|
||||||
|
|
||||||
|
if ($stream) {
|
||||||
|
echo $output;
|
||||||
|
ob_flush();
|
||||||
|
flush();
|
||||||
|
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
return $output;
|
return $output;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,3 +29,8 @@ function htmx(): HtmxResponse
|
|||||||
{
|
{
|
||||||
return new HtmxResponse();
|
return new HtmxResponse();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function vito_version(): string
|
||||||
|
{
|
||||||
|
return exec('git describe --tags');
|
||||||
|
}
|
||||||
|
@ -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,
|
||||||
],
|
],
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -27,9 +27,9 @@ COPY docker/php.ini /etc/php/8.2/cli/conf.d/99-vito.ini
|
|||||||
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
|
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
|
||||||
|
|
||||||
# app
|
# app
|
||||||
COPY . /var/www/html
|
RUN rm -rf /var/www/html
|
||||||
RUN rm -rf /var/www/html/vendor
|
RUN git clone -b 1.x https://github.com/vitodeploy/vito.git /var/www/html
|
||||||
RUN rm -rf /var/www/html/.env
|
RUN git checkout $(git tag -l --merged 1.x --sort=-v:refname | head -n 1)
|
||||||
RUN composer install --no-dev --prefer-dist
|
RUN composer install --no-dev --prefer-dist
|
||||||
RUN chown -R www-data:www-data /var/www/html \
|
RUN chown -R www-data:www-data /var/www/html \
|
||||||
&& chmod -R 755 /var/www/html/storage /var/www/html/bootstrap/cache
|
&& chmod -R 755 /var/www/html/storage /var/www/html/bootstrap/cache
|
||||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
public/build/assets/app-e3775b0a.css
Normal file
1
public/build/assets/app-e3775b0a.css
Normal file
File diff suppressed because one or more lines are too long
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"resources/css/app.css": {
|
"resources/css/app.css": {
|
||||||
"file": "assets/app-986a0fb7.css",
|
"file": "assets/app-e3775b0a.css",
|
||||||
"isEntry": true,
|
"isEntry": true,
|
||||||
"src": "resources/css/app.css"
|
"src": "resources/css/app.css"
|
||||||
},
|
},
|
||||||
@ -12,7 +12,7 @@
|
|||||||
"css": [
|
"css": [
|
||||||
"assets/app-a1ae07b3.css"
|
"assets/app-a1ae07b3.css"
|
||||||
],
|
],
|
||||||
"file": "assets/app-e823d2ab.js",
|
"file": "assets/app-5f99a92f.js",
|
||||||
"isEntry": true,
|
"isEntry": true,
|
||||||
"src": "resources/js/app.js"
|
"src": "resources/js/app.js"
|
||||||
}
|
}
|
||||||
|
@ -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 |
@ -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 |
File diff suppressed because one or more lines are too long
@ -26,7 +26,9 @@ document.body.addEventListener('htmx:configRequest', (event) => {
|
|||||||
if (window.getSelection) { window.getSelection().removeAllRanges(); }
|
if (window.getSelection) { window.getSelection().removeAllRanges(); }
|
||||||
else if (document.selection) { document.selection.empty(); }
|
else if (document.selection) { document.selection.empty(); }
|
||||||
});
|
});
|
||||||
|
let activeElement = null;
|
||||||
document.body.addEventListener('htmx:beforeRequest', (event) => {
|
document.body.addEventListener('htmx:beforeRequest', (event) => {
|
||||||
|
activeElement = document.activeElement;
|
||||||
let targetElements = event.target.querySelectorAll('[hx-disable]');
|
let targetElements = event.target.querySelectorAll('[hx-disable]');
|
||||||
for (let i = 0; i < targetElements.length; i++) {
|
for (let i = 0; i < targetElements.length; i++) {
|
||||||
targetElements[i].disabled = true;
|
targetElements[i].disabled = true;
|
||||||
@ -38,6 +40,18 @@ document.body.addEventListener('htmx:afterRequest', (event) => {
|
|||||||
targetElements[i].disabled = false;
|
targetElements[i].disabled = false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
document.body.addEventListener('htmx:afterSwap', (event) => {
|
||||||
|
tippy('[data-tooltip]', {
|
||||||
|
content(reference) {
|
||||||
|
return reference.getAttribute('data-tooltip');
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (activeElement) {
|
||||||
|
activeElement.blur();
|
||||||
|
activeElement.focus();
|
||||||
|
activeElement = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
import toastr from 'toastr';
|
import toastr from 'toastr';
|
||||||
window.toastr = toastr;
|
window.toastr = toastr;
|
||||||
@ -49,13 +63,6 @@ window.toastr.options = {
|
|||||||
|
|
||||||
import tippy from 'tippy.js';
|
import tippy from 'tippy.js';
|
||||||
import 'tippy.js/dist/tippy.css';
|
import 'tippy.js/dist/tippy.css';
|
||||||
document.body.addEventListener('htmx:afterSettle', (event) => {
|
|
||||||
tippy('[data-tooltip]', {
|
|
||||||
content(reference) {
|
|
||||||
return reference.getAttribute('data-tooltip');
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
tippy('[data-tooltip]', {
|
tippy('[data-tooltip]', {
|
||||||
content(reference) {
|
content(reference) {
|
||||||
return reference.getAttribute('data-tooltip');
|
return reference.getAttribute('data-tooltip');
|
||||||
|
6
resources/views/application/phpmyadmin-app.blade.php
Normal file
6
resources/views/application/phpmyadmin-app.blade.php
Normal 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>
|
@ -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>
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
document.documentElement.className === 'dark'
|
document.documentElement.className === 'dark'
|
||||||
? 'one-dark'
|
? 'one-dark'
|
||||||
: 'github'
|
: 'github'
|
||||||
editor = window.ace.edit(this.editorId)
|
editor = window.ace.edit(this.editorId, {})
|
||||||
let contentElement = document.getElementById(
|
let contentElement = document.getElementById(
|
||||||
`text-${this.editorId}`,
|
`text-${this.editorId}`,
|
||||||
)
|
)
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<div
|
<div
|
||||||
class="h-[500px] w-full overflow-auto whitespace-pre-line rounded-md border border-gray-200 bg-gray-900 p-5 text-gray-50 dark:border-gray-700"
|
{{ $attributes->merge(["class" => "relative h-[500px] w-full overflow-auto whitespace-pre-line rounded-md border border-gray-200 bg-black p-5 text-gray-50 dark:border-gray-700"]) }}
|
||||||
>
|
>
|
||||||
{{ $slot }}
|
{{ $slot }}
|
||||||
</div>
|
</div>
|
||||||
|
@ -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="m6.75 7.5 3 2.25-3 2.25m4.5 0h3m-9 8.25h13.5A2.25 2.25 0 0 0 21 18V6a2.25 2.25 0 0 0-2.25-2.25H5.25A2.25 2.25 0 0 0 3 6v12a2.25 2.25 0 0 0 2.25 2.25Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 406 B |
14
resources/views/components/heroicons/o-no-symbol.blade.php
Normal file
14
resources/views/components/heroicons/o-no-symbol.blade.php
Normal 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 |
@ -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
|
||||||
|
@ -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>
|
||||||
|
@ -1,20 +1,17 @@
|
|||||||
@props([
|
@props([
|
||||||
"interval" => "30s",
|
"interval" => "7s",
|
||||||
"id",
|
"id",
|
||||||
"target" => null,
|
"target" => null,
|
||||||
])
|
])
|
||||||
|
|
||||||
<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>
|
||||||
|
@ -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))
|
||||||
|
104
resources/views/console/index.blade.php
Normal file
104
resources/views/console/index.blade.php
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
<x-server-layout :server="$server">
|
||||||
|
<x-slot name="pageTitle">{{ $server->name }} - Console</x-slot>
|
||||||
|
|
||||||
|
<div
|
||||||
|
x-data="{
|
||||||
|
user: '{{ $server->ssh_user }}',
|
||||||
|
running: false,
|
||||||
|
command: '',
|
||||||
|
output: '',
|
||||||
|
runUrl: '{{ route("servers.console.run", ["server" => $server]) }}',
|
||||||
|
async run() {
|
||||||
|
this.running = true
|
||||||
|
this.output = 'Running...\n'
|
||||||
|
const fetchOptions = {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-CSRF-TOKEN': '{{ csrf_token() }}',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
user: this.user,
|
||||||
|
command: this.command,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch(this.runUrl, fetchOptions)
|
||||||
|
const reader = response.body.getReader()
|
||||||
|
const decoder = new TextDecoder('utf-8')
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
if (! this.running) {
|
||||||
|
reader.cancel()
|
||||||
|
this.output += '\nStopped!'
|
||||||
|
break
|
||||||
|
}
|
||||||
|
const { value, done } = await reader.read()
|
||||||
|
if (done) break
|
||||||
|
|
||||||
|
const textChunk = decoder.decode(value, { stream: true })
|
||||||
|
|
||||||
|
this.output += textChunk
|
||||||
|
|
||||||
|
document.getElementById('console-output').scrollTop =
|
||||||
|
document.getElementById('console-output').scrollHeight
|
||||||
|
}
|
||||||
|
this.output += '\nDone!'
|
||||||
|
this.running = false
|
||||||
|
},
|
||||||
|
stop() {
|
||||||
|
this.running = false
|
||||||
|
},
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<x-card-header>
|
||||||
|
<x-slot name="title">Headless Console</x-slot>
|
||||||
|
<x-slot name="description">
|
||||||
|
Here you can run ssh commands on your server and see the result right away.
|
||||||
|
<br />
|
||||||
|
<b>Note that</b>
|
||||||
|
this is a headless console, it doesn't keep the current path. it will always run from the home path of
|
||||||
|
the selected user.
|
||||||
|
</x-slot>
|
||||||
|
</x-card-header>
|
||||||
|
|
||||||
|
<div class="space-y-3">
|
||||||
|
<x-console-view id="console-output">
|
||||||
|
<div class="w-full" x-text="output"></div>
|
||||||
|
</x-console-view>
|
||||||
|
<form onsubmit="return false" id="console-form" class="flex items-center justify-between">
|
||||||
|
<x-select-input x-model="user" id="user" name="user" class="flex-none" data-tooltip="User">
|
||||||
|
<option value="{{ $server->ssh_user }}">{{ $server->ssh_user }}</option>
|
||||||
|
<option value="root">root</option>
|
||||||
|
</x-select-input>
|
||||||
|
<x-text-input
|
||||||
|
id="command"
|
||||||
|
name="command"
|
||||||
|
x-model="command"
|
||||||
|
type="text"
|
||||||
|
placeholder="Type your command here..."
|
||||||
|
class="mx-1 flex-grow"
|
||||||
|
autocomplete="off"
|
||||||
|
/>
|
||||||
|
<x-secondary-button
|
||||||
|
type="button"
|
||||||
|
id="btn-stop"
|
||||||
|
x-on:click="stop"
|
||||||
|
class="mr-1 h-[40px]"
|
||||||
|
x-bind:disabled="!running"
|
||||||
|
>
|
||||||
|
Stop
|
||||||
|
</x-secondary-button>
|
||||||
|
<x-primary-button
|
||||||
|
type="submit"
|
||||||
|
id="btn-run"
|
||||||
|
x-on:click="run"
|
||||||
|
class="h-[40px]"
|
||||||
|
x-bind:disabled="running || command === ''"
|
||||||
|
>
|
||||||
|
Run
|
||||||
|
</x-primary-button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</x-server-layout>
|
@ -1,9 +1,12 @@
|
|||||||
<nav
|
<nav
|
||||||
class="fixed top-0 z-50 flex h-[64px] w-full items-center border-b border-gray-200 bg-white dark:border-gray-700 dark:bg-gray-800"
|
class="fixed top-0 z-50 flex h-[64px] w-full items-center border-b border-gray-200 bg-white dark:border-gray-700 dark:bg-gray-800"
|
||||||
>
|
>
|
||||||
<div class="w-full px-3 py-3 lg:px-5 lg:pl-3">
|
<div class="w-full">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<div class="flex items-center justify-start">
|
<div class="flex items-center justify-start">
|
||||||
|
<div
|
||||||
|
class="flex items-center justify-start border-r border-gray-200 px-3 py-3 dark:border-gray-700 md:w-64"
|
||||||
|
>
|
||||||
<button
|
<button
|
||||||
data-drawer-target="logo-sidebar"
|
data-drawer-target="logo-sidebar"
|
||||||
data-drawer-toggle="logo-sidebar"
|
data-drawer-toggle="logo-sidebar"
|
||||||
@ -15,12 +18,17 @@ class="inline-flex items-center rounded-md p-2 text-sm text-gray-500 hover:bg-gr
|
|||||||
<x-heroicon name="o-bars-3-center-left" class="h-6 w-6" />
|
<x-heroicon name="o-bars-3-center-left" class="h-6 w-6" />
|
||||||
</button>
|
</button>
|
||||||
<a href="/" class="ms-2 flex md:me-24">
|
<a href="/" class="ms-2 flex md:me-24">
|
||||||
<div class="flex items-center justify-start text-3xl font-extrabold">
|
<div class="relative flex items-center justify-start text-3xl font-extrabold">
|
||||||
<x-application-logo class="h-9 w-9 rounded-md" />
|
<x-application-logo class="h-9 w-9 rounded-md" />
|
||||||
<span class="ml-1 hidden sm:block">Deploy</span>
|
<span class="ml-1 hidden md:block">Deploy</span>
|
||||||
|
<span
|
||||||
|
class="absolute bottom-0 left-0 right-0 rounded-b-md bg-gray-700/60 text-center text-xs text-white md:relative md:ml-1 md:block md:bg-inherit md:text-inherit"
|
||||||
|
>
|
||||||
|
{{ vito_version() }}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
<div class="h-[64px] w-1 border-r border-gray-200 px-3 dark:border-gray-700 md:px-0"></div>
|
</div>
|
||||||
<div class="ml-5 cursor-pointer" x-data="">
|
<div class="ml-5 cursor-pointer" x-data="">
|
||||||
<div
|
<div
|
||||||
class="flex w-full items-center rounded-md border border-gray-200 bg-gray-100 px-4 py-2 text-sm text-gray-900 focus:ring-4 focus:ring-gray-300 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-300 dark:focus:ring-gray-600"
|
class="flex w-full items-center rounded-md border border-gray-200 bg-gray-100 px-4 py-2 text-sm text-gray-900 focus:ring-4 focus:ring-gray-300 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-300 dark:focus:ring-gray-600"
|
||||||
@ -31,7 +39,7 @@ class="flex w-full items-center rounded-md border border-gray-200 bg-gray-100 px
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center">
|
<div class="flex items-center px-3 py-3">
|
||||||
<div class="mr-3">
|
<div class="mr-3">
|
||||||
@include("layouts.partials.color-scheme")
|
@include("layouts.partials.color-scheme")
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<div data-tooltip="Project" class="cursor-pointer">
|
<div data-tooltip="Project" class="cursor-pointer">
|
||||||
<x-dropdown align="left">
|
<x-dropdown width="full">
|
||||||
<x-slot:trigger>
|
<x-slot:trigger>
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
|
@ -137,8 +137,8 @@
|
|||||||
x-on:click="close"
|
x-on:click="close"
|
||||||
class="fixed inset-0 bottom-0 left-0 right-0 top-0 z-[1000] items-center bg-gray-500 opacity-75 dark:bg-gray-900"
|
class="fixed inset-0 bottom-0 left-0 right-0 top-0 z-[1000] items-center bg-gray-500 opacity-75 dark:bg-gray-900"
|
||||||
></div>
|
></div>
|
||||||
<div class="absolute z-[1000] mt-20 lg:scale-110">
|
<div class="absolute left-1 right-1 z-[1000] mt-20 md:left-auto md:right-auto lg:scale-110">
|
||||||
<div class="w-[500px]">
|
<div class="w-full px-10 md:w-[500px]">
|
||||||
<x-text-input
|
<x-text-input
|
||||||
id="search-input"
|
id="search-input"
|
||||||
x-ref="input"
|
x-ref="input"
|
||||||
|
@ -116,6 +116,18 @@ class="fixed left-0 top-0 z-40 h-screen w-64 -translate-x-full border-r border-g
|
|||||||
</x-sidebar-link>
|
</x-sidebar-link>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
<x-sidebar-link
|
||||||
|
:href="route('servers.console', ['server' => $server])"
|
||||||
|
:active="request()->routeIs('servers.console')"
|
||||||
|
>
|
||||||
|
<x-heroicon name="o-command-line" class="h-6 w-6" />
|
||||||
|
<span class="ml-2">
|
||||||
|
{{ __("Console") }}
|
||||||
|
</span>
|
||||||
|
</x-sidebar-link>
|
||||||
|
</li>
|
||||||
|
|
||||||
<li>
|
<li>
|
||||||
<x-sidebar-link
|
<x-sidebar-link
|
||||||
:href="route('servers.settings', ['server' => $server])"
|
:href="route('servers.settings', ['server' => $server])"
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
</x-slot>
|
</x-slot>
|
||||||
</x-card-header>
|
</x-card-header>
|
||||||
|
|
||||||
|
<x-live id="php-default-cli">
|
||||||
<a class="block">
|
<a class="block">
|
||||||
<x-item-card>
|
<x-item-card>
|
||||||
<div class="flex items-start justify-center">
|
<div class="flex items-start justify-center">
|
||||||
@ -47,4 +48,5 @@ class="cursor-pointer"
|
|||||||
</div>
|
</div>
|
||||||
</x-item-card>
|
</x-item-card>
|
||||||
</a>
|
</a>
|
||||||
|
</x-live>
|
||||||
</div>
|
</div>
|
||||||
|
@ -39,7 +39,7 @@ class="cursor-pointer"
|
|||||||
</x-dropdown-link>
|
</x-dropdown-link>
|
||||||
<x-dropdown-link
|
<x-dropdown-link
|
||||||
class="cursor-pointer"
|
class="cursor-pointer"
|
||||||
x-on:click="$dispatch('open-modal', 'update-php-ini'); document.getElementById('ini').value = 'Loading...';"
|
x-on:click="version = '{{ $php->version }}'; $dispatch('open-modal', 'update-php-ini'); document.getElementById('ini').value = 'Loading...';"
|
||||||
hx-get="{{ route('servers.php.get-ini', ['server' => $server, 'version' => $php->version]) }}"
|
hx-get="{{ route('servers.php.get-ini', ['server' => $server, 'version' => $php->version]) }}"
|
||||||
hx-swap="outerHTML"
|
hx-swap="outerHTML"
|
||||||
hx-target="#update-php-ini-form"
|
hx-target="#update-php-ini-form"
|
||||||
|
@ -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>
|
||||||
|
@ -0,0 +1 @@
|
|||||||
|
@include("services.partials.unit-actions")
|
@ -0,0 +1 @@
|
|||||||
|
@include("services.partials.unit-actions")
|
@ -0,0 +1 @@
|
|||||||
|
@include("services.partials.unit-actions")
|
1
resources/views/services/partials/actions/php.blade.php
Normal file
1
resources/views/services/partials/actions/php.blade.php
Normal file
@ -0,0 +1 @@
|
|||||||
|
@include("services.partials.unit-actions")
|
@ -0,0 +1 @@
|
|||||||
|
@include("services.partials.unit-actions")
|
@ -0,0 +1 @@
|
|||||||
|
@include("services.partials.unit-actions")
|
@ -0,0 +1 @@
|
|||||||
|
@include("services.partials.unit-actions")
|
1
resources/views/services/partials/actions/ufw.blade.php
Normal file
1
resources/views/services/partials/actions/ufw.blade.php
Normal file
@ -0,0 +1 @@
|
|||||||
|
@include("services.partials.unit-actions")
|
33
resources/views/services/partials/add-ons.blade.php
Normal file
33
resources/views/services/partials/add-ons.blade.php
Normal 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>
|
@ -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>
|
||||||
|
42
resources/views/services/partials/unit-actions.blade.php
Normal file
42
resources/views/services/partials/unit-actions.blade.php
Normal 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>
|
13
resources/views/sites/partials/create/phpmyadmin.blade.php
Normal file
13
resources/views/sites/partials/create/phpmyadmin.blade.php
Normal 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>
|
@ -1,7 +1,9 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
// git hook
|
// git hook
|
||||||
use App\Http\Controllers\GitHookController;
|
use App\Http\Controllers\API\GitHookController;
|
||||||
|
use App\Http\Controllers\API\HealthController;
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
|
|
||||||
Route::any('git-hooks', GitHookController::class)->name('git-hooks');
|
Route::get('health', HealthController::class)->name('api.health');
|
||||||
|
Route::any('git-hooks', GitHookController::class)->name('api.git-hooks');
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
use App\Http\Controllers\ApplicationController;
|
use App\Http\Controllers\ApplicationController;
|
||||||
|
use App\Http\Controllers\ConsoleController;
|
||||||
use App\Http\Controllers\CronjobController;
|
use App\Http\Controllers\CronjobController;
|
||||||
use App\Http\Controllers\DatabaseBackupController;
|
use App\Http\Controllers\DatabaseBackupController;
|
||||||
use App\Http\Controllers\DatabaseController;
|
use App\Http\Controllers\DatabaseController;
|
||||||
@ -123,6 +124,11 @@
|
|||||||
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
|
||||||
|
Route::get('/{server}/console', [ConsoleController::class, 'index'])->name('servers.console');
|
||||||
|
Route::post('/{server}/console', [ConsoleController::class, 'run'])->name('servers.console.run');
|
||||||
});
|
});
|
||||||
|
|
||||||
// settings
|
// settings
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
|
export VITO_VERSION="1.x"
|
||||||
export DEBIAN_FRONTEND=noninteractive
|
export DEBIAN_FRONTEND=noninteractive
|
||||||
export NEEDRESTART_MODE=a
|
export NEEDRESTART_MODE=a
|
||||||
|
|
||||||
@ -151,11 +152,13 @@ ln -s /etc/nginx/sites-available/vito /etc/nginx/sites-enabled/
|
|||||||
service nginx restart
|
service nginx restart
|
||||||
rm -rf /home/${V_USERNAME}/vito
|
rm -rf /home/${V_USERNAME}/vito
|
||||||
git config --global core.fileMode false
|
git config --global core.fileMode false
|
||||||
git clone -b 1.x ${V_REPO} /home/${V_USERNAME}/vito
|
git clone -b ${VITO_VERSION} ${V_REPO} /home/${V_USERNAME}/vito
|
||||||
find /home/${V_USERNAME}/vito -type d -exec chmod 755 {} \;
|
find /home/${V_USERNAME}/vito -type d -exec chmod 755 {} \;
|
||||||
find /home/${V_USERNAME}/vito -type f -exec chmod 644 {} \;
|
find /home/${V_USERNAME}/vito -type f -exec chmod 644 {} \;
|
||||||
cd /home/${V_USERNAME}/vito && git config core.fileMode false
|
cd /home/${V_USERNAME}/vito && git config core.fileMode false
|
||||||
cd /home/${V_USERNAME}/vito && composer install --no-dev
|
cd /home/${V_USERNAME}/vito
|
||||||
|
git checkout $(git tag -l --merged ${VITO_VERSION} --sort=-v:refname | head -n 1)
|
||||||
|
composer install --no-dev
|
||||||
cp .env.prod .env
|
cp .env.prod .env
|
||||||
touch /home/${V_USERNAME}/vito/storage/database.sqlite
|
touch /home/${V_USERNAME}/vito/storage/database.sqlite
|
||||||
php artisan key:generate
|
php artisan key:generate
|
||||||
|
@ -4,7 +4,9 @@ cd /home/vito/vito
|
|||||||
|
|
||||||
php artisan down
|
php artisan down
|
||||||
|
|
||||||
git pull
|
git fetch --all
|
||||||
|
|
||||||
|
git checkout $(git tag -l --merged 1.x --sort=-v:refname | head -n 1)
|
||||||
|
|
||||||
composer install --no-dev
|
composer install --no-dev
|
||||||
|
|
||||||
|
@ -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();
|
||||||
@ -212,7 +212,7 @@ public function test_git_hook_deployment(): void
|
|||||||
'content' => 'git pull',
|
'content' => 'git pull',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$this->post(route('git-hooks'), [
|
$this->post(route('api.git-hooks'), [
|
||||||
'secret' => 'secret',
|
'secret' => 'secret',
|
||||||
])->assertSessionDoesntHaveErrors();
|
])->assertSessionDoesntHaveErrors();
|
||||||
|
|
||||||
@ -240,7 +240,7 @@ public function test_git_hook_deployment_invalid_secret(): void
|
|||||||
'content' => 'git pull',
|
'content' => 'git pull',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$this->post(route('git-hooks'), [
|
$this->post(route('api.git-hooks'), [
|
||||||
'secret' => 'invalid-secret',
|
'secret' => 'invalid-secret',
|
||||||
])->assertNotFound();
|
])->assertNotFound();
|
||||||
|
|
||||||
|
46
tests/Feature/ConsoleTest.php
Normal file
46
tests/Feature/ConsoleTest.php
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Feature;
|
||||||
|
|
||||||
|
use App\Facades\SSH;
|
||||||
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
class ConsoleTest extends TestCase
|
||||||
|
{
|
||||||
|
use RefreshDatabase;
|
||||||
|
|
||||||
|
public function test_see_console(): void
|
||||||
|
{
|
||||||
|
$this->actingAs($this->user);
|
||||||
|
|
||||||
|
$this->get(route('servers.console', $this->server))
|
||||||
|
->assertSuccessful()
|
||||||
|
->assertSeeText('Headless Console');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_run(): void
|
||||||
|
{
|
||||||
|
SSH::fake('fake output');
|
||||||
|
|
||||||
|
$this->actingAs($this->user);
|
||||||
|
|
||||||
|
$this->post(route('servers.console.run', $this->server), [
|
||||||
|
'user' => 'vito',
|
||||||
|
'command' => 'ls -la',
|
||||||
|
])->assertStreamedContent('fake output');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_run_validation_error(): void
|
||||||
|
{
|
||||||
|
$this->actingAs($this->user);
|
||||||
|
|
||||||
|
$this->post(route('servers.console.run', $this->server), [
|
||||||
|
'user' => 'vito',
|
||||||
|
])->assertSessionHasErrors('command');
|
||||||
|
|
||||||
|
$this->post(route('servers.console.run', $this->server), [
|
||||||
|
'command' => 'ls -la',
|
||||||
|
])->assertSessionHasErrors('user');
|
||||||
|
}
|
||||||
|
}
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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');
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ public function test_see_queues()
|
|||||||
'site' => $this->site,
|
'site' => $this->site,
|
||||||
])
|
])
|
||||||
)
|
)
|
||||||
->assertOk()
|
->assertSuccessful()
|
||||||
->assertSee($queue->command);
|
->assertSee($queue->command);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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'],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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!"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user