First initial start

This commit is contained in:
Dennis Smink
2020-09-15 10:31:37 +02:00
commit e5d9b241e6
508 changed files with 284762 additions and 0 deletions

15
.editorconfig Normal file
View File

@@ -0,0 +1,15 @@
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 4
trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = false
[*.{yml,yaml}]
indent_size = 2

50
.env.example Normal file
View File

@@ -0,0 +1,50 @@
APP_NAME=Laravel
APP_ENV=local
APP_KEY=
APP_DEBUG=true
APP_URL=http://localhost
APP_DEMO=false
PLOI_TOKEN=
PLOI_CORE_TOKEN=
LOG_CHANNEL=stack
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel
DB_USERNAME=root
DB_PASSWORD=
BROADCAST_DRIVER=log
CACHE_DRIVER=file
QUEUE_CONNECTION=redis
SESSION_DRIVER=file
SESSION_LIFETIME=120
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
MAIL_MAILER=smtp
MAIL_HOST=smtp.mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS=null
MAIL_FROM_NAME="${APP_NAME}"
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=
PUSHER_APP_ID=
PUSHER_APP_KEY=
PUSHER_APP_SECRET=
PUSHER_APP_CLUSTER=mt1
MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"

5
.gitattributes vendored Normal file
View File

@@ -0,0 +1,5 @@
* text=auto
*.css linguist-vendored
*.scss linguist-vendored
*.js linguist-vendored
CHANGELOG.md export-ignore

14
.gitignore vendored Normal file
View File

@@ -0,0 +1,14 @@
/node_modules
/public/hot
/public/storage
/storage/*.key
/vendor
.env
.env.backup
.phpunit.result.cache
Homestead.json
Homestead.yaml
npm-debug.log
yarn-error.log
.idea
.php_cs.cache

18
.php_cs Normal file
View File

@@ -0,0 +1,18 @@
<?php
$finder = Symfony\Component\Finder\Finder::create()
->notPath('vendor')
->notPath('bootstrap')
->notPath('storage')
->in(__DIR__)
->name('*.php')
->notName('*.blade.php');
return PhpCsFixer\Config::create()
->setRules([
'@PSR2' => true,
'array_syntax' => ['syntax' => 'short'],
'ordered_imports' => ['sortAlgorithm' => 'length'],
'no_unused_imports' => true,
])
->setFinder($finder);

13
.styleci.yml Normal file
View File

@@ -0,0 +1,13 @@
php:
preset: laravel
disabled:
- unused_use
finder:
not-name:
- index.php
- server.php
js:
finder:
not-name:
- webpack.mix.js
css: true

20
README.md Normal file
View File

@@ -0,0 +1,20 @@
# Ploi Core
With Ploi Core, you'll power-launch your webhosting company.
Using the ploi.io system as backbone you will be able to serve your customers your custom panel & feeling.
<p align="center"><img src="https://ploi-core.io/images/featured.png" width="100%"></p>
## Documentation
https://docs.ploi-core.io/
## Company
https://ploi.io
## Contributing
The contribution guide can be found inside our documentation:
https://docs.ploi-core.io/getting-started/contribution-guide

40
app/Casts/Encrypted.php Normal file
View File

@@ -0,0 +1,40 @@
<?php
namespace App\Casts;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
class Encrypted implements CastsAttributes
{
/**
* Cast the given value.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @param string $key
* @param mixed $value
* @param array $attributes
* @return mixed
*/
public function get($model, $key, $value, $attributes)
{
return $value;
}
/**
* Prepare the given value for storage.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @param string $key
* @param array $value
* @param array $attributes
* @return mixed
*/
public function set($model, $key, $value, $attributes)
{
if ($value) {
return encrypt($value);
}
return $value;
}
}

View File

@@ -0,0 +1,346 @@
<?php
namespace App\Console\Commands\Core;
use Exception;
use App\Models\User;
use RuntimeException;
use App\Models\Package;
use App\Services\Ploi\Ploi;
use Illuminate\Support\Str;
use Illuminate\Console\Command;
use App\Services\VersionChecker;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Http;
class Install extends Command
{
protected $company;
protected $signature = 'core:install';
protected $description = 'Installation command for Ploi Core';
protected $versionChecker;
protected $installationFile = 'app/installation';
public function __construct()
{
parent::__construct();
$this->versionChecker = (new VersionChecker)->getVersions();
}
public function handle()
{
$this->intro();
$this->isInstalled();
$this->checkApplicationKey();
$this->checkDatabaseConnection();
$this->runDatabaseMigrations();
$this->checkCredentials();
$this->askAboutAdministrationAccount();
$this->askAboutDefaultPackages();
$this->checkApplicationUrl();
$this->createInstallationFile();
$this->info('Succes! Installation has finished.');
$this->info('Visit your platform at ' . env('APP_URL'));
}
protected function askAboutAdministrationAccount()
{
$this->info('Let\'s start by setting up your administration account.');
$name = $this->ask('What is your name', $this->company['user_name']);
$email = $this->ask('What is your e-mail address', $this->company['email']);
$password = $this->secret('What password do you desire');
$check = User::where('email', $email)->count();
if ($check) {
$this->line('');
$this->comment('This user is already present in your system, please refresh your database or use different credentials.');
$this->comment('Aborting installation..');
exit();
}
User::forceCreate([
'name' => $name,
'email' => $email,
'password' => $password,
'role' => User::ADMIN
]);
}
protected function askAboutDefaultPackages()
{
$basicPackages = $this->confirm(
'Do you want to create the basic packages which you can edit later?',
true
);
if (!$basicPackages) {
return false;
}
Package::create([
'name' => 'Basic',
'maximum_sites' => 5,
]);
Package::create([
'name' => 'Professional',
'maximum_sites' => 5,
]);
Package::create([
'name' => 'Unlimited',
'maximum_sites' => 0,
]);
}
protected function getCompany($ploiCoreKey, $token)
{
$response = Http::withHeaders([
'Accept' => 'application/json',
'Content-Type' => 'application/json',
'X-Ploi-Core-Key' => $ploiCoreKey
])
->withToken($token)
->get((new Ploi)->url . 'ping');
if (!$response->ok() || !$response->json()) {
return false;
}
return $response->json();
}
protected function getInstallationPayload()
{
return [
'installed_at' => now()
];
}
protected function intro()
{
$this->info('*---------------------------------------------------------------------------*');
$this->line('Ploi Core Installation');
$this->line('Ploi Core version: ' . $this->versionChecker->currentVersion);
$this->line('Ploi Core remote: ' . $this->versionChecker->remoteVersion);
$this->line('Laravel version: ' . app()->version());
$this->line('PHP version: ' . trim(phpversion()));
$this->line(' ');
$this->line('Website: https://ploi-core.io');
$this->line('E-mail: core@ploi.io');
$this->line('Terms of service: https://ploi-core.io/terms');
$this->info('*---------------------------------------------------------------------------*');
$this->line('');
}
protected function isInstalled()
{
if (file_exists(storage_path($this->installationFile))) {
$this->line('');
$this->comment('Ploi Core has already been installed before.');
$this->comment('If you still want to start installation, remove this file to continue: ./storage/' . $this->installationFile);
$this->comment('Aborting installation..');
exit();
}
return false;
}
protected function checkApplicationKey(): void
{
if (!config('app.key')) {
$this->call('key:generate');
}
$this->info('Application key has been set');
}
protected function checkApplicationUrl()
{
// Ask about URL
$url = $this->ask('What URL is this platform using?', env('APP_URL'));
$this->writeToEnvironmentFile('APP_URL', $url);
}
protected function createInstallationFile()
{
file_put_contents(storage_path($this->installationFile), json_encode($this->getInstallationPayload(), JSON_PRETTY_PRINT));
}
protected function createDatabaseCredentials(): bool
{
$storeCredentials = $this->confirm(
'Unable to connect to your database. Would you like to enter your credentials now?',
true
);
if (!$storeCredentials) {
return false;
}
$connection = $this->choice('Type', ['mysql', 'pgsql'], 0);
$variables = [
'DB_CONNECTION' => $connection,
'DB_HOST' => $this->anticipate(
'Host',
['127.0.0.1', 'localhost'],
config("database.connections.{$connection}.host", '127.0.0.1')
),
'DB_PORT' => $this->ask(
'Port',
config("database.connections.{$connection}.port", '3306')
),
'DB_DATABASE' => $this->ask(
'Database',
config("database.connections.{$connection}.database")
),
'DB_USERNAME' => $this->ask(
'Username',
config("database.connections.{$connection}.username")
),
'DB_PASSWORD' => $this->secret(
'Password',
config("database.connections.{$connection}.password")
),
];
$this->persistVariables($variables);
return true;
}
protected function checkCredentials()
{
do {
$ploiApiToken = $this->ask('Enter the Ploi API token', env('PLOI_TOKEN'));
} while (empty($ploiApiToken));
do {
$ploiCoreKey = $this->ask('Enter the Ploi Core key', env('PLOI_CORE_TOKEN'));
} while (empty($ploiCoreKey));
$this->company = $this->getCompany($ploiCoreKey, $ploiApiToken);
if (!$this->company) {
$this->error('Could not authenticate with ploi.io, please retry by running this command again.');
exit();
}
$this->writeToEnvironmentFile('PLOI_TOKEN', $ploiApiToken);
$this->writeToEnvironmentFile('PLOI_CORE_TOKEN', $ploiCoreKey);
$name = $this->ask('What is the name of your company? (Press enter to keep the name here)', $this->company['name']);
$this->writeToEnvironmentFile('APP_NAME', $name);
setting(['name' => $name]);
}
protected function runDatabaseMigrations()
{
$this->info('Running database migrations..');
$this->call('migrate');
$this->info('Database migrations successful');
}
protected function checkDatabaseConnection(): void
{
try {
DB::connection()->getPdo();
$this->info('Database connection successful.');
} catch (Exception $e) {
try {
if (!$this->createDatabaseCredentials()) {
$this->error('A database connection could not be established. Please update your configuration and try again.');
$this->printDatabaseConfig();
exit();
}
} catch (RuntimeException $e) {
$this->error('Failed to persist environment configuration.');
exit();
}
$this->checkDatabaseConnection();
}
}
protected function printDatabaseConfig(): void
{
$connection = config('database.default');
$this->line('');
$this->info('Database Configuration:');
$this->line("- Connection: {$connection}");
$this->line('- Host: ' . config("database.connections.{$connection}.host"));
$this->line('- Port: ' . config("database.connections.{$connection}.port"));
$this->line('- Database: ' . config("database.connections.{$connection}.database"));
$this->line('- Username: ' . config("database.connections.{$connection}.username"));
$this->line('- Password: ' . config("database.connections.{$connection}.password"));
}
protected function persistVariables(array $connectionData): void
{
$connection = $connectionData['DB_CONNECTION'];
$configMap = [
'DB_CONNECTION' => "database.default",
'DB_HOST' => "database.connections.{$connection}.host",
'DB_PORT' => "database.connections.{$connection}.port",
'DB_DATABASE' => "database.connections.{$connection}.database",
'DB_USERNAME' => "database.connections.{$connection}.username",
'DB_PASSWORD' => "database.connections.{$connection}.password",
];
foreach ($connectionData as $envKey => $value) {
$this->writeToEnvironmentFile($envKey, $value);
$this->writeToConfig($configMap[$envKey], $value);
}
DB::purge($this->laravel['config']['database.default']);
}
protected function writeToEnvironmentFile(string $key, ?string $value): void
{
file_put_contents($this->laravel->environmentFilePath(), preg_replace(
$this->keyReplacementPattern($key),
"{$key}=\"{$value}\"",
file_get_contents($this->laravel->environmentFilePath())
));
if (!$this->checkEnvValuePresent($key, $value)) {
throw new RuntimeException("Failed to persist environment variable value. {$key}={$value}");
}
}
protected function checkEnvValuePresent(string $key, ?string $value): bool
{
$envContents = file_get_contents($this->laravel->environmentFilePath());
$needle = "{$key}=\"{$value}\"";
return Str::contains($envContents, $needle);
}
protected function keyReplacementPattern(string $key): string
{
return "/^{$key}.*/m";
}
protected function writeToConfig(string $key, ?string $value): void
{
$this->laravel['config'][$key] = $value;
}
}

