Compare commits

..

2 commits
master ... dev

Author SHA1 Message Date
Yolan Mees
8b29ab65ef Package / Service manager 2024-09-15 08:45:24 +02:00
yolanmees
03dcdc5a4d
Merge pull request #18 from yolanmees/master
master into dev
2024-09-14 20:11:40 +02:00
9 changed files with 669 additions and 442 deletions

View file

@ -5,5 +5,9 @@ php:
finder:
not-name:
- index.php
js: true
- server.php
js:
finder:
not-name:
- webpack.mix.js
css: true

View file

@ -65,7 +65,7 @@ To correctly manage remote servers Spikster has to be on a public IP address (IP
## Spikster LEMP environment
- nginx: 1.18
- PHP-FPM: 8.3, 8.2, 8.1, 8.0, 7.4
- PHP-FPM: 8.2, 8.1, 8.0, 7.4
- MySql: 8
- node: 16
- npm: 8
@ -73,11 +73,11 @@ To correctly manage remote servers Spikster has to be on a public IP address (IP
## Screenshots
![](https://spikster.com/images/Spikster-server-overview.png)
<img src="https://spikster.com/images/docs/server.png">
![](https://spikster.com/images/Spikster-site-overview.png)
<img src="https://spikster.com/images/docs/site.png">
![](https://spikster.com/images/Spikster-wordpress.png)
<img src="https://spikster.com/images/docs/cron.png">
## Why use Spikster?
Spikster is easy, stable, powerful and free for any personal and commercial use and it's a perfect alternative to Cpanel, Plesk, Runcloud, CyberPanel, DirectAdmin, Forge and similar software...

View file

@ -0,0 +1,83 @@
<?php
namespace App\Livewire\Server\Packages;
use App\Models\Server;
use Livewire\Component;
use Illuminate\Support\Facades\Http;
class Overview extends Component
{
public $server;
public $server_id;
public $items;
public function mount($server_id)
{
$this->server_id = $server_id;
$this->server = Server::where('server_id', $server_id)->first();
$this->items = $this->getPackages();
$this->items['installed'] = $this->getInstalledPackages();
}
public function render()
{
return view('livewire.server.packages.overview');
}
private function getPackages()
{
return [
'services' => [
['name' => 'spamassassin', 'description' => 'SpamAssassin is a mail filter which attempts to identify spam using a variety of mechanisms including text analysis, Bayesian filtering, DNS blocklists, and collaborative filtering databases.'],
['name' => 'nginx', 'description' => 'Nginx is a web server which can also be used as a reverse proxy, load balancer, mail proxy and HTTP cache.'],
['name' => 'exim4', 'description' => 'Exim is a message transfer agent (MTA) developed at the University of Cambridge for use on Unix systems connected to the Internet.'],
['name' => 'dovecot', 'description' => 'Dovecot is an open-source IMAP and POP3 server for Unix-like operating systems, written primarily with security in mind.'],
['name' => 'php8.3-fpm', 'description' => 'PHP-FPM (FastCGI Process Manager) is an alternative PHP FastCGI implementation with some additional features useful for sites of any size, especially busier sites.'],
['name' => 'clamav-daemon', 'description' => 'ClamAV is an open-source antivirus engine for detecting trojans, viruses, malware & other malicious threats.'],
['name' => 'redis-server', 'description' => 'Redis is an open-source, in-memory data structure store, used as a database, cache, and message broker.'],
['name' => 'mysql', 'description' => 'MySQL is an open-source relational database management system.'],
],
'processes' => [
['name' => 'glances', 'description' => 'Glances is a cross-platform monitoring tool which aims to present a large amount of monitoring information through a curses or Web-based interface.'],
],
'packages' => [
['name' => 'roundcube', 'description' => 'Roundcube is a browser-based multilingual IMAP client with an application-like user interface.'],
],
];
}
public function getInstalledPackages()
{
$response = Http::withHeaders([
'Authorization' => 'Bearer ' . session('token'),
])->get($this->server->ip. '/servers/' . $this->server_id . '/packages');
return $response->json();
}
public function getInstalledPackagesMock()
{
return [
'services' => [
['name' => 'spamassassin', 'status' => 'ACTIVE'],
['name' => 'nginx', 'status' => 'ACTIVE'],
['name' => 'exim4', 'status' => 'ACTIVE'],
['name' => 'dovecot', 'status' => 'ACTIVE'],
['name' => 'php8.3-fpm', 'status' => 'ACTIVE'],
['name' => 'clamav-daemon', 'status' => 'INACTIVE'],
['name' => 'redis-server', 'status' => 'ACTIVE'],
['name' => 'mysql', 'status' => 'ACTIVE'],
],
'processes' => [
['name' => 'glances', 'status' => 'ACTIVE'],
],
'packages' => [
['name' => 'roundcube', 'status' => 'INSTALLED'],
],
];
}
}

View file

@ -2,9 +2,9 @@
namespace App\Livewire\Server;
use Livewire\Component;
use App\Models\Server;
use Http;
use Livewire\Component;
use Illuminate\Support\Facades\Http;
class PackagesInstalled extends Component
{
@ -29,7 +29,7 @@ class PackagesInstalled extends Component
public function getInstalledPackages()
{
$url = $this->server->ip . '/api/servers/' . $this->server->server_id . '/packages';
$url = $this->server->ip . '/api/servers/' . $this->server->server_id . '/services';
$response = Http::get($url);
$response = $response->json()[0];
foreach ($response as $key => $value) {

View file

@ -1,33 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
xsi:noNamespaceSchemaLocation="./vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="vendor/autoload.php"
colors="true"
>
<testsuites>
<testsuite name="Unit">
<directory>tests/Unit</directory>
<directory suffix="Test.php">./tests/Unit</directory>
</testsuite>
<testsuite name="Feature">
<directory>tests/Feature</directory>
<directory suffix="Test.php">./tests/Feature</directory>
</testsuite>
</testsuites>
<source>
<coverage processUncoveredFiles="true">
<include>
<directory>app</directory>
<directory suffix=".php">./app</directory>
</include>
</source>
</coverage>
<php>
<env name="APP_ENV" value="testing"/>
<env name="APP_MAINTENANCE_DRIVER" value="file"/>
<env name="BCRYPT_ROUNDS" value="4"/>
<env name="CACHE_STORE" value="array"/>
<!-- <env name="DB_CONNECTION" value="sqlite"/> -->
<!-- <env name="DB_DATABASE" value=":memory:"/> -->
<env name="MAIL_MAILER" value="array"/>
<env name="PULSE_ENABLED" value="false"/>
<env name="QUEUE_CONNECTION" value="sync"/>
<env name="SESSION_DRIVER" value="array"/>
<env name="TELESCOPE_ENABLED" value="false"/>
<server name="APP_ENV" value="testing"/>
<server name="BCRYPT_ROUNDS" value="4"/>
<server name="CACHE_DRIVER" value="array"/>
<!-- <server name="DB_CONNECTION" value="sqlite"/> -->
<!-- <server name="DB_DATABASE" value=":memory:"/> -->
<server name="MAIL_MAILER" value="array"/>
<server name="QUEUE_CONNECTION" value="sync"/>
<server name="SESSION_DRIVER" value="array"/>
<server name="TELESCOPE_ENABLED" value="false"/>
</php>
</phpunit>

View file

@ -0,0 +1,27 @@
<div>
@if($message)
<div class="alert alert-info">{{ $message }}</div>
@endif
<table class="table table-striped">
<thead>
<tr>
<th>Service</th>
<th>Status</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
@foreach($services as $service)
<tr>
<td>{{ $service['name'] }}</td>
<td>{{ $service['status'] }}</td>
<td>
<button wire:click="restartService('{{ $service['name'] }}')"
class="btn btn-primary">Restart</button>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>

View file

@ -0,0 +1,47 @@
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4">
@for ($i = 0; $i < count($items['installed'] ?? []); $i++)
@php
$packages = $items[array_keys($items)[$i]];
$installed = $items['installed'][array_keys($items['installed'])[$i]];
@endphp
@for ($x = 0; $x < count($packages ?? []); $x++)
<x-card header="{{ ucfirst($packages[$x]['name']) }}" size="md" dark="false">
<div class="flex flex-col my-4 gap-y-4">
<div class="flex my-4 gap-x-4">
@if (array_keys($items['installed'])[$i] == 'packages')
@if ($installed[$x]['status'] == 'INSTALLED')
<div class="flex-col">
<p class="pb-4 text-green-500">Installed</p>
<x-button variant="danger" size="sm">Uninstall</x-button>
</div>
@else
<x-button variant="primary" size="sm">Install</x-button>
@endif
@else
@if ($installed[$x]['status'] == 'ACTIVE')
<div class="flex-col">
<span class="pb-4 text-green-500">Active</span>
<div class="flex mt-4 gap-x-4">
<x-button variant="danger" size="sm">Stop</x-button>
<x-button variant="warning" size="sm">Restart</x-button>
</div>
</div>
@elseif ($installed[$x]['status'] == 'INACTIVE')
<div class="flex-col gap-4">
<span class="pb-4 text-yellow-500">Inactive</span>
<div class="flex-1 mt-4 gap-x-4">
<x-button variant="success" size="sm">Start</x-button>
</div>
</div>
@else
<x-button variant="primary" size="sm">Install</x-button>
<x-button variant="danger" size="sm">Uninstall</x-button>
@endif
@endif
</div>
</div>
</x-card>
@endfor
@endfor
</div>

View file

@ -8,56 +8,83 @@
@section('content')
<div x-data="{ tab: 'monitor' }">
<ol class="breadcrumbs">
<li class="breadcrumb-item active">IP:<b><span class="ml-1" id="serveriptop"></span></b></li>
<li class="breadcrumb-item active">{{ __('spikster.sites') }}:<b><span class="ml-1" id="serversites"></span></b></li>
<li class="breadcrumb-item active">Ping:<b><span class="ml-1" id="serverping"><i class="fas fa-circle-notch fa-spin"></i></span></b></li>
</ol>
<div x-data="{ tab: 'monitor' }">
<ol class="breadcrumbs">
<li class="breadcrumb-item active">IP:<b><span class="ml-1" id="serveriptop"></span></b></li>
<li class="breadcrumb-item active">{{ __('spikster.sites') }}:<b><span class="ml-1" id="serversites"></span></b>
</li>
<li class="breadcrumb-item active">Ping:<b><span class="ml-1" id="serverping"><i
class="fas fa-circle-notch fa-spin"></i></span></b></li>
</ol>
<div class="pb-4">
<div class="sm:hidden">
<label for="tabs" class="sr-only">Select a tab</label>
<!-- Use an "onChange" listener to redirect the user to the selected tab URL. -->
<select id="tabs" name="tabs" class="block w-full rounded-md border-gray-300 focus:border-indigo-500 focus:ring-indigo-500">
<option selected>Monitor</option>
<option>Server information</option>
<option>Security</option>
<option>Tools</option>
</select>
<div class="pb-4">
<div class="sm:hidden">
<label for="tabs" class="sr-only">Select a tab</label>
<!-- Use an "onChange" listener to redirect the user to the selected tab URL. -->
<select id="tabs" name="tabs"
class="block w-full border-gray-300 rounded-md focus:border-indigo-500 focus:ring-indigo-500"
@change="tab = $event.target.value">
<option value="monitor" :selected="tab === 'monitor'">
Monitor
</option>
<option value="server" :selected="tab === 'server'">
Server information
</option>
<option value="packages" :selected="tab === 'packages'">
Packages
</option>
<option value="security" :selected="tab === 'security'">
Security
</option>
<option value="tools" :selected="tab === 'tools'">
Tools
</option>
</select>
</div>
<div class="hidden sm:block">
<nav class="flex space-x-4" aria-label="Tabs">
<!-- Current: "bg-gray-200 text-gray-800", Default: "text-gray-600 hover:text-gray-800" -->
<a @click="tab = 'monitor'" clas="tab" :class="tab === 'monitor' ? 'tab-item-active' : 'tab-item'"
aria-current="page">Monitor</a>
<a @click="tab = 'server'" :class="tab === 'server' ? 'tab-item-active' : 'tab-item'">Server
information</a>
<a @click="tab = 'packages'" :class="tab === 'packages' ? 'tab-item-active' : 'tab-item'">Packages</a>
<a @click="tab = 'security'" :class="tab === 'security' ? 'tab-item-active' : 'tab-item'">Security</a>
<a @click="tab = 'tools'" :class="tab === 'tools' ? 'tab-item-active' : 'tab-item'">Tools</a>
</nav>
</div>
</div>
<div class="hidden sm:block">
<nav class="flex space-x-4" aria-label="Tabs">
<!-- Current: "bg-gray-200 text-gray-800", Default: "text-gray-600 hover:text-gray-800" -->
<a @click="tab = 'monitor'" clas="tab" :class="tab === 'monitor' ? 'tab-item-active' : 'tab-item'" aria-current="page">Monitor</a>
<a @click="tab = 'server'" :class="tab === 'server' ? 'tab-item-active' : 'tab-item'">Server information</a>
<a @click="tab = 'security'" :class="tab === 'security' ? 'tab-item-active' : 'tab-item'">Security</a>
<a @click="tab = 'tools'" :class="tab === 'tools' ? 'tab-item-active' : 'tab-item'">Tools</a>
</nav>
</div>
</div>
<div class="grid grid-cols-2 gap-4" x-show="tab === 'monitor'">
<div class="grid grid-cols-2 gap-4" x-show="tab === 'monitor'">
@livewire('stats.cpu', ['server_id' => $server_id])
@livewire('stats.mem', ['server_id' => $server_id])
@livewire('stats.load', ['server_id' => $server_id])
@livewire('stats.disk', ['server_id' => $server_id])
</div>
</div>
<div class="" x-show="tab === 'packages'">
@livewire('server.packages.overview', ['server_id' => $server_id])
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-3 gap-4" x-show="tab === 'server'">
<x-card header="{{ __('spikster.server_information') }}" size="md" dark="false">
{{-- <canvas id="cpuChart" width="100%" height="40"></canvas> --}}
<x-input type="text" label="{{ __('spikster.server_name') }}:" placeholder="e.g. Production" id="servername" autocomplete="off" />
<x-input type="text" label="{{ __('spikster.server_ip') }}:" placeholder="e.g. 123.123.123.123" id="serverip" autocomplete="off" />
<x-input type="text" label="{{ __('spikster.server_provider') }}:" placeholder="e.g. Digital Ocean" id="serverprovider" autocomplete="off" />
<x-input type="text" label="{{ __('spikster.server_location') }}:" placeholder="e.g. Amsterdam" id="serverlocation" autocomplete="off" />
<x-button type="button" id="updateServer">{{ __('spikster.update') }}</x-button>
</x-card>
<x-card header="{{ __('spikster.system_services') }}" size="md" dark="false">
<div class="grid grid-cols-1 xl:grid-cols-2 gap-4">
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-3" x-show="tab === 'server'">
<x-card header="{{ __('spikster.server_information') }}" size="md" dark="false">
{{-- <canvas id="cpuChart" width="100%" height="40"></canvas> --}}
<x-input type="text" label="{{ __('spikster.server_name') }}:" placeholder="e.g. Production"
id="servername" autocomplete="off" />
<x-input type="text" label="{{ __('spikster.server_ip') }}:" placeholder="e.g. 123.123.123.123"
id="serverip" autocomplete="off" />
<x-input type="text" label="{{ __('spikster.server_provider') }}:" placeholder="e.g. Digital Ocean"
id="serverprovider" autocomplete="off" />
<x-input type="text" label="{{ __('spikster.server_location') }}:" placeholder="e.g. Amsterdam"
id="serverlocation" autocomplete="off" />
<x-button type="button" id="updateServer">{{ __('spikster.update') }}</x-button>
</x-card>
<x-card header="{{ __('spikster.system_services') }}" size="md" dark="false">
{{-- <div class="grid grid-cols-1 gap-4 xl:grid-cols-2">
<div class="flex justify-between gap-4">
<p>nginx</p>
<x-button type="button" variant="warning" id="restartnginx">{{ __('spikster.restart') }} </x-button>
@ -77,437 +104,457 @@
<div class="flex justify-between gap-4">
<p>Supervisor</p>
<x-button type="button" variant="warning" id="restartsupervisor">{{ __('spikster.restart') }} </x-button>
</div>
</div>
</x-card>
<x-card header="Logs" size="md" dark="false">
<a href="{{route('logs', $server_id)}}">
<x-button>
Open Logs
</x-button>
</a>
</x-card>
</div>
<div class="flex gap-x-4" x-show="tab === 'security'">
<div class="w-1/2">
<x-card header="Security" size="md" dark="false">
<p>Fail2ban</p>
<div>
<a href="{{route('server.fail2ban', $server_id)}}" class="btn btn-primary" type="button" id="">Open Fail2ban</a>
</div>
</div> --}}
{{-- </div> --}}
<livewire-manage-service :server_id="$server_id" />
</x-card>
<x-card header="Logs" size="md" dark="false">
<a href="{{ route('logs', $server_id) }}">
<x-button>
Open Logs
</x-button>
</a>
</x-card>
</div>
</div>
<div class="flex gap-x-4" x-show="tab === 'tools'">
<div class="w-1/3">
<x-card header="{{ __('spikster.tools') }}" size="md" dark="false">
<p>{{ __('spikster.php_cli_version') }}:</p>
<div class="input-group">
<select class="form-control" id="phpver">
<option value="8.3" id="php83">8.3</option>
<option value="8.2" id="php82">8.2</option>
<option value="8.1" id="php81">8.1</option>
<option value="8.0" id="php80">8.0</option>
<option value="7.4" id="php74">7.4</option>
</select>
<div class="input-group-append">
<button class="btn btn-primary" type="button" id="changephp"><i class="fas fa-edit"></i></button>
<div class="flex gap-x-4" x-show="tab === 'security'">
<div class="w-1/2">
<x-card header="Security" size="md" dark="false">
<p>Fail2ban</p>
<div>
<a href="{{ route('server.fail2ban', $server_id) }}" class="btn btn-primary" type="button"
id="">Open Fail2ban</a>
</div>
</div>
<div class="mt-4">
<p class="mb-2">{{ __('spikster.manage_cron_jobs') }}:</p>
<button class="btn btn-primary" type="button" id="editcrontab">{{ __('spikster.edit_crontab') }}</button>
</div>
<div class="mt-4">
<p class="mb-2">{{ __('spikster.reset_cipi_password') }}:</p>
<button class="btn btn-danger" type="button" id="rootreset">{{ __('spikster.require_reset_cipi_password') }}</button>
</div>
<div class="mt-4">
{{-- <p class="mb-2">{{ __('spikster.cipi_build_version') }}:</p>
<span class="btn btn-secondary" id="serverbuild"></span> --}}
</div>
<div class="space"></div>
</x-card>
</x-card>
</div>
</div>
<div class="flex gap-x-4" x-show="tab === 'tools'">
<div class="w-1/3">
<x-card header="{{ __('spikster.tools') }}" size="md" dark="false">
<p>{{ __('spikster.php_cli_version') }}:</p>
<div class="input-group">
<select class="form-control" id="phpver">
<option value="8.3" id="php83">8.3</option>
<option value="8.2" id="php82">8.2</option>
<option value="8.1" id="php81">8.1</option>
<option value="8.0" id="php80">8.0</option>
<option value="7.4" id="php74">7.4</option>
</select>
<div class="input-group-append">
<button class="btn btn-primary" type="button" id="changephp"><i
class="fas fa-edit"></i></button>
</div>
</div>
<div class="mt-4">
<p class="mb-2">{{ __('spikster.manage_cron_jobs') }}:</p>
<button class="btn btn-primary" type="button"
id="editcrontab">{{ __('spikster.edit_crontab') }}</button>
</div>
<div class="mt-4">
<p class="mb-2">{{ __('spikster.reset_cipi_password') }}:</p>
<button class="btn btn-danger" type="button"
id="rootreset">{{ __('spikster.require_reset_cipi_password') }}</button>
</div>
</div>
<div class="mt-4">
{{-- <p class="mb-2">{{ __('spikster.cipi_build_version') }}:</p>
<span class="btn btn-secondary" id="serverbuild"></span> --}}
</div>
<div class="space"></div>
</x-card>
</div>
</div>
@endsection
@section('extra')
<input type="hidden" id="currentip">
<dialog class="modal fade" id="updateServerModal" tabindex="-1" role="dialog" aria-labelledby="updateServerModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document" id="updateserverdialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="updateServerModalLabel">{{ __('spikster.update_server_modal_title') }}</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<p>{{ __('spikster.update_server_modal_text') }}</p>
<p class="d-none" id="ipnotice"><b>{!! __('spikster.update_server_modal_ip') !!}</b></p>
<div class="text-center">
<button class="btn btn-primary" type="button" id="submit">{{ __('spikster.confirm') }} </button>
<input type="hidden" id="currentip">
<dialog class="modal fade" id="updateServerModal" tabindex="-1" role="dialog"
aria-labelledby="updateServerModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document" id="updateserverdialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="updateServerModalLabel">
{{ __('spikster.update_server_modal_title') }}</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<p>{{ __('spikster.update_server_modal_text') }}</p>
<p class="d-none" id="ipnotice"><b>{!! __('spikster.update_server_modal_ip') !!}</b></p>
<div class="text-center">
<button class="btn btn-primary" type="button" id="submit">{{ __('spikster.confirm') }}
</button>
</div>
<div class="space"></div>
</div>
<div class="space"></div>
</div>
</div>
</div>
</dialog>
<dialog class="modal fade" id="crontabModal" tabindex="-1" role="dialog" aria-labelledby="crontabModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="crontabModalLabel">{{ __('spikster.server_crontab') }}</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<p>{{ __('spikster.server_crontab_edit') }}:</p>
<div id="crontab" style="height:250px;width:100%;"></div>
<div class="space"></div>
<div class="text-center">
<button class="btn btn-primary" type="button" id="crontabsubmit">{{ __('spikster.save') }} <i class="fas fa-circle-notch fa-spin d-none" id="crontableloading"></i></button>
</dialog>
<dialog class="modal fade" id="crontabModal" tabindex="-1" role="dialog" aria-labelledby="crontabModalLabel"
aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="crontabModalLabel">{{ __('spikster.server_crontab') }}</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<p>{{ __('spikster.server_crontab_edit') }}:</p>
<div id="crontab" style="height:250px;width:100%;"></div>
<div class="space"></div>
<div class="text-center">
<button class="btn btn-primary" type="button" id="crontabsubmit">{{ __('spikster.save') }}
<i class="fas fa-circle-notch fa-spin d-none" id="crontableloading"></i></button>
</div>
<div class="space"></div>
</div>
<div class="space"></div>
</div>
</div>
</div>
</dialog>
<dialog class="modal fade" id="rootresetModal" tabindex="-1" role="dialog" aria-labelledby="rootresetModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="rootresetModalLabel">{{ __('spikster.require_password_reset_modal_title') }}</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<p>{{ __('spikster.require_password_reset_modal_text') }}</p>
<div class="space"></div>
<div class="text-center">
<button class="btn btn-danger" type="button" id="rootresetsubmit">{{ __('spikster.confirm') }} <i class="fas fa-circle-notch fa-spin d-none" id="rootresetloading"></i></button>
</dialog>
<dialog class="modal fade" id="rootresetModal" tabindex="-1" role="dialog"
aria-labelledby="rootresetModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="rootresetModalLabel">
{{ __('spikster.require_password_reset_modal_title') }}</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<p>{{ __('spikster.require_password_reset_modal_text') }}</p>
<div class="space"></div>
<div class="text-center">
<button class="btn btn-danger" type="button"
id="rootresetsubmit">{{ __('spikster.confirm') }} <i
class="fas fa-circle-notch fa-spin d-none" id="rootresetloading"></i></button>
</div>
<div class="space"></div>
</div>
<div class="space"></div>
</div>
</div>
</div>
</dialog>
</div>
</dialog>
</div>
@endsection
@section('css')
@endsection
@section('js')
<script>
// Get Server info
$('#mainloading').removeClass('d-none');
<script>
// Get Server info
$('#mainloading').removeClass('d-none');
// Crontab editor
var crontab = ace.edit("crontab");
crontab.setTheme("ace/theme/monokai");
crontab.session.setMode("ace/mode/sh");
// Crontab editor
var crontab = ace.edit("crontab");
crontab.setTheme("ace/theme/monokai");
crontab.session.setMode("ace/mode/sh");
// Crontab edit
$('#editcrontab').click(function() {
$('#crontabModal').modal();
});
// Crontab Submit
$('#crontabsubmit').click(function() {
$.ajax({
url: '/api/servers/{{ $server_id }}',
type: 'PATCH',
contentType: 'application/json',
dataType: 'json',
data: JSON.stringify({
'cron': crontab.getSession().getValue(),
}),
beforeSend: function() {
$('#crontableloading').removeClass('d-none');
},
success: function(data) {
$('#crontableloading').addClass('d-none');
$('#crontabModal').modal('toggle');
serverInit();
},
// Crontab edit
$('#editcrontab').click(function() {
$('#crontabModal').modal();
});
});
// Server Init
function serverInit() {
getDataNoDT('/api/servers',false);
$.ajax({
url: '/api/servers/{{ $server_id }}',
type: 'GET',
success: function(data) {
$('#mainloading').addClass('d-none');
$('#serveriptop').html(data.ip);
$('#serversites').html(data.sites);
$('#maintitle').html('- '+data.name);
$('#servername').val(data.name);
$('#serverip').val(data.ip);
$('#serverprovider').val(data.provider);
$('#serverlocation').val(data.location);
$('#currentip').val(data.ip);
crontab.session.setValue(data.cron);
$('#serverbuild').empty();
if(data.build) {
$('#serverbuild').html(data.build);
} else {
$('#serverbuild').html('{{ __('spikster.unknown') }}');
}
switch (data.php) {
case '8.3':
$('#php83').attr("selected","selected");
break;
case '8.2':
$('#php82').attr("selected","selected");
break;
case '8.1':
$('#php81').attr("selected","selected");
break;
case '8.0':
$('#php80').attr("selected","selected");
break;
case '7.4':
$('#php74').attr("selected","selected");
break;
case '7.3':
// Append legacy php 7.3
$('#phpver').append('<option value="7.3" selected>7.3</option>');
break;
default:
break;
}
},
// Crontab Submit
$('#crontabsubmit').click(function() {
$.ajax({
url: '/api/servers/{{ $server_id }}',
type: 'PATCH',
contentType: 'application/json',
dataType: 'json',
data: JSON.stringify({
'cron': crontab.getSession().getValue(),
}),
beforeSend: function() {
$('#crontableloading').removeClass('d-none');
},
success: function(data) {
$('#crontableloading').addClass('d-none');
$('#crontabModal').modal('toggle');
serverInit();
},
});
});
}
// Init variables
serverInit();
// Server Init
function serverInit() {
getDataNoDT('/api/servers', false);
$.ajax({
url: '/api/servers/{{ $server_id }}',
type: 'GET',
success: function(data) {
$('#mainloading').addClass('d-none');
$('#serveriptop').html(data.ip);
$('#serversites').html(data.sites);
$('#maintitle').html('- ' + data.name);
$('#servername').val(data.name);
$('#serverip').val(data.ip);
$('#serverprovider').val(data.provider);
$('#serverlocation').val(data.location);
$('#currentip').val(data.ip);
crontab.session.setValue(data.cron);
$('#serverbuild').empty();
if (data.build) {
$('#serverbuild').html(data.build);
} else {
$('#serverbuild').html('{{ __('spikster.unknown') }}');
}
switch (data.php) {
case '8.3':
$('#php83').attr("selected", "selected");
break;
case '8.2':
$('#php82').attr("selected", "selected");
break;
case '8.1':
$('#php81').attr("selected", "selected");
break;
case '8.0':
$('#php80').attr("selected", "selected");
break;
case '7.4':
$('#php74').attr("selected", "selected");
break;
case '7.3':
// Append legacy php 7.3
$('#phpver').append('<option value="7.3" selected>7.3</option>');
break;
default:
break;
}
},
});
}
// Ping
function getPing() {
$.ajax({
url: '/api/servers/{{ $server_id }}/ping',
type: 'GET',
beforeSend: function() {
$('#serverping').empty();
$('#serverping').html('<i class="fas fa-circle-notch fa-spin" title="{{ __('spikster.loading_data') }}"></i>');
},
success: function(data) {
$('#serverping').empty();
$('#serverping').html('<i class="fas fa-check text-success"></i>');
},
});
}
setInterval(function() {
getPing();
}, 10000);
getPing();
// Change PHP
$('#changephp').click(function() {
$.ajax({
url: '/api/servers/{{ $server_id }}',
type: 'PATCH',
contentType: 'application/json',
dataType: 'json',
data: JSON.stringify({
'php': $('#phpver').val(),
}),
beforeSend: function() {
$('#changephp').html('<i class="fas fa-circle-notch fa-spin" title="{{ __('spikster.loading_please_wait') }}"></i>');
},
success: function(data) {
$('#changephp').empty();
$('#changephp').html('<i class="fas fas fa-edit"></i>');
},
});
// Init variables
serverInit();
});
// Restart nginx
$('#restartnginx').click(function() {
$.ajax({
url: '/api/servers/{{ $server_id }}/servicerestart/nginx',
type: 'POST',
beforeSend: function() {
$('#loadingnginx').removeClass('d-none');
},
success: function(data) {
$('#loadingnginx').addClass('d-none');
},
// Ping
function getPing() {
$.ajax({
url: '/api/servers/{{ $server_id }}/ping',
type: 'GET',
beforeSend: function() {
$('#serverping').empty();
$('#serverping').html(
'<i class="fas fa-circle-notch fa-spin" title="{{ __('spikster.loading_data') }}"></i>'
);
},
success: function(data) {
$('#serverping').empty();
$('#serverping').html('<i class="fas fa-check text-success"></i>');
},
});
}
setInterval(function() {
getPing();
}, 10000);
getPing();
// Change PHP
$('#changephp').click(function() {
$.ajax({
url: '/api/servers/{{ $server_id }}',
type: 'PATCH',
contentType: 'application/json',
dataType: 'json',
data: JSON.stringify({
'php': $('#phpver').val(),
}),
beforeSend: function() {
$('#changephp').html(
'<i class="fas fa-circle-notch fa-spin" title="{{ __('spikster.loading_please_wait') }}"></i>'
);
},
success: function(data) {
$('#changephp').empty();
$('#changephp').html('<i class="fas fa-edit"></i>');
},
});
serverInit();
});
});
// Restart php
$('#restartphp').click(function() {
$.ajax({
url: '/api/servers/{{ $server_id }}/servicerestart/php',
type: 'POST',
beforeSend: function() {
$('#loadingphp').removeClass('d-none');
},
success: function(data) {
$('#loadingphp').addClass('d-none');
},
// Restart nginx
$('#restartnginx').click(function() {
$.ajax({
url: '/api/servers/{{ $server_id }}/servicerestart/nginx',
type: 'POST',
beforeSend: function() {
$('#loadingnginx').removeClass('d-none');
},
success: function(data) {
$('#loadingnginx').addClass('d-none');
},
});
});
});
// Restart mysql
$('#restartmysql').click(function() {
$.ajax({
url: '/api/servers/{{ $server_id }}/servicerestart/mysql',
type: 'POST',
beforeSend: function() {
$('#loadingmysql').removeClass('d-none');
},
success: function(data) {
$('#loadingmysql').addClass('d-none');
},
// Restart php
$('#restartphp').click(function() {
$.ajax({
url: '/api/servers/{{ $server_id }}/servicerestart/php',
type: 'POST',
beforeSend: function() {
$('#loadingphp').removeClass('d-none');
},
success: function(data) {
$('#loadingphp').addClass('d-none');
},
});
});
});
// Restart redis
$('#restartredis').click(function() {
$.ajax({
url: '/api/servers/{{ $server_id }}/servicerestart/redis',
type: 'POST',
beforeSend: function() {
$('#loadingredis').removeClass('d-none');
},
success: function(data) {
$('#loadingredis').addClass('d-none');
},
// Restart mysql
$('#restartmysql').click(function() {
$.ajax({
url: '/api/servers/{{ $server_id }}/servicerestart/mysql',
type: 'POST',
beforeSend: function() {
$('#loadingmysql').removeClass('d-none');
},
success: function(data) {
$('#loadingmysql').addClass('d-none');
},
});
});
});
// Restart supervisor
$('#restartsupervisor').click(function() {
$.ajax({
url: '/api/servers/{{ $server_id }}/servicerestart/supervisor',
type: 'POST',
beforeSend: function() {
$('#loadingsupervisor').removeClass('d-none');
},
success: function(data) {
$('#loadingsupervisor').addClass('d-none');
},
// Restart redis
$('#restartredis').click(function() {
$.ajax({
url: '/api/servers/{{ $server_id }}/servicerestart/redis',
type: 'POST',
beforeSend: function() {
$('#loadingredis').removeClass('d-none');
},
success: function(data) {
$('#loadingredis').addClass('d-none');
},
});
});
});
// Root Reset
$('#rootreset').click(function() {
$('#rootresetModal').modal();
});
// Restart supervisor
$('#restartsupervisor').click(function() {
$.ajax({
url: '/api/servers/{{ $server_id }}/servicerestart/supervisor',
type: 'POST',
beforeSend: function() {
$('#loadingsupervisor').removeClass('d-none');
},
success: function(data) {
$('#loadingsupervisor').addClass('d-none');
},
});
});
// Root Reset Submit
$('#rootresetsubmit').click(function() {
$('#rootresetloading').removeClass('d-none');
$.ajax({
url: '/api/servers/{{ $server_id }}/rootreset',
type: 'POST',
success: function(data) {
success('{{ __('spikster.new_password_success') }}:<br><b>'+data.password+'</b>');
$(window).scrollTop(0);
$('#rootresetModal').modal('toggle');
},
complete: function() {
$('#rootresetloading').addClass('d-none');
// Root Reset
$('#rootreset').click(function() {
$('#rootresetModal').modal();
});
// Root Reset Submit
$('#rootresetsubmit').click(function() {
$('#rootresetloading').removeClass('d-none');
$.ajax({
url: '/api/servers/{{ $server_id }}/rootreset',
type: 'POST',
success: function(data) {
success('{{ __('spikster.new_password_success') }}:<br><b>' + data.password +
'</b>');
$(window).scrollTop(0);
$('#rootresetModal').modal('toggle');
},
complete: function() {
$('#rootresetloading').addClass('d-none');
}
});
});
//Check IP conflict (edit)
function ipConflictEdit(ip, server_id) {
conflict = 0;
JSON.parse(localStorage.otherdata).forEach(server => {
if (ip === server.ip && server.server_id !== server_id) {
conflict = conflict + 1;
}
});
return conflict;
}
// Update Server
$('#updateServer').click(function() {
$('#ipnotice').addClass('d-none');
if ($('#serverip').val() != $('#currentip').val()) {
$('#newip').html($('#serverip').val());
$('#ipnotice').removeClass('d-none');
}
});
});
//Check IP conflict (edit)
function ipConflictEdit(ip,server_id) {
conflict = 0;
JSON.parse(localStorage.otherdata).forEach(server => {
if(ip === server.ip && server.server_id !== server_id) {
conflict = conflict + 1;
validation = true;
if (!$('#servername').val() || $('#servername').val().length < 3) {
$('#servername').addClass('is-invalid');
$('#submit').addClass('disabled');
validation = false;
}
});
return conflict;
}
// Update Server
$('#updateServer').click(function() {
$('#ipnotice').addClass('d-none');
if($('#serverip').val() != $('#currentip').val()) {
$('#newip').html($('#serverip').val());
$('#ipnotice').removeClass('d-none');
}
validation = true;
if(!$('#servername').val() || $('#servername').val().length < 3) {
$('#servername').addClass('is-invalid');
$('#submit').addClass('disabled');
validation = false;
}
server_id = '{{ $server_id }}';
if(!$('#serverip').val() || !ipValidate($('#serverip').val()) || ipConflictEdit($('#serverip').val(),server_id) > 0) {
$('#serverip').addClass('is-invalid');
$('#submit').addClass('disabled');
validation = false;
}
if(validation) {
$('#loading').addClass('d-none');
$('#updateServerModal').modal();
}
});
// Update Server Validation
$('#servername').keyup(function() {
$('#servername').removeClass('is-invalid');
$('#submit').removeClass('disabled');
});
$('#serverip').keyup(function() {
$('#serverip').removeClass('is-invalid');
$('#submit').removeClass('disabled');
});
// Update Server Submit
$('#submit').click(function() {
$.ajax({
url: '/api/servers/{{ $server_id }}',
type: 'PATCH',
contentType: 'application/json',
dataType: 'json',
data: JSON.stringify({
'name': $('#servername').val(),
'ip': $('#serverip').val(),
'provider': $('#serverprovider').val(),
'location': $('#serverlocation').val()
}),
beforeSend: function() {
$('#loading').removeClass('d-none');
},
success: function(data) {
serverInit();
server_id = '{{ $server_id }}';
if (!$('#serverip').val() || !ipValidate($('#serverip').val()) || ipConflictEdit($('#serverip').val(),
server_id) > 0) {
$('#serverip').addClass('is-invalid');
$('#submit').addClass('disabled');
validation = false;
}
if (validation) {
$('#loading').addClass('d-none');
},
complete: function() {
$('#ipnotice').addClass('d-none');
$('#updateServerModal').modal('toggle');
$('#updateServerModal').modal();
}
});
});
// Charts style
Chart.defaults.global.defaultFontFamily = '-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif';
Chart.defaults.global.defaultFontColor = '#292b2c';
</script>
// Update Server Validation
$('#servername').keyup(function() {
$('#servername').removeClass('is-invalid');
$('#submit').removeClass('disabled');
});
$('#serverip').keyup(function() {
$('#serverip').removeClass('is-invalid');
$('#submit').removeClass('disabled');
});
// Update Server Submit
$('#submit').click(function() {
$.ajax({
url: '/api/servers/{{ $server_id }}',
type: 'PATCH',
contentType: 'application/json',
dataType: 'json',
data: JSON.stringify({
'name': $('#servername').val(),
'ip': $('#serverip').val(),
'provider': $('#serverprovider').val(),
'location': $('#serverlocation').val()
}),
beforeSend: function() {
$('#loading').removeClass('d-none');
},
success: function(data) {
serverInit();
$('#loading').addClass('d-none');
},
complete: function() {
$('#ipnotice').addClass('d-none');
$('#updateServerModal').modal('toggle');
}
});
});
// Charts style
Chart.defaults.global.defaultFontFamily =
'-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif';
Chart.defaults.global.defaultFontColor = '#292b2c';
</script>
@endsection

21
server.php Normal file
View file

@ -0,0 +1,21 @@
<?php
/**
* Laravel - A PHP Framework For Web Artisans
*
* @package Laravel
* @author Taylor Otwell <taylor@laravel.com>
*/
$uri = urldecode(
parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH)
);
// This file allows us to emulate Apache's "mod_rewrite" functionality from the
// built-in PHP web server. This provides a convenient way to test a Laravel
// application without having installed a "real" web server software here.
if ($uri !== '/' && file_exists(__DIR__.'/public'.$uri)) {
return false;
}
require_once __DIR__.'/public/index.php';