View File

@@ -0,0 +1,56 @@
<?php
namespace App\Console\Commands\Core;
use App\Services\Ploi\Ploi;
use Illuminate\Console\Command;
class Synchronize extends Command
{
protected $signature = 'core:synchronize';
protected $description = 'Synchronze data';
public function handle()
{
$ploi = new Ploi(config('services.ploi.token'));
$data = collect($ploi->user()->serverProviders()->getData());
foreach ($data as $apiProvider) {
$provider = \App\Models\Provider::where('ploi_id', $apiProvider->id)->first();
if (!$provider) {
$provider = \App\Models\Provider::create([
'ploi_id' => $apiProvider->id,
'label' => $apiProvider->label,
'name' => $apiProvider->label
]);
}
// Synchronize the plans
foreach ($apiProvider->provider->plans as $apiPlan) {
$planCheck = $provider->plans()->where('plan_id', $apiPlan->id)->count();
if (!$planCheck) {
$provider->plans()->create([
'plan_id' => $apiPlan->id,
'label' => $apiPlan->name
]);
}
}
// Synchronize the regions
foreach ($apiProvider->provider->regions as $apiRegion) {
$regionCheck = $provider->regions()->where('region_id', $apiRegion->id)->count();
if (!$regionCheck) {
$provider->regions()->create([
'region_id' => $apiRegion->id,
'label' => $apiRegion->name
]);
}
}
}
}
}

24
app/Console/Kernel.php Normal file
View File

@@ -0,0 +1,24 @@
<?php
namespace App\Console;
use App\Jobs\Core\Ping;
use App\Console\Commands\Core\Install;
use App\Console\Commands\Core\Synchronize;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
class Kernel extends ConsoleKernel
{
protected $commands = [
Install::class,
Synchronize::class,
];
protected function schedule(Schedule $schedule)
{
$schedule->call(function () {
dispatch(new Ping())->delay(now()->addMinutes(rand(1, 30)));
})->dailyAt('02:00');
}
}

View File

@@ -0,0 +1,67 @@
<?php
namespace App\Exceptions;
use Throwable;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
class Handler extends ExceptionHandler
{
/**
* A list of the exception types that are not reported.
*
* @var array
*/
protected $dontReport = [
//
];
/**
* A list of the inputs that are never flashed for validation exceptions.
*
* @var array
*/
protected $dontFlash = [
'password',
'password_confirmation',
];
/**
* Report or log an exception.
*
* @param \Throwable $exception
* @return void
*
* @throws \Exception
*/
public function report(Throwable $exception)
{
parent::report($exception);
}
/**
* Render an exception into an HTTP response.
*
* @param \Illuminate\Http\Request $request
* @param \Throwable $exception
* @return \Symfony\Component\HttpFoundation\Response
*
* @throws \Throwable
*/
public function render($request, Throwable $exception)
{
$response = parent::render($request, $exception);
if (in_array($response->status(), [404, 403])) {
\Route::any($request->path(), function () use ($exception, $request) {
return parent::render($request, $exception);
})->middleware('web');
return inertia()->render('Errors/' . $response->status(), ['status' => $response->status()])
->toResponse($request)
->setStatusCode($response->status());
}
return $response;
}
}

18
app/Helpers/Languages.php Normal file
View File

@@ -0,0 +1,18 @@
<?php
if (!function_exists('languages')) {
function languages()
{
$languages = [
'en'
];
foreach (glob(base_path('resources/lang') . '/*.json') as $file) {
$file = str_replace('.json', null, $file);
$file = str_replace(base_path('resources/lang/'), null, $file);
$languages[] = $file;
}
return $languages;
}
}

37
app/Helpers/Setting.php Normal file
View File

@@ -0,0 +1,37 @@
<?php
use Illuminate\Support\Arr;
if (!function_exists('setting')) {
/**
* @param null $key
* @param null $default
* @return array|ArrayAccess|bool|\Illuminate\Contracts\Foundation\Application|mixed
*/
function setting($key = null, $default = null)
{
if (is_array($key)) {
\App\Models\Setting::updateOrCreate([
'key' => key($key)
], [
'value' => Arr::first($key)
]);
try {
cache()->forget('core.settings');
} catch (Exception $e) {
}
return true;
}
$value = Arr::get(app('settings'), $key, $default);
// Boolean casting
if ($value === "0" || $value === "1") {
return (bool) $value;
}
return $value;
}
}

View File

@@ -0,0 +1,31 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Models\Site;
use App\Models\User;
use App\Models\Server;
use App\Models\SystemLog;
use App\Http\Controllers\Controller;
class DashboardController extends Controller
{
public function index()
{
return inertia('Admin/Dashboard', [
'servers' => Server::count(),
'sites' => Site::count(),
'users' => User::count(),
'logs' => SystemLog::latest()->limit(10)->with('model')->get()
->map(function (SystemLog $systemLog) {
return [
'title' => __($systemLog->title, [
'site' => $systemLog->model->domain ?? '-Unknown-'
]),
'description' => __($systemLog->description),
'created_at_human' => $systemLog->created_at->diffForHumans()
];
})
]);
}
}

View File

@@ -0,0 +1,13 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
class DocumentationController extends Controller
{
public function index()
{
return inertia('Admin/Documentation/Index');
}
}

View File

@@ -0,0 +1,61 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Models\Package;
use App\Http\Controllers\Controller;
use App\Http\Requests\Admin\PackageRequest;
class PackageController extends Controller
{
public function index()
{
$packages = Package::withCount('users')->latest()->get();
return inertia('Admin/Packages/Index', [
'packages' => $packages,
]);
}
public function create()
{
return inertia('Admin/Packages/Create');
}
public function store(PackageRequest $request)
{
Package::create($request->all());
return redirect()->route('admin.packages.index')->with('success', 'Package has been created');
}
public function show($id)
{
//
}
public function edit($id)
{
return inertia('Admin/Packages/Edit', [
'package' => Package::findOrFail($id)
]);
}
public function update(PackageRequest $request, $id)
{
$package = Package::findOrFail($id);
$package->update($request->validated());
return redirect()->route('admin.packages.index')->with('success', 'Package has been updated');
}
public function destroy($id)
{
$package = Package::findOrFail($id);
$package->delete();
return redirect()->route('admin.packages.index')->with('success', 'Package has been removed');
}
}

View File

@@ -0,0 +1,10 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
class ProviderController extends Controller
{
//
}

View File

@@ -0,0 +1,10 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
class ProviderPlanController extends Controller
{
//
}

View File

@@ -0,0 +1,10 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
class ProviderRegionController extends Controller
{
//
}

View File

@@ -0,0 +1,75 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Requests\Admin\ServerAttachRequest;
use App\Models\Server;
use App\Models\User;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
class ServerController extends Controller
{
public function edit($id)
{
$server = Server::findOrFail($id);
$users = $server->users()->select('id', 'name', 'email')->get()->map(function($user){
return [
'id' => $user->id,
'name' => $user->name,
'email' => $user->email
];
});
return inertia('Admin/Services/Server/Edit', [
'server' => $server,
'users' => $users
]);
}
public function update(Request $request, $id)
{
$server = Server::findOrFail($id);
$server->update($request->all());
return redirect()->route('admin.services.index')->with('success', __('Server has been updated'));
}
public function destroy($id)
{
$server = Server::findOrFail($id);
$server->delete();
return redirect()->route('admin.services.index')->with('success', __('Server has been deleted'));
}
public function attach(ServerAttachRequest $request, $id)
{
/* @var $server \App\Models\Server */
$server = Server::findOrFail($id);
$user = User::where('email', $request->input('email'))->first();
if ($server->users()->where('email', $request->input('email'))->count()) {
return redirect()->back()->withErrors([
'email' => __('This user is already attached to this server')
]);
}
$server->users()->attach($user);
return redirect()->route('admin.services.servers.edit', $id)->with('success', __('User has been attached'));
}
public function detach($id, $userId)
{
$server = Server::findOrFail($id);
$server->users()->detach($userId);
return redirect()->route('admin.services.servers.edit', $server->id)->with('success', __('User has been detached'));
}
}

View File

@@ -0,0 +1,18 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Models\Site;
use App\Models\Server;
use App\Http\Controllers\Controller;
class ServiceController extends Controller
{
public function index()
{
return inertia('Admin/Services/Index', [
'servers' => Server::withCount('sites')->latest()->paginate(5, ['*'], 'servers_per_page'),
'sites' => Site::with('server:id,name')->latest()->paginate(5, ['*'], 'sites_per_page'),
]);
}
}

View File

@@ -0,0 +1,49 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Models\Package;
use App\Http\Controllers\Controller;
use App\Http\Requests\Admin\SettingRequest;
class SettingController extends Controller
{
public function index()
{
$settings = [
'name' => setting('name'),
'email' => setting('email'),
'support_emails' => setting('support_emails'),
'support' => setting('support'),
'documentation' => setting('documentation'),
'allow_registration' => setting('allow_registration'),
'default_package' => setting('default_package'),
];
$packages = Package::pluck('name', 'id');
return inertia('Admin/Settings', [
'company_settings' => $settings,
'packages' => $packages
]);
}
public function update(SettingRequest $request)
{
foreach ($request->only([
'name',
'email',
'support',
'support_emails',
'allow_registration',
'documentation',
'default_package'
]) as $key => $value) {
setting([$key => $value]);
}
cache()->forget('core.settings');
return redirect()->route('admin.settings')->with('success', __('Settings have been updated'));
}
}

View File

@@ -0,0 +1,76 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Requests\Admin\ServerAttachRequest;
use App\Models\Server;
use App\Models\Site;
use App\Models\User;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
class SiteController extends Controller
{
public function edit($id)
{
$site = Site::findOrFail($id);
$users = $site->users()->select('id', 'name', 'email')->get()->map(function($user){
return [
'id' => $user->id,
'name' => $user->name,
'email' => $user->email
];
});
return inertia('Admin/Services/Site/Edit', [
'site' => $site,
'users' => $users
]);
}
public function update(Request $request, $id)
{
$site = Site::findOrFail($id);
$site->update($request->all());
return redirect()->route('admin.services.index')->with('success', __('Server has been updated'));
}
public function destroy($id)
{
$site = Site::findOrFail($id);
$site->delete();
return redirect()->route('admin.services.index')->with('success', __('Server has been deleted'));
}
public function attach(ServerAttachRequest $request, $id)
{
/* @var $site \App\Models\Server */
$site = Site::findOrFail($id);
$user = User::where('email', $request->input('email'))->first();
if ($site->users()->where('email', $request->input('email'))->count()) {
return redirect()->back()->withErrors([
'email' => __('This user is already attached to this site')
]);
}
$site->users()->attach($user);
return redirect()->route('admin.services.sites.edit', $id)->with('success', __('User has been attached'));
}
public function detach($id, $userId)
{
$site = Site::findOrFail($id);
$site->users()->detach($userId);
return redirect()->route('admin.services.sites.edit', $site->id)->with('success', __('User has been detached'));
}
}

View File

@@ -0,0 +1,13 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
class StatusController extends Controller
{
public function index()
{
return inertia('Admin/Status');
}
}

View File

@@ -0,0 +1,71 @@
<?php
namespace App\Http\Controllers\Admin;
use Illuminate\Http\Request;
use App\Models\SupportTicket;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Mail;
use App\Mail\Support\TicketRepliedToEmail;
use App\Http\Requests\SupportTicketReplyRequest;
class SupportController extends Controller
{
public function index()
{
$tickets = SupportTicket::whereIn('status', [
SupportTicket::STATUS_CUSTOMER_REPLY,
SupportTicket::STATUS_OPEN
])
->withCount('replies')
->latest()
->get();
return inertia('Admin/Support/Index', [
'tickets' => $tickets
]);
}
public function show($id)
{
$ticket = SupportTicket::with('user:id,name,email')->findOrFail($id);
$replies = $ticket->replies()
->with('user:id,name,email')
->get();
return inertia('Admin/Support/Show', [
'ticket' => $ticket,
'replies' => $replies
]);
}
public function reply(SupportTicketReplyRequest $request, $id)
{
$ticket = SupportTicket::findOrFail($id);
$ticket->status = SupportTicket::STATUS_SUPPORT_REPLY;
$ticket->save();
$reply = $ticket->replies()->create([
'content' => $request->input('content')
]);
$reply->user_id = $request->user()->id;
$reply->save();
Mail::to($ticket->user)->send(new TicketRepliedToEmail($ticket));
return redirect()->route('admin.support.index');
}
public function close(Request $request, $id)
{
$ticket = SupportTicket::findOrFail($id);
$ticket->status = SupportTicket::STATUS_CLOSED;
$ticket->save();
return redirect()->route('admin.support.index')->with('success', __('Support ticket has been closed'));
}
}

View File

@@ -0,0 +1,43 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Models\Server;
use App\Services\Ploi\Ploi;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
class SynchronizeServerController extends Controller
{
public function index()
{
if (config('app.demo')) {
return redirect('/')->with('info', __('This feature is not available in demo mode.'));
}
$ploi = new Ploi(config('services.ploi.token'));
$availableServers = $ploi->synchronize()->servers()->getData();
$currentServers = Server::whereNotIn('id', array_keys((array)$availableServers))->get();
return inertia('Admin/Services/Servers', [
'availableServers' => $availableServers,
'currentServers' => $currentServers
]);
}
public function synchronizeServer(Request $request)
{
Server::updateOrCreate([
'ploi_id' => $request->input('id')
], [
'status' => $request->input('status'),
'name' => $request->input('name'),
'ip' => $request->input('ip_address'),
'ssh_port' => $request->input('ssh_port', 22),
'internal_ip' => $request->input('internal_ip'),
'available_php_versions' => $request->input('installed_php_versions')
]);
}
}

View File

@@ -0,0 +1,13 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
class SynchronizeSiteController extends Controller
{
public function index()
{
return inertia('Admin/Services/Sites');
}
}

View File

@@ -0,0 +1,35 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Jobs\Core\UpdateSystem;
use App\Services\VersionChecker;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
class SystemController extends Controller
{
public function index()
{
if (config('app.demo')) {
return redirect('/')->with('info', __('This feature is not available in demo mode.'));
}
$version = (new VersionChecker)->getVersions();
return inertia('Admin/System', [
'version' => [
'out_of_date' => $version->isOutOfDate(),
'current' => $version->currentVersion,
'remote' => $version->remoteVersion
]
]);
}
public function update(Request $request)
{
dispatch(new UpdateSystem);
return redirect()->route('admin.system')->with('success', __('System update has been dispatched, this could take around 2/3 minutes for it to complete'));
}
}

View File

@@ -0,0 +1,96 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Models\User;
use App\Models\Package;
use App\Http\Controllers\Controller;
use App\Http\Requests\Admin\UserRequest;
class UserController extends Controller
{
public function index()
{
$users = User::with('package:id,name')
->when(request()->input('search'), function ($query, $value) {
return $query->where('name', 'like', '%' . $value . '%')->orWhere('email', 'like', '%' . $value . '%');
})
->latest()
->paginate(5);
return inertia('Admin/Users/Index', [
'filters' => request()->all('search'),
'users' => $users
]);
}
public function create()
{
$packages = Package::orderBy('name')->pluck('name', 'id');
return inertia('Admin/Users/Create', [
'packages' => $packages,
'languages' => languages(),
'defaultPackage' => (string)setting('default_package')
]);
}
public function store(UserRequest $request)
{
$user = User::create($request->all());
if ($request->input('role') === User::ADMIN) {
$user->role = User::ADMIN;
$user->save();
}
if ($package = $request->input('package')) {
$user->package_id = $package;
$user->save();
}
return redirect()->route('admin.users.index')->with('success', 'User ' . $user->name . ' has been created');
}
public function show($id)
{
//
}
public function edit($id)
{
$packages = Package::orderBy('name')->pluck('name', 'id');
return inertia('Admin/Users/Edit', [
'user' => User::findOrFail($id),
'packages' => $packages,
'languages' => languages()
]);
}
public function update(UserRequest $request, $id)
{
$user = User::findOrFail($id);
$user->update($request->all());
if ($request->input('role') !== $user->role) {
$user->role = $request->input('role');
$user->save();
}
if ($request->input('package') !== $user->package_id) {
$user->package_id = $request->input('package');
$user->save();
}
return redirect()->route('admin.users.index')->with('success', 'User ' . $user->name . ' has been updated');
}
public function destroy($id)
{
User::findOrFail($id)->delete();
return redirect()->route('admin.users.index')->with('success', 'User has been deleted');
}
}

View File

@@ -0,0 +1,40 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use Illuminate\Foundation\Auth\ConfirmsPasswords;
class ConfirmPasswordController extends Controller
{
/*
|--------------------------------------------------------------------------
| Confirm Password Controller
|--------------------------------------------------------------------------
|
| This controller is responsible for handling password confirmations and
| uses a simple trait to include the behavior. You're free to explore
| this trait and override any functions that require customization.
|
*/
use ConfirmsPasswords;
/**
* Where to redirect users when the intended url fails.
*
* @var string
*/
protected $redirectTo = RouteServiceProvider::HOME;
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('auth');
}
}

View File

@@ -0,0 +1,42 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Models\User;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
class CreatePasswordController extends Controller
{
public function index()
{
$user = User::where('email', request('email'))
->whereNull('password')
->firstOrFail();
return inertia('Auth/PasswordCreation', [
'email' => request('email')
]);
}
public function start(Request $request)
{
$this->validate($request, [
'password' => ['required', 'string', 'min:8', 'confirmed'],
]);
$user = User::where('email', $request->input('email'))->firstOrFail();
$user->password = $request->input('password');
$user->save();
$user->systemLogs()->create([
'title' => 'Password creation',
'description' => 'You have set a password for your account'
]);
auth()->login($user);
return redirect()->route('dashboard');
}
}

View File

@@ -0,0 +1,25 @@
<?php
namespace App\Http\Controllers\Auth;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\SendsPasswordResetEmails;
class ForgotPasswordController extends Controller
{
use SendsPasswordResetEmails;
public function showLinkRequestForm()
{
return inertia('Auth/Email');
}
protected function sendResetLinkResponse(Request $request, $response)
{
return $request->wantsJson()
? new JsonResponse(['message' => trans($response)], 200)
: back()->with('success', trans($response));
}
}

View File

@@ -0,0 +1,45 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
class LoginController extends Controller
{
/*
|--------------------------------------------------------------------------
| Login Controller
|--------------------------------------------------------------------------
|
| This controller handles authenticating users for the application and
| redirecting them to your home screen. The controller uses a trait
| to conveniently provide its functionality to your applications.
|
*/
use AuthenticatesUsers;
/**
* Where to redirect users after login.
*
* @var string
*/
protected $redirectTo = RouteServiceProvider::HOME;
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('guest')->except('logout');
}
public function showLoginForm()
{
return inertia('Auth/Login');
}
}

View File

@@ -0,0 +1,53 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Models\User;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use Illuminate\Support\Facades\Validator;
use Illuminate\Foundation\Auth\RegistersUsers;
class RegisterController extends Controller
{
use RegistersUsers;
protected $redirectTo = RouteServiceProvider::HOME;
public function __construct()
{
$this->middleware('guest');
}
public function showRegistrationForm()
{
return inertia('Auth/Register');
}
protected function validator(array $data)
{
return Validator::make($data, [
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
'password' => ['required', 'string', 'min:8', 'confirmed'],
]);
}
protected function create(array $data)
{
return User::create([
'name' => $data['name'],
'email' => $data['email'],
'password' => $data['password'],
]);
}
protected function registered(Request $request, $user)
{
if ($defaultPackage = setting('default_package')) {
$user->package_id = $defaultPackage;
$user->save();
}
}
}

View File

@@ -0,0 +1,44 @@
<?php
namespace App\Http\Controllers\Auth;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use Illuminate\Foundation\Auth\ResetsPasswords;
class ResetPasswordController extends Controller
{
/*
|--------------------------------------------------------------------------
| Password Reset Controller
|--------------------------------------------------------------------------
|
| This controller is responsible for handling password reset requests
| and uses a simple trait to include this behavior. You're free to
| explore this trait and override any methods you wish to tweak.
|
*/
use ResetsPasswords;
/**
* Where to redirect users after resetting their password.
*
* @var string
*/
protected $redirectTo = RouteServiceProvider::HOME;
public function showResetForm(Request $request, $token = null)
{
return inertia('Auth/Reset', [
'token' => $token,
'email' => $request->email
]);
}
protected function setUserPassword($user, $password)
{
$user->password = $password;
}
}

View File

@@ -0,0 +1,42 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use Illuminate\Foundation\Auth\VerifiesEmails;
class VerificationController extends Controller
{
/*
|--------------------------------------------------------------------------
| Email Verification Controller
|--------------------------------------------------------------------------
|
| This controller is responsible for handling email verification for any
| user that recently registered with the application. Emails may also
| be re-sent if the user didn't receive the original email message.
|
*/
use VerifiesEmails;
/**
* Where to redirect users after verification.
*
* @var string
*/
protected $redirectTo = RouteServiceProvider::HOME;
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('auth');
$this->middleware('signed')->only('verify');
$this->middleware('throttle:6,1')->only('verify', 'resend');
}
}

View File

@@ -0,0 +1,12 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
class Controller extends BaseController
{
use ValidatesRequests, AuthorizesRequests;
}

View File

@@ -0,0 +1,40 @@
<?php
namespace App\Http\Controllers;
use App\Models\SystemLog;
class DashboardController extends Controller
{
public function index()
{
$package = auth()->user()->package;
$logs = auth()->user()->systemLogs()
->with('model')
->latest()
->limit(10)
->get()
->map(function (SystemLog $log) {
return [
'title' => __($log->title, [
$log->getKeyFromType() => $log->getTitleDescription(),
]),
'description' => __($log->description, [
$log->getKeyFromType() => $log->getTitleDescription()
]),
'created_at' => $log->created_at,
'created_at_human' => $log->created_at->diffForHumans()
];
});
return inertia('Dashboard/Index', [
'sites' => auth()->user()->sites()->count(),
'servers' => auth()->user()->servers()->count(),
'package' => [
'name' => $package->name ?? 'None'
],
'logs' => $logs,
]);
}
}

View File

@@ -0,0 +1,18 @@
<?php
namespace App\Http\Controllers;
use App\Models\DocumentationItem;
use App\Http\Resources\DocumentationItemResource;
class DocumentationController extends Controller
{
public function index()
{
$documentationItems = DocumentationItem::latest()->paginate();
return inertia('Documentation/Index', [
'items' => DocumentationItemResource::collection($documentationItems)
]);
}
}

View File

@@ -0,0 +1,11 @@
<?php
namespace App\Http\Controllers;
class PageController extends Controller
{
public function installationIncomplete()
{
return inertia('Core/InstallationIncomplete');
}
}

View File

@@ -0,0 +1,59 @@
<?php
namespace App\Http\Controllers\Profile;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Hash;
use App\Http\Requests\UserProfileRequest;
use App\Http\Resources\UserProfileResource;
class ProfileController extends Controller
{
public function index()
{
return inertia('Profile/Index', [
'profile' => new UserProfileResource(auth()->user()),
'languages' => languages()
]);
}
public function update(UserProfileRequest $request)
{
$request->user()->update($request->validated());
return redirect()->route('profile.index')->with('success', __('Profile saved'));
}
public function toggleTheme(Request $request)
{
$mode = 'light';
if ($request->user()->theme === 'light') {
$mode = 'dark';
}
$request->user()->theme = $mode;
$request->user()->save();
return $mode;
}
public function requestFtpPassword(Request $request)
{
$this->validate($request, ['password' => 'required|string']);
if (!Hash::check($request->input('password'), $request->user()->password)) {
return response([
'message' => 'The given data was invalid',
'errors' => [
'password' => [
trans('auth.failed')
]
]
], 422);
}
return ['ftp_password' => decrypt($request->user()->ftp_password)];
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace App\Http\Controllers\Profile;
use App\Http\Controllers\Controller;
use App\Rules\MatchOldPassword;
use Illuminate\Http\Request;
class ProfilePasswordController extends Controller
{
public function index()
{
return inertia('Profile/Security');
}
public function update(Request $request)
{
$request->validate([
'current_password' => ['required', new MatchOldPassword],
'password' => ['required', 'string', 'min:8', 'confirmed'],
]);
$request->user()->update(['password' => $request->input('password')]);
return redirect()->route('profile.security.index')->with('success', __('Your password has been updated'));
}
}

View File

@@ -0,0 +1,34 @@
<?php
namespace App\Http\Controllers\Profile;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Validation\Rule;
class ProfileSettingController extends Controller
{
public function index()
{
return inertia('Profile/Settings', [
'profile' => [
'theme' => auth()->user()->theme
]
]);
}
public function update(Request $request)
{
$this->validate($request, [
'theme' => [
'required',
'string',
Rule::in(['light', 'dark', 'auto'])
]
]);
$request->user()->update(['theme' => $request->input('theme')]);
return redirect()->route('profile.settings.index')->with('success', __('Instellingen zijn aangepast'));
}
}

View File

@@ -0,0 +1,96 @@
<?php
namespace App\Http\Controllers;
use App\Models\Site;
use App\Models\Server;
use Illuminate\Http\Request;
use App\Models\SupportTicket;
class SearchController extends Controller
{
public function handle(Request $request)
{
$collected = collect();
// Search Servers
$servers = $request->user()->servers()
->where(function ($query) use ($request) {
return $query
->where('name', 'like', '%' . $request->input('query') . '%')
->orWhere('ip', 'like', '%' . $request->input('query') . '%');
})
->select(['id', 'name', 'ip'])
->get();
$mappedServers = $servers->map(function (Server $server) {
return [
'name' => $server->name,
'ip' => $server->ip,
'route' => route('servers.show', $server->id),
];
});
if ($serversCount = $mappedServers->count()) {
$collected[] = [
'total' => $serversCount,
'results' => $mappedServers,
'label' => __('Servers')
];
}
$sites = Site::query()
->whereHas('users', function ($query) {
return $query->where('user_service.user_id', auth()->id());
})
->select(['id', 'domain'])
->orderBy('domain', 'ASC')
->where(function ($query) use ($request) {
return $query
->where('domain', 'like', '%' . $request->input('query') . '%');
})
->get();
$mappedSites = $sites->map(function (Site $site) {
return [
'name' => $site->domain,
'route' => route('sites.show', $site->id),
];
});
if ($sitesCount = $mappedSites->count()) {
$collected[] = [
'total' => $sitesCount,
'results' => $mappedSites,
'label' => __('Sites')
];
}
$tickets = $request->user()->supportTickets()
->where(function ($query) use ($request) {
return $query
->where('title', 'like', '%' . $request->input('query') . '%');
})
->get();
$mappedTickets = $tickets->map(function (SupportTicket $ticket) {
return [
'name' => $ticket->title,
'route' => route('support.show', $ticket->id),
];
});
if ($ticketsCount = $mappedTickets->count()) {
$collected[] = [
'total' => $ticketsCount,
'results' => $mappedTickets,
'label' => __('Support requests')
];
}
return [
'total' => $collected->count(),
'results' => $collected,
];
}
}

View File

@@ -0,0 +1,26 @@
<?php
namespace App\Http\Controllers;
use App\Models\Server;
use Illuminate\Http\Request;
use App\Http\Resources\ServerResource;
class ServerController extends Controller
{
public function index()
{
$servers = auth()->user()->servers()->latest()->paginate();
return inertia('Servers/Index', [
'servers' => ServerResource::collection($servers)
]);
}
public function store(Request $request)
{
abort_if(!$request->user()->can('create', Server::class), 403);
return 'test';
}
}

View File

@@ -0,0 +1,45 @@
<?php
namespace App\Http\Controllers;
use App\Models\Site;
use App\Jobs\Apps\InstallApp;
use App\Jobs\Apps\UninstallApp;
use App\Http\Requests\SiteAppRequest;
class SiteAppController extends Controller
{
public function index($id)
{
$site = auth()->user()->sites()->findOrFail($id);
return inertia('Sites/Apps', [
'site' => $site,
]);
}
public function store(SiteAppRequest $request, $id)
{
$site = $request->user()->sites()->findOrFail($id);
dispatch(new InstallApp(
$site,
$request->input('type', Site::PROJECT_WORDPRESS),
$request->input('options', [])
));
$site->project = $request->input('type', Site::PROJECT_WORDPRESS);
$site->save();
return redirect()->route('sites.apps.index', $site->id)->with('success', __('Application is being installed'));
}
public function destroy($id)
{
$site = auth()->user()->sites()->findOrFail($id);
dispatch(new UninstallApp($site));
return redirect()->route('sites.show', $site->id)->with('success', __('Application is being uninstalled'));
}
}

View File

@@ -0,0 +1,50 @@
<?php
namespace App\Http\Controllers;
use App\Jobs\Certificates\CreateCertificate;
use App\Jobs\Certificates\DeleteCertificate;
use App\Http\Requests\SiteCertificateRequest;
use App\Http\Resources\SiteCertificateResource;
class SiteCertificateController extends Controller
{
public function index($id)
{
$site = auth()->user()->sites()->findOrFail($id);
return inertia('Sites/Certificates', [
'site' => $site,
'certificates' => SiteCertificateResource::collection($site->certificates()->latest()->paginate())
]);
}
public function store(SiteCertificateRequest $request, $id)
{
$site = auth()->user()->sites()->findOrFail($id);
$certificate = $site->certificates()->create([
'domain' => $request->input('domain'),
]);
$certificate->server_id = $site->server_id;
$certificate->save();
dispatch(new CreateCertificate($certificate));
return redirect()->route('sites.certificates.index', $id)->with('success', __('Certificate has been created'));
}
public function destroy($id, $certificateId)
{
$site = auth()->user()->sites()->findOrFail($id);
$certificate = $site->certificates()->findOrFail($certificateId);
dispatch(new DeleteCertificate($site->server->ploi_id, $site->ploi_id, $certificate->ploi_id));
$certificate->delete();
return redirect()->route('sites.certificates.index', $id)->with('success', __('Certificate has been deleted'));
}
}

View File

@@ -0,0 +1,89 @@
<?php
namespace App\Http\Controllers;
use App\Models\Server;
use App\Jobs\Sites\CreateSite;
use App\Jobs\Sites\DeleteSite;
use App\Http\Requests\SiteRequest;
use Illuminate\Support\Facades\DB;
use App\Http\Resources\SiteResource;
class SiteController extends Controller
{
public function index()
{
$sites = auth()->user()
->sites()
->when(request('server'), function ($query, $value) {
return $query->where('server_id', $value);
})
->latest()
->paginate(10);
$availableServers = auth()->user()->servers()->pluck('name', 'id');
return inertia('Sites/Index', [
'sites' => SiteResource::collection($sites),
'availableServers' => $availableServers
]);
}
public function store(SiteRequest $request)
{
if ($serverId = $request->input('server_id')) {
$server = $request->user()->servers()->findOrFail($serverId);
} else {
$server = Server::query()
->doesntHave('users')
->withCount('sites')
->having('sites_count', '<', DB::raw('maximum_sites'))
->inRandomOrder()
->first();
}
if (!$server) {
return redirect()->back()->withErrors([
'domain' => __('It seems there is no free server room for this site to take place. Please get in touch with support to resolve this.')
]);
}
$site = $server->sites()->create($request->all());
$request->user()->sites()->save($site);
dispatch(new CreateSite($site));
$request->user()->systemLogs()->create([
'title' => 'New site :site created',
'description' => 'A new site has been created'
])->model()->associate($site)->save();
return redirect()->route('sites.index')->with('success', __('Your website is being created'));
}
public function show($id)
{
$site = auth()->user()->sites()->findOrFail($id);
if (!$site->isActive()) {
return redirect()->route('sites.index')->with('info', __('This site does not seem to be active, please wait for the process to finish'));
}
return inertia('Sites/Show', [
'site' => $site,
'ip_address' => $site->server->ip
]);
}
public function destroy($id)
{
$site = auth()->user()->sites()->findOrFail($id);
dispatch(new DeleteSite($site->server->ploi_id, $site->ploi_id));
$site->delete();
return redirect()->route('sites.index')->with('success', __('Your website is deleted'));
}
}

View File

@@ -0,0 +1,56 @@
<?php
namespace App\Http\Controllers;
use App\Jobs\Cronjobs\CreateCronjob;
use App\Jobs\Cronjobs\DeleteCronjob;
use App\Http\Requests\SiteCronjobRequest;
use App\Http\Resources\SiteCronjobResource;
class SiteCronjobController extends Controller
{
public function index($id)
{
$site = auth()->user()->sites()->findOrFail($id);
return inertia('Sites/Cronjobs', [
'site' => $site,
'cronjobs' => SiteCronjobResource::collection($site->cronjobs()->latest()->paginate())
]);
}
public function store(SiteCronjobRequest $request, $id)
{
$site = auth()->user()->sites()->findOrFail($id);
$cronjob = $site->cronjobs()->create([
'command' => $request->input('command'),
'frequency' => $request->input('frequency')
]);
$cronjob->server_id = $site->server_id;
$cronjob->save();
dispatch(new CreateCronjob($cronjob));
$request->user()->systemLogs()->create([
'title' => 'New cronjob created',
'description' => 'A new cronjob has been created'
])->model()->associate($cronjob)->save();
return redirect()->route('sites.cronjobs.index', $id)->with('success', __('New cronjob has been created'));
}
public function destroy($id, $cronjobId)
{
$site = auth()->user()->sites()->findOrFail($id);
$cronjob = $site->cronjobs()->findOrFail($cronjobId);
dispatch(new DeleteCronjob($site->server->ploi_id, $cronjob->ploi_id));
$cronjob->delete();
return redirect()->route('sites.cronjobs.index', $id)->with('success', __('Cronjob has been deleted'));
}
}

View File

@@ -0,0 +1,59 @@
<?php
namespace App\Http\Controllers;
use App\Jobs\Databases\CreateDatabase;
use App\Jobs\Databases\DeleteDatabase;
use App\Http\Requests\SiteDatabaseRequest;
use App\Http\Resources\SiteDatabaseResource;
class SiteDatabaseController extends Controller
{
public function index($id)
{
$site = auth()->user()->sites()->findOrFail($id);
return inertia('Sites/Databases', [
'site' => $site,
'databases' => SiteDatabaseResource::collection($site->databases()->latest()->paginate())
]);
}
public function store(SiteDatabaseRequest $request, $id)
{
$site = auth()->user()->sites()->findOrFail($id);
$database = $site->databases()->create([
'name' => $request->input('name')
]);
$database->users()->create([
'name' => $request->input('user_name'),
]);
$database->server_id = $site->server_id;
$database->save();
dispatch(new CreateDatabase($database, $request->input('password')));
$request->user()->systemLogs()->create([
'title' => 'New database :database created',
'description' => 'A new database has been created'
])->model()->associate($database)->save();
return redirect()->route('sites.databases.index', $id)->with('success', __('New database has been created'));
}
public function destroy($id, $databaseId)
{
$site = auth()->user()->sites()->findOrFail($id);
$database = $site->databases()->findOrFail($databaseId);
dispatch(new DeleteDatabase($site->server->ploi_id, $database->ploi_id));
$database->delete();
return redirect()->route('sites.databases.index', $id)->with('success', __('Database has been deleted'));
}
}

View File

@@ -0,0 +1,37 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class SiteDnsController extends Controller
{
public function index($id)
{
$site = auth()->user()->sites()->findOrFail($id);
return inertia('Sites/Dns', [
'site' => $site,
'records' => []
]);
}
public function store(Request $request)
{
//
}
public function update(Request $request, $id)
{
//
}
public function records($id)
{
}
public function destroy($id)
{
//
}
}

View File

@@ -0,0 +1,61 @@
<?php
namespace App\Http\Controllers;
use App\Jobs\Redirects\CreateRedirect;
use App\Jobs\Redirects\DeleteRedirect;
use App\Http\Requests\SiteRedirectRequest;
use App\Http\Resources\SiteRedirectResource;
class SiteRedirectController extends Controller
{
public function index($id)
{
$site = auth()->user()->sites()->findOrFail($id);
return inertia('Sites/Redirects', [
'site' => $site,
'redirects' => SiteRedirectResource::collection($site->redirects()->latest()->paginate())
]);
}
public function store(SiteRedirectRequest $request, $id)
{
$site = auth()->user()->sites()->findOrFail($id);
$redirect = $site->redirects()->create([
'redirect_from' => $request->input('redirect_from'),
'redirect_to' => $request->input('redirect_to'),
'type' => $request->input('type', 'redirect')
]);
$redirect->server_id = $site->server_id;
$redirect->save();
dispatch(new CreateRedirect($redirect));
$request->user()->systemLogs()->create([
'title' => 'New redirect :redirect created',
'description' => 'A new redirect has been created'
])->model()->associate($redirect)->save();
return redirect()->route('sites.redirects.index', $id)->with('success', __('New redirect has been created'));
}
public function destroy($id, $redirectId)
{
$site = auth()->user()->sites()->findOrFail($id);
$redirect = $site->redirects()->findOrFail($redirectId);
dispatch(new DeleteRedirect(
$site->server->ploi_id,
$site->ploi_id,
$redirect->ploi_id
));
$redirect->delete();
return redirect()->route('sites.redirects.index', $id)->with('success', __('Redirect has been deleted'));
}
}

View File

@@ -0,0 +1,44 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Jobs\Sites\ChangePhpVersion;
class SiteSettingController extends Controller
{
public function show($id)
{
$site = auth()->user()->sites()->findOrFail($id);
$availablePhpVersions = $site->server->available_php_versions;
return inertia('Sites/Settings', [
'site' => $site,
'available_php_versions' => $availablePhpVersions
]);
}
public function update(Request $request, $id)
{
$site = $request->user()->sites()->findOrFail($id);
$site->update($request->all());
return redirect()->route('sites.settings.show', $id)->with('success', __('Site settings have been updated'));
}
public function changePhpVersion(Request $request, $id)
{
$site = $request->user()->sites()->findOrFail($id);
dispatch(new ChangePhpVersion($site, $request->input('version')));
$request->user()->systemLogs()->create([
'title' => 'Site PHP version has changed',
'description' => 'PHP version changed for site :site'
])->model()->associate($site)->save();
return redirect()->route('sites.settings.show', $id)->with('success', __('Site PHP version is changing'));
}
}

View File

@@ -0,0 +1,85 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\SupportTicket;
use Illuminate\Support\Facades\Mail;
use App\Http\Requests\SupportTicketRequest;
use App\Http\Requests\SupportTicketReplyRequest;
use App\Mail\Support\NotifyEmailsAboutNewTicket;
class SupportController extends Controller
{
public function index()
{
return inertia('Support/Index', [
'tickets' => auth()->user()
->supportTickets()
->where('status', '!=', SupportTicket::STATUS_CLOSED)
->latest()
->paginate()
]);
}
public function indexClosed()
{
return inertia('Support/Closed', [
'tickets' => auth()->user()->supportTickets()->closed()->latest()->paginate()
]);
}
public function store(SupportTicketRequest $request)
{
$ticket = $request->user()->supportTickets()->create($request->validated());
if ($emails = setting('support_emails')) {
foreach (explode(',', $emails) as $email) {
Mail::to($email)->send(new NotifyEmailsAboutNewTicket($ticket));
}
}
return redirect()->route('support.index')->with('success', __('Thank you for submitting your support request.'));
}
public function show($id)
{
$ticket = auth()->user()->supportTickets()
->with('user:id,name,email')
->findOrFail($id);
return inertia('Support/Show', [
'ticket' => $ticket,
'replies' => $ticket->replies()->with('user:id,name,email')->oldest()->paginate()
]);
}
public function reply(SupportTicketReplyRequest $request, $id)
{
$ticket = $request->user()->supportTickets()
->findOrFail($id);
$ticket->status = SupportTicket::STATUS_CUSTOMER_REPLY;
$ticket->save();
$reply = $ticket->replies()->create([
'content' => $request->input('content')
]);
$reply->user_id = $request->user()->id;
$reply->save();
return redirect()->route('support.show', $id);
}
public function close(Request $request, $id)
{
$ticket = $request->user()->supportTickets()
->findOrFail($id);
$ticket->status = SupportTicket::STATUS_CLOSED;
$ticket->save();
return redirect()->route('support.index')->with('success', __('Support ticket has been closed'));
}
}

View File

@@ -0,0 +1,8 @@
<?php
namespace App\Http\Controllers;
class UserController extends Controller
{
//
}

72
app/Http/Kernel.php Normal file
View File

@@ -0,0 +1,72 @@
<?php
namespace App\Http;
use Illuminate\Foundation\Http\Kernel as HttpKernel;
class Kernel extends HttpKernel
{
/**
* The application's global HTTP middleware stack.
*
* These middleware are run during every request to your application.
*
* @var array
*/
protected $middleware = [
// \App\Http\Middleware\TrustHosts::class,
\App\Http\Middleware\TrustProxies::class,
\Fruitcake\Cors\HandleCors::class,
\App\Http\Middleware\PreventRequestsDuringMaintenance::class,
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
\App\Http\Middleware\TrimStrings::class,
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
];
/**
* The application's route middleware groups.
*
* @var array
*/
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
// \Illuminate\Session\Middleware\AuthenticateSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\App\Http\Middleware\SetLocale::class,
\App\Http\Middleware\Demo::class,
\App\Http\Middleware\InstallationComplete::class,
],
'api' => [
'throttle:60,1',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
];
/**
* The application's route middleware.
*
* These middleware may be assigned to groups or used individually.
*
* @var array
*/
protected $routeMiddleware = [
'auth' => \App\Http\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'auth.blocked' => \App\Http\Middleware\UserBlocked::class,
'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
'can' => \Illuminate\Auth\Middleware\Authorize::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
'role' => \App\Http\Middleware\RoleMiddleware::class,
'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
];
}

View File

@@ -0,0 +1,21 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Auth\Middleware\Authenticate as Middleware;
class Authenticate extends Middleware
{
/**
* Get the path the user should be redirected to when they are not authenticated.
*
* @param \Illuminate\Http\Request $request
* @return string|null
*/
protected function redirectTo($request)
{
if (! $request->expectsJson()) {
return route('login');
}
}
}

View File

@@ -0,0 +1,57 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class Demo
{
protected $safeRoutes = [
'login',
'logout',
//'profile/toggle-theme'
];
protected $allowedIps = [
//
];
public function handle(Request $request, Closure $next)
{
if ($this->isNotAllowedToDoThis($request)) {
return redirect('/')->with('info', __('This action is unavailable in the demo mode.'));
}
return $next($request);
}
protected function isNotAllowedToDoThis($request): bool
{
return
$this->isInDemo() && // App should be in demo mode
$this->isMethodMatched($request) && // Should match any of these REST request methods
!$this->isRouteWhitelisted($request) && // Should not be included inside the safe routes
!$this->isIpWhitelisted($request); // Current IP should not be whitelisted
}
protected function isInDemo(): bool
{
return config('app.demo');
}
protected function isMethodMatched(Request $request): bool
{
return in_array(strtoupper($request->method()), ['POST', 'PATCH', 'DELETE', 'PUT']);
}
protected function isIpWhitelisted(Request $request): bool
{
return in_array($request->ip(), $this->allowedIps);
}
protected function isRouteWhitelisted(Request $request): bool
{
return in_array($request->path(), $this->safeRoutes);
}
}

View File

@@ -0,0 +1,17 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Cookie\Middleware\EncryptCookies as Middleware;
class EncryptCookies extends Middleware
{
/**
* The names of the cookies that should not be encrypted.
*
* @var array
*/
protected $except = [
//
];
}

View File

@@ -0,0 +1,44 @@
<?php
namespace App\Http\Middleware;
use Closure;
class InstallationComplete
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
// If its not complete
if (
!$request->routeIs('installation-incomplete') &&
!$this->isInstallationComplete()
) {
return redirect()->route('installation-incomplete');
}
// If it is complete, and we're on the installation complete route, redirect to dashboard
if (
$request->routeIs('installation-incomplete') &&
$this->isInstallationComplete()
) {
return redirect()->route('dashboard');
}
return $next($request);
}
protected function isInstallationComplete()
{
return config('app.key') &&
config('services.ploi.token') &&
config('services.ploi.core-token');
}
}

View File

@@ -0,0 +1,17 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\PreventRequestsDuringMaintenance as Middleware;
class PreventRequestsDuringMaintenance extends Middleware
{
/**
* The URIs that should be reachable while maintenance mode is enabled.
*
* @var array
*/
protected $except = [
//
];
}

View File

@@ -0,0 +1,27 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Support\Facades\Auth;
use App\Providers\RouteServiceProvider;
class RedirectIfAuthenticated
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @param string|null $guard
* @return mixed
*/
public function handle($request, Closure $next, $guard = null)
{
if (Auth::guard($guard)->check()) {
return redirect(RouteServiceProvider::HOME);
}
return $next($request);
}
}

View File

@@ -0,0 +1,35 @@
<?php
namespace App\Http\Middleware;
use Closure;
use App\Models\User;
use App\Models\SupportTicket;
class RoleMiddleware
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next, $role = null)
{
abort_if(!$role, 403);
abort_if($request->user()->role !== $role, 403);
if ($request->user()->role === User::ADMIN) {
inertia()->share([
'openTickets' => SupportTicket::whereIn('status', [
SupportTicket::STATUS_OPEN,
SupportTicket::STATUS_CUSTOMER_REPLY
])->count()
]);
}
return $next($request);
}
}

View File

@@ -0,0 +1,61 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Carbon\Carbon;
class SetLocale
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
*
* @return mixed
*/
public function handle($request, Closure $next)
{
if ($user = $request->user()) {
app()->setLocale($user->language);
Carbon::setLocale($user->language);
}
inertia()->share([
'translations' => $this->getTranslations()
]);
return $next($request);
}
/**
* Get the translation keys from file.
*
* @return array
*/
private static function getTranslations()
{
// Less cache hits if its english, this is default language
if (app()->getLocale() == 'en') {
return [];
}
$resolver = function () {
$translationFile = resource_path('lang/' . app()->getLocale() . '.json');
if (!is_readable($translationFile)) {
return [];
}
return json_decode(file_get_contents($translationFile), true);
};
if (app()->isLocal()) {
return $resolver();
}
return \Cache::remember('translations-' . app()->getLocale(), now()->addDay(), $resolver);
}
}

View File

@@ -0,0 +1,18 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\TrimStrings as Middleware;
class TrimStrings extends Middleware
{
/**
* The names of the attributes that should not be trimmed.
*
* @var array
*/
protected $except = [
'password',
'password_confirmation',
];
}

View File

@@ -0,0 +1,20 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Http\Middleware\TrustHosts as Middleware;
class TrustHosts extends Middleware
{
/**
* Get the host patterns that should be trusted.
*
* @return array
*/
public function hosts()
{
return [
$this->allSubdomainsOfApplicationUrl(),
];
}
}

View File

@@ -0,0 +1,23 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Http\Request;
use Fideloper\Proxy\TrustProxies as Middleware;
class TrustProxies extends Middleware
{
/**
* The trusted proxies for this application.
*
* @var array|string|null
*/
protected $proxies;
/**
* The headers that should be used to detect proxies.
*
* @var int
*/
protected $headers = Request::HEADER_X_FORWARDED_ALL;
}

View File

@@ -0,0 +1,31 @@
<?php
namespace App\Http\Middleware;
use Closure;
class UserBlocked
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
if ($request->user()->isUserBlocked()) {
$reason = $request->user()->blocked;
auth()->logout();
return redirect()->route('login')->withErrors([
'email' => 'Your account has been blocked: ' . $reason
]);
}
return $next($request);
}
}

View File

@@ -0,0 +1,17 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware;
class VerifyCsrfToken extends Middleware
{
/**
* The URIs that should be excluded from CSRF verification.
*
* @var array
*/
protected $except = [
//
];
}

View File

@@ -0,0 +1,43 @@
<?php
namespace App\Http\Requests\Admin;
use Illuminate\Foundation\Http\FormRequest;
class PackageRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return auth()->check() && auth()->user()->isAdmin();
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'name' => [
'required',
'string',
'max:255'
],
'maximum_sites' => [
'required',
'numeric',
'min:0',
],
'server_creation' => [
'required',
'boolean'
]
];
}
}

View File

@@ -0,0 +1,34 @@
<?php
namespace App\Http\Requests\Admin;
use Illuminate\Foundation\Http\FormRequest;
class ServerAttachRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return auth()->check() && auth()->user()->isAdmin();
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'email' => [
'required',
'email',
'exists:users'
]
];
}
}

View File

@@ -0,0 +1,39 @@
<?php
namespace App\Http\Requests\Admin;
use Illuminate\Foundation\Http\FormRequest;
class SettingRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return auth()->check() && auth()->user()->isAdmin();
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'name' => [
'required',
'string',
'max:255'
],
'email' => [
'nullable',
'email'
]
];
}
}

View File

@@ -0,0 +1,53 @@
<?php
namespace App\Http\Requests\Admin;
use App\Models\User;
use Illuminate\Validation\Rule;
use Illuminate\Foundation\Http\FormRequest;
class UserRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return auth()->check() && auth()->user()->isAdmin();
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'name' => [
'required',
'string',
'max:255'
],
'email' => [
'required',
'email:rfc,filter',
'max:255',
],
'role' => [
'required',
Rule::in([
User::ADMIN,
User::USER,
])
],
'language' => [
'required',
'string',
Rule::in(languages()),
]
];
}
}

View File

@@ -0,0 +1,40 @@
<?php
namespace App\Http\Requests;
use App\Models\Site;
use Illuminate\Validation\Rule;
use Illuminate\Foundation\Http\FormRequest;
class SiteAppRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return auth()->check();
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'type' => [
'required',
'string',
Rule::in([
Site::PROJECT_WORDPRESS,
Site::PROJECT_NEXTCLOUD,
Site::PROJECT_OCTOBERCMS
])
]
];
}
}

View File

@@ -0,0 +1,39 @@
<?php
namespace App\Http\Requests;
use Illuminate\Validation\Rule;
use App\Rules\LetsEncryptMatchHostWithIp;
use Illuminate\Foundation\Http\FormRequest;
class SiteCertificateRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return auth()->check();
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'domain' => [
'required',
'string',
Rule::unique('certificates', 'domain')->where(function ($query) {
return $query->where('site_id', $this->route('site'));
}),
new LetsEncryptMatchHostWithIp($this->route('site'))
]
];
}
}

View File

@@ -0,0 +1,33 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class SiteCronjobRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return auth()->check();
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'command' => [
'required',
'string'
]
];
}
}

View File

@@ -0,0 +1,67 @@
<?php
namespace App\Http\Requests;
use App\Rules\NotContains;
use Illuminate\Validation\Rule;
use Illuminate\Foundation\Http\FormRequest;
class SiteDatabaseRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return auth()->check();
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'name' => [
'required',
'string',
'alpha_dash',
'min:2',
'max:64',
Rule::unique('databases')->where(function ($query) {
return $query->where('site_id', $this->route('site'));
}),
Rule::notIn([
'database',
'ploi',
'information_schema',
'performance_schema',
'sys'
]),
new NotContains('-'),
],
'user_name' => [
'sometimes',
'nullable',
'string',
'alpha_dash',
'min:2',
'max:64',
new NotContains('-'),
Rule::notIn([
'root',
'ploi'
])
],
'password' => [
'sometimes',
'nullable',
'string',
],
];
}
}

View File

@@ -0,0 +1,51 @@
<?php
namespace App\Http\Requests;
use App\Rules\StartsWith;
use Illuminate\Validation\Rule;
use Illuminate\Foundation\Http\FormRequest;
class SiteRedirectRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return auth()->check();
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'redirect_from' => [
'required',
'string',
'max:150',
new StartsWith('/')
],
'redirect_to' => [
'required',
'string',
'max:150',
new StartsWith(['/', 'http'])
],
'type' => [
'required',
'string',
Rule::in([
'redirect',
'permanent'
])
]
];
}
}

View File

@@ -0,0 +1,37 @@
<?php
namespace App\Http\Requests;
use App\Rules\Hostname;
use App\Rules\ValidateMaximumSites;
use Illuminate\Foundation\Http\FormRequest;
class SiteRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return auth()->check();
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'domain' => [
'required',
'string',
new Hostname,
new ValidateMaximumSites
],
];
}
}

View File

@@ -0,0 +1,35 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class SupportTicketReplyRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return auth()->check();
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'content' => [
'required',
'string',
'min:1',
'max:5000'
]
];
}
}

View File

@@ -0,0 +1,41 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class SupportTicketRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return auth()->check();
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'title' => [
'required',
'string',
'min:2',
'max:255'
],
'content' => [
'required',
'string',
'min:2',
'max:5000'
]
];
}
}

View File

@@ -0,0 +1,46 @@
<?php
namespace App\Http\Requests;
use Illuminate\Validation\Rule;
use Illuminate\Foundation\Http\FormRequest;
class UserProfileRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return auth()->check();
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'name' => [
'required',
'string',
'max:255',
'min:2'
],
'email' => [
'required',
'email',
'max:255'
],
'language' => [
'required',
'string',
Rule::in(languages()),
]
];
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class DocumentationItemResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
return parent::toArray($request);
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class ServerResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
return parent::toArray($request);
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class SiteCertificateResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
return parent::toArray($request);
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class SiteCronjobResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
return parent::toArray($request);
}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class SiteDatabaseResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
/* @var $this \App\Models\Database */
return [
'id' => $this->id,
'status' => $this->status,
'name' => $this->name
];
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class SiteRedirectResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
return parent::toArray($request);
}
}

View File

@@ -0,0 +1,41 @@
<?php
namespace App\Http\Resources;
use App\Models\Site;
use Illuminate\Http\Resources\Json\JsonResource;
class SiteResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
/* @var $this \App\Models\Site */
return [
'id' => $this->id,
'status' => $this->parseStatus($this->status),
'domain' => $this->domain,
'project' => $this->project,
'created_at' => $this->created_at
];
}
private function parseStatus($status)
{
switch ($status) {
case Site::STATUS_BUSY:
return 'busy';
break;
case Site::STATUS_ACTIVE:
return 'active';
break;
default:
return 'unknown';
}
}
}

View File

@@ -0,0 +1,25 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class UserProfileResource extends JsonResource
{
public static $wrap = null;
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
return [
'name' => $this->name,
'email' => $this->email,
'language' => $this->language
];
}
}

View File

@@ -0,0 +1,46 @@
<?php
namespace App\Jobs\Apps;
use App\Models\Site;
use App\Services\Ploi\Ploi;
use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
class InstallApp implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $site;
public $type;
public $options;
/**
* Create a new job instance.
*
* @param Site $site
* @param string $type
* @param array $options
*/
public function __construct(Site $site, $type = Site::PROJECT_WORDPRESS, array $options = [])
{
$this->site = $site;
$this->type = $type;
$this->options = $options;
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
$ploi = new Ploi(config('services.ploi.token'));
$ploi->server($this->site->server->ploi_id)->sites($this->site->ploi_id)->app()->install($this->type, $this->options);
}
}

View File

@@ -0,0 +1,47 @@
<?php
namespace App\Jobs\Apps;
use App\Models\Site;
use App\Services\Ploi\Ploi;
use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
class UninstallApp implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $site;
/**
* Create a new job instance.
*
* @param Site $site
*/
public function __construct(Site $site)
{
$this->site = $site;
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
if (!$this->site->project) {
return;
}
$ploi = new Ploi(config('services.ploi.token'));
$ploi->server($this->site->server->ploi_id)->sites($this->site->ploi_id)->app()->uninstall($this->site->project);
$this->site->project = null;
$this->site->save();
}
}

View File

@@ -0,0 +1,56 @@
<?php
namespace App\Jobs\Certificates;
use App\Models\Certificate;
use App\Services\Ploi\Ploi;
use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
class CreateCertificate implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $certificate;
/**
* Create a new job instance.
*
* @param Certificate $certificate
*/
public function __construct(Certificate $certificate)
{
$this->certificate = $certificate;
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
$ploi = new Ploi(config('services.ploi.token'));
$ploiCertificate = $ploi->server($this->certificate->server->ploi_id)
->sites($this->certificate->site->ploi_id)
->certificates()
->create(
$this->certificate->domain
);
$this->certificate->ploi_id = $ploiCertificate->id;
$this->certificate->save();
// Lets fetch the status after 5 seconds
dispatch(new FetchCertificateStatus($this->certificate))->delay(now()->addSeconds(5));
}
public function failed()
{
$this->certificate->delete();
}
}

View File

@@ -0,0 +1,52 @@
<?php
namespace App\Jobs\Certificates;
use App\Services\Ploi\Ploi;
use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
class DeleteCertificate implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $serverPloiId;
public $sitePloiId;
public $certificatePloiId;
/**
* Create a new job instance.
*
* @param $serverPloiId
* @param $sitePloiId
* @param $certificatePloiId
*/
public function __construct($serverPloiId, $sitePloiId, $certificatePloiId)
{
$this->serverPloiId = $serverPloiId;
$this->sitePloiId = $sitePloiId;
$this->certificatePloiId = $certificatePloiId;
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
if (!$this->serverPloiId || !$this->certificatePloiId) {
return;
}
$ploi = new Ploi(config('services.ploi.token'));
$ploi->server($this->serverPloiId)
->sites($this->sitePloiId)
->certificates()
->delete($this->certificatePloiId);
}
}

View File

@@ -0,0 +1,65 @@
<?php
namespace App\Jobs\Certificates;
use App\Models\Certificate;
use App\Services\Ploi\Ploi;
use Illuminate\Bus\Queueable;
use App\Traits\JobHasThresholds;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
class FetchCertificateStatus implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, JobHasThresholds;
public $certificate;
/**
* Create a new job instance.
*
* @param Certificate $certificate
* @param int $threshold
*/
public function __construct(Certificate $certificate, $threshold = 0)
{
$this->certificate = $certificate;
$this->setThreshold($threshold);
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
// If we tried over 5 times, game over, delete the database
if ($this->hasReachedThresholdLimit()) {
$this->certificate->delete();
return;
}
$ploi = new Ploi(config('services.ploi.token'));
$ploiCronjob = $ploi->server($this->certificate->server->ploi_id)
->sites($this->certificate->site->ploi_id)
->certificates()
->get($this->certificate->ploi_id)
->getData();
if ($ploiCronjob->status !== Certificate::STATUS_ACTIVE) {
$this->incrementThreshold();
dispatch(new self($this->certificate, $this->threshold))->delay(now()->addSeconds(4));
return;
}
$this->certificate->status = $ploiCronjob->status;
$this->certificate->save();
}
}

36
app/Jobs/Core/Ping.php Normal file
View File

@@ -0,0 +1,36 @@
<?php
namespace App\Jobs\Core;
use App\Services\Ploi\Ploi;
use Illuminate\Bus\Queueable;
use Illuminate\Support\Facades\Http;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
class Ping implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function handle()
{
$response = Http::withHeaders([
'Accept' => 'application/json',
'Content-Type' => 'application/json',
'X-Ploi-Core-Key' => config('services.ploi.core-token')
])
->withToken(config('services.ploi.token'))
->get((new Ploi)->url . 'ping');
if (!$response->ok() || !$response->json()) {
// Something went wrong..
return;
}
$response->json();
}
}

View File

@@ -0,0 +1,37 @@
<?php
namespace App\Jobs\Core;
use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
class UpdateSystem implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct()
{
//
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
shell_exec('git pull origin master');
shell_exec('composer install --no-interaction --prefer-dist --no-dev --optimize-autoloader --quiet');
cache()->forget('ploi-core-current-version');
}
}

View File

@@ -0,0 +1,52 @@
<?php
namespace App\Jobs\Cronjobs;
use App\Models\Cronjob;
use App\Services\Ploi\Ploi;
use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
class CreateCronjob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $cronjob;
/**
* Create a new job instance.
*
* @param Cronjob $cronjob
*/
public function __construct(Cronjob $cronjob)
{
$this->cronjob = $cronjob;
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
$owner = $this->cronjob->site->users()->first();
$ploi = new Ploi(config('services.ploi.token'));
$ploiCronjob = $ploi->server($this->cronjob->server->ploi_id)->cronjobs()->create(
$this->cronjob->command,
$this->cronjob->frequency,
$owner->user_name
);
$this->cronjob->ploi_id = $ploiCronjob->id;
$this->cronjob->save();
// Lets fetch the status after 5 seconds
dispatch(new FetchCronjobStatus($this->cronjob))->delay(now()->addSeconds(3));
}
}

View File

@@ -0,0 +1,46 @@
<?php
namespace App\Jobs\Cronjobs;
use App\Services\Ploi\Ploi;
use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
class DeleteCronjob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $serverPloiId;
public $cronjobPloiId;
/**
* Create a new job instance.
*
* @param $serverPloiId
* @param $cronjobPloiId
*/
public function __construct($serverPloiId, $cronjobPloiId)
{
$this->cronjobPloiId = $cronjobPloiId;
$this->serverPloiId = $serverPloiId;
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
if (!$this->serverPloiId || !$this->cronjobPloiId) {
return;
}
$ploi = new Ploi(config('services.ploi.token'));
$ploi->server($this->serverPloiId)->cronjobs()->delete($this->cronjobPloiId);
}
}

Some files were not shown because too many files have changed in this diff Show More