Compare commits
90 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a43cd19efd | ||
|
|
e5eec000d3 | ||
|
|
e5c8a62b32 | ||
|
|
9ae1c145b6 | ||
|
|
eb8b75e4f9 | ||
|
|
7378b82adf | ||
|
|
6f3b588f3d | ||
|
|
a2154cf37c | ||
|
|
9dab5f8093 | ||
|
|
9c9469d2f6 | ||
|
|
94acc313b1 | ||
|
|
448398322f | ||
|
|
3a78339396 | ||
|
|
a925a70448 | ||
|
|
e283eacaa3 | ||
|
|
3df6b6baed | ||
|
|
b65526e040 | ||
|
|
673bbf73be | ||
|
|
094d22eaa8 | ||
|
|
0fdba5fdec | ||
|
|
cf0730be89 | ||
|
|
221e67fd12 | ||
|
|
f9074309d1 | ||
|
|
b2bdbb9e30 | ||
|
|
63d0cb9626 | ||
|
|
e3bb3ae4d1 | ||
|
|
31890005ac | ||
|
|
04a216dee1 | ||
|
|
5d403c1202 | ||
|
|
a2d92c67b3 | ||
|
|
0fec3d82a3 | ||
|
|
6530a64f97 | ||
|
|
656f02d652 | ||
|
|
0dbf3bba4d | ||
|
|
ea47c0c3c6 | ||
|
|
b4072c7892 | ||
|
|
e80cd1990a | ||
|
|
2585cc1db4 | ||
|
|
0225828445 | ||
|
|
63af93592d | ||
|
|
b87fcd0f25 | ||
|
|
23a6b3cc55 | ||
|
|
dac3d229fd | ||
|
|
e360d9c5df | ||
|
|
8f100fc027 | ||
|
|
06fb331ae4 | ||
|
|
2bcf02a779 | ||
|
|
1b24664b60 | ||
|
|
2ce96a2062 | ||
|
|
5d737ba328 | ||
|
|
bafe8ba780 | ||
|
|
b7ff40fd72 | ||
|
|
8e036b1c01 | ||
|
|
b016c18880 | ||
|
|
d17ae49155 | ||
|
|
6ef5cd25f5 | ||
|
|
c98f077a0e | ||
|
|
a1f58c8e13 | ||
|
|
37281b01e4 | ||
|
|
65b0a768af | ||
|
|
39af06d3b2 | ||
|
|
7d137dd612 | ||
|
|
ce5e6c18f0 | ||
|
|
3d445ca61a | ||
|
|
8566eaaa6c | ||
|
|
db1647569c | ||
|
|
204afb7eb1 | ||
|
|
37f82d4579 | ||
|
|
2ed440e65f | ||
|
|
57a5ec2206 | ||
|
|
f14bc0494b | ||
|
|
c4147f0125 | ||
|
|
cfd0c3cbe9 | ||
|
|
0d0e2732b5 | ||
|
|
0a7b072eeb | ||
|
|
af6e12ca01 | ||
|
|
5eba94fd9b | ||
|
|
e9756494d9 | ||
|
|
f7f919b5de | ||
|
|
6f434f3b07 | ||
|
|
8820851afa | ||
|
|
4582e955d0 | ||
|
|
e07395b3d5 | ||
|
|
3ad7d06976 | ||
|
|
f57cbb76e3 | ||
|
|
bb3151a2fe | ||
|
|
5b48d204a0 | ||
|
|
680d96882a | ||
|
|
7347356646 | ||
|
|
2652e7ed71 |
@@ -36,6 +36,10 @@ MAIL_ENCRYPTION=null
|
||||
MAIL_FROM_ADDRESS=null
|
||||
MAIL_FROM_NAME="${APP_NAME}"
|
||||
|
||||
STRIPE_KEY=your-stripe-key
|
||||
STRIPE_SECRET=your-stripe-secret
|
||||
CASHIER_MODEL=App\Models\User
|
||||
|
||||
AWS_ACCESS_KEY_ID=
|
||||
AWS_SECRET_ACCESS_KEY=
|
||||
AWS_DEFAULT_REGION=us-east-1
|
||||
|
||||
44
app/Casts/PermissionCast.php
Normal file
44
app/Casts/PermissionCast.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace App\Casts;
|
||||
|
||||
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
|
||||
|
||||
class PermissionCast 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)
|
||||
{
|
||||
if (!$value) {
|
||||
return [
|
||||
'create' => false,
|
||||
'update' => false,
|
||||
'delete' => false,
|
||||
];
|
||||
}
|
||||
|
||||
return json_decode($value, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
{
|
||||
return json_encode($value);
|
||||
}
|
||||
}
|
||||
@@ -21,15 +21,9 @@ class Install extends Command
|
||||
protected $versionChecker;
|
||||
protected $installationFile = 'app/installation';
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->versionChecker = (new VersionChecker)->getVersions();
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$this->init();
|
||||
$this->intro();
|
||||
$this->isInstalled();
|
||||
$this->checkApplicationKey();
|
||||
@@ -45,6 +39,11 @@ class Install extends Command
|
||||
$this->info('Visit your platform at ' . env('APP_URL'));
|
||||
}
|
||||
|
||||
protected function init()
|
||||
{
|
||||
$this->versionChecker = (new VersionChecker)->getVersions();
|
||||
}
|
||||
|
||||
protected function askAboutAdministrationAccount()
|
||||
{
|
||||
$this->info('Let\'s start by setting up your administration account.');
|
||||
@@ -85,16 +84,46 @@ class Install extends Command
|
||||
Package::create([
|
||||
'name' => 'Basic',
|
||||
'maximum_sites' => 5,
|
||||
'site_permissions' => [
|
||||
'create' => true,
|
||||
'update' => true,
|
||||
'delete' => true
|
||||
],
|
||||
'server_permissions' => [
|
||||
'create' => false,
|
||||
'update' => false,
|
||||
'delete' => false
|
||||
]
|
||||
]);
|
||||
|
||||
Package::create([
|
||||
'name' => 'Professional',
|
||||
'maximum_sites' => 5,
|
||||
'site_permissions' => [
|
||||
'create' => true,
|
||||
'update' => true,
|
||||
'delete' => true
|
||||
],
|
||||
'server_permissions' => [
|
||||
'create' => false,
|
||||
'update' => false,
|
||||
'delete' => false
|
||||
]
|
||||
]);
|
||||
|
||||
Package::create([
|
||||
'name' => 'Unlimited',
|
||||
'maximum_sites' => 0,
|
||||
'site_permissions' => [
|
||||
'create' => true,
|
||||
'update' => true,
|
||||
'delete' => true
|
||||
],
|
||||
'server_permissions' => [
|
||||
'create' => false,
|
||||
'update' => false,
|
||||
'delete' => false
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
81
app/Http/Controllers/Admin/AlertController.php
Normal file
81
app/Http/Controllers/Admin/AlertController.php
Normal file
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Admin\AlertRequest;
|
||||
use App\Models\Alert;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class AlertController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
return inertia('Admin/Alerts/Index', [
|
||||
'alerts' => Alert::query()->latest()->paginate()
|
||||
]);
|
||||
}
|
||||
|
||||
public function create()
|
||||
{
|
||||
return inertia('Admin/Alerts/Create');
|
||||
}
|
||||
|
||||
public function store(AlertRequest $request)
|
||||
{
|
||||
Alert::create($request->all());
|
||||
|
||||
return redirect()->route('admin.alerts.index');
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*
|
||||
* @param int $id
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function show($id)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for editing the specified resource.
|
||||
*
|
||||
* @param int $id
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function edit($id)
|
||||
{
|
||||
return inertia('Admin/Alerts/Edit', [
|
||||
'alert' => Alert::findOrFail($id)
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param int $id
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function update(AlertRequest $request, $id)
|
||||
{
|
||||
Alert::findOrFail($id)->update($request->all());
|
||||
|
||||
return redirect()->route('admin.alerts.index');
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*
|
||||
* @param int $id
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function destroy($id)
|
||||
{
|
||||
Alert::findOrFail($id)->delete();
|
||||
|
||||
return redirect()->route('admin.alerts.index');
|
||||
}
|
||||
}
|
||||
98
app/Http/Controllers/Admin/ApplicationLogController.php
Normal file
98
app/Http/Controllers/Admin/ApplicationLogController.php
Normal file
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use App\Http\Controllers\Controller;
|
||||
|
||||
class ApplicationLogController extends Controller
|
||||
{
|
||||
protected $final = [];
|
||||
protected $config = [
|
||||
'date' => null
|
||||
];
|
||||
|
||||
public function index(Request $request)
|
||||
{
|
||||
$this->config['date'] = date('Y-m-d');
|
||||
|
||||
if ($date = $request->input('date')) {
|
||||
$this->config['date'] = $date;
|
||||
} else {
|
||||
$request->merge(['date' => date('Y-m-d')]);
|
||||
}
|
||||
|
||||
return inertia('Admin/ApplicationLogs', [
|
||||
'logData' => $this->get(),
|
||||
'filters' => $request->all('date')
|
||||
]);
|
||||
}
|
||||
|
||||
public function getLogFileDates()
|
||||
{
|
||||
$dates = [];
|
||||
$files = glob(storage_path('logs/laravel-*.log'));
|
||||
$files = array_reverse($files);
|
||||
|
||||
foreach ($files as $path) {
|
||||
$fileName = basename($path);
|
||||
preg_match('/(?<=laravel-)(.*)(?=.log)/', $fileName, $dtMatch);
|
||||
$date = $dtMatch[0];
|
||||
array_push($dates, $date);
|
||||
}
|
||||
|
||||
return $dates;
|
||||
}
|
||||
|
||||
public function get()
|
||||
{
|
||||
$availableDates = $this->getLogFileDates();
|
||||
|
||||
if (count($availableDates) == 0) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'No log available'
|
||||
]);
|
||||
}
|
||||
|
||||
$configDate = $this->config['date'];
|
||||
if ($configDate == null) {
|
||||
$configDate = $availableDates[0];
|
||||
}
|
||||
|
||||
if (!in_array($configDate, $availableDates)) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'No log file found with selected date ' . $configDate
|
||||
]);
|
||||
}
|
||||
|
||||
$pattern = "/^\[(?<date>.*)\]\s(?<env>\w+)\.(?<type>\w+):(?<message>.*)/m";
|
||||
|
||||
$fileName = 'laravel-' . $configDate . '.log';
|
||||
$content = file_get_contents(storage_path('logs/' . $fileName));
|
||||
preg_match_all($pattern, $content, $matches, PREG_SET_ORDER, 0);
|
||||
|
||||
$logs = [];
|
||||
foreach ($matches as $match) {
|
||||
$logs[] = [
|
||||
'timestamp' => $match['date'],
|
||||
'env' => $match['env'],
|
||||
'type' => $match['type'],
|
||||
'message' => trim($match['message'])
|
||||
];
|
||||
}
|
||||
|
||||
preg_match('/(?<=laravel-)(.*)(?=.log)/', $fileName, $dtMatch);
|
||||
$date = $dtMatch[0];
|
||||
|
||||
$data = [
|
||||
'available_dates' => $availableDates,
|
||||
'date' => $date,
|
||||
'filename' => $fileName,
|
||||
'logs' => array_reverse($logs)
|
||||
];
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Models\DocumentationItem;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\DocumentationCategory;
|
||||
use App\Http\Requests\Admin\DocumentationArticleRequest;
|
||||
|
||||
class DocumentationArticleController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
$articles = DocumentationItem::query()->with('category:id,title')->latest()->paginate();
|
||||
|
||||
return inertia('Admin/Documentation/Articles/Index', [
|
||||
'articles' => $articles
|
||||
]);
|
||||
}
|
||||
|
||||
public function create()
|
||||
{
|
||||
$categories = DocumentationCategory::pluck('title', 'id');
|
||||
|
||||
return inertia('Admin/Documentation/Articles/Create', [
|
||||
'categories' => $categories,
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(DocumentationArticleRequest $request)
|
||||
{
|
||||
$article = DocumentationItem::create([
|
||||
'title' => $request->input('title'),
|
||||
'content' => $request->input('content')
|
||||
]);
|
||||
|
||||
$article->documentation_category_id = $request->input('category_id');
|
||||
$article->save();
|
||||
|
||||
return redirect()->route('admin.documentation.articles.index')->with('success', __('Documentation article has been created'));
|
||||
}
|
||||
|
||||
public function edit($id)
|
||||
{
|
||||
$article = DocumentationItem::findOrFail($id);
|
||||
|
||||
$categories = DocumentationCategory::pluck('title', 'id');
|
||||
|
||||
return inertia('Admin/Documentation/Articles/Edit', [
|
||||
'article' => $article,
|
||||
'categories' => $categories
|
||||
]);
|
||||
}
|
||||
|
||||
public function update(DocumentationArticleRequest $request, $id)
|
||||
{
|
||||
$article = DocumentationItem::findOrFail($id);
|
||||
|
||||
$article->update([
|
||||
'title' => $request->input('title'),
|
||||
'content' => $request->input('content')
|
||||
]);
|
||||
|
||||
$article->documentation_category_id = $request->input('category_id');
|
||||
$article->save();
|
||||
|
||||
return redirect()->route('admin.documentation.articles.index')->with('success', __('Documentation article has been updated'));
|
||||
}
|
||||
}
|
||||
@@ -3,11 +3,53 @@
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\DocumentationCategory;
|
||||
use App\Http\Requests\Admin\DocumentationCategoryRequest;
|
||||
|
||||
class DocumentationController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
return inertia('Admin/Documentation/Index');
|
||||
$categories = DocumentationCategory::query()->latest()->paginate();
|
||||
|
||||
return inertia('Admin/Documentation/Index', [
|
||||
'categories' => $categories
|
||||
]);
|
||||
}
|
||||
|
||||
public function create()
|
||||
{
|
||||
return inertia('Admin/Documentation/Create');
|
||||
}
|
||||
|
||||
public function store(DocumentationCategoryRequest $request)
|
||||
{
|
||||
DocumentationCategory::create([
|
||||
'title' => $request->input('title'),
|
||||
'description' => $request->input('description')
|
||||
]);
|
||||
|
||||
return redirect()->route('admin.documentation.index')->with('success', __('Documentation category has been created'));
|
||||
}
|
||||
|
||||
public function edit($id)
|
||||
{
|
||||
$category = DocumentationCategory::findOrFail($id);
|
||||
|
||||
return inertia('Admin/Documentation/Edit', [
|
||||
'category' => $category
|
||||
]);
|
||||
}
|
||||
|
||||
public function update(DocumentationCategoryRequest $request, $id)
|
||||
{
|
||||
$category = DocumentationCategory::findOrFail($id);
|
||||
|
||||
$category->update([
|
||||
'title' => $request->input('title'),
|
||||
'description' => $request->input('description'),
|
||||
]);
|
||||
|
||||
return redirect()->route('admin.documentation.index')->with('success', __('Documentation category has been updated'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Models\Package;
|
||||
use App\Models\Provider;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Admin\PackageRequest;
|
||||
|
||||
@@ -19,25 +20,32 @@ class PackageController extends Controller
|
||||
|
||||
public function create()
|
||||
{
|
||||
return inertia('Admin/Packages/Create');
|
||||
$providers = Provider::get(['name', 'label', 'id'])->pluck('nameWithLabel', 'id');
|
||||
|
||||
return inertia('Admin/Packages/Create', [
|
||||
'providers' => $providers
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(PackageRequest $request)
|
||||
{
|
||||
Package::create($request->all());
|
||||
$package = Package::create($request->validated());
|
||||
|
||||
return redirect()->route('admin.packages.index')->with('success', 'Package has been created');
|
||||
}
|
||||
$package->providers()->sync($request->input('providers'));
|
||||
|
||||
public function show($id)
|
||||
{
|
||||
//
|
||||
return redirect()->route('admin.packages.index')->with('success', __('Package has been created'));
|
||||
}
|
||||
|
||||
public function edit($id)
|
||||
{
|
||||
$package = Package::with('providers:id')->findOrFail($id);
|
||||
|
||||
$providers = Provider::get(['name', 'label', 'id'])->pluck('nameWithLabel', 'id');
|
||||
|
||||
return inertia('Admin/Packages/Edit', [
|
||||
'package' => Package::findOrFail($id)
|
||||
'package' => $package,
|
||||
'providers' => $providers,
|
||||
'syncedProviders' => $package->providers->pluck('id')
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -47,7 +55,9 @@ class PackageController extends Controller
|
||||
|
||||
$package->update($request->validated());
|
||||
|
||||
return redirect()->route('admin.packages.index')->with('success', 'Package has been updated');
|
||||
$package->providers()->sync($request->input('providers'));
|
||||
|
||||
return redirect()->route('admin.packages.index')->with('success', __('Package has been updated'));
|
||||
}
|
||||
|
||||
public function destroy($id)
|
||||
@@ -56,6 +66,6 @@ class PackageController extends Controller
|
||||
|
||||
$package->delete();
|
||||
|
||||
return redirect()->route('admin.packages.index')->with('success', 'Package has been removed');
|
||||
return redirect()->route('admin.packages.index')->with('success', __('Package has been removed'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,36 @@
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Models\Provider;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Admin\ProviderRequest;
|
||||
|
||||
class ProviderController extends Controller
|
||||
{
|
||||
//
|
||||
public function edit($id)
|
||||
{
|
||||
$provider = Provider::findOrFail($id);
|
||||
|
||||
return inertia('Admin/Services/Provider/Edit', [
|
||||
'provider' => $provider,
|
||||
]);
|
||||
}
|
||||
|
||||
public function update(ProviderRequest $request, $id)
|
||||
{
|
||||
$provider = Provider::findOrFail($id);
|
||||
|
||||
$provider->update($request->validated());
|
||||
|
||||
return redirect()->route('admin.services.index')->with('success', __('Provider has been updated'));
|
||||
}
|
||||
|
||||
public function destroy($id)
|
||||
{
|
||||
$provider = Provider::findOrFail($id);
|
||||
|
||||
$provider->delete();
|
||||
|
||||
return redirect()->route('admin.services.index')->with('success', __('Provider has been deleted'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Requests\Admin\ServerAttachRequest;
|
||||
use App\Models\Server;
|
||||
use App\Models\User;
|
||||
use App\Models\Server;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Admin\ServerAttachRequest;
|
||||
|
||||
class ServerController extends Controller
|
||||
{
|
||||
@@ -14,7 +14,7 @@ class ServerController extends Controller
|
||||
{
|
||||
$server = Server::findOrFail($id);
|
||||
|
||||
$users = $server->users()->select('id', 'name', 'email')->get()->map(function($user){
|
||||
$users = $server->users()->select('id', 'name', 'email')->get()->map(function ($user) {
|
||||
return [
|
||||
'id' => $user->id,
|
||||
'name' => $user->name,
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Models\Site;
|
||||
use App\Models\Server;
|
||||
use App\Models\Provider;
|
||||
use App\Http\Controllers\Controller;
|
||||
|
||||
class ServiceController extends Controller
|
||||
@@ -13,6 +14,10 @@ class ServiceController extends Controller
|
||||
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'),
|
||||
'providers' => Provider::query()
|
||||
->withCount('regions', 'plans', 'servers')
|
||||
->latest()
|
||||
->paginate(5, ['*'], 'providers_per_page'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,15 +18,19 @@ class SettingController extends Controller
|
||||
'documentation' => setting('documentation'),
|
||||
'allow_registration' => setting('allow_registration'),
|
||||
'default_package' => setting('default_package'),
|
||||
'receive_email_on_server_creation' => setting('receive_email_on_server_creation'),
|
||||
'isolate_per_site_per_user' => setting('isolate_per_site_per_user'),
|
||||
'enable_api' => setting('enable_api'),
|
||||
'api_token' => setting('api_token') ? decrypt(setting('api_token')) : null,
|
||||
'default_language' => setting('default_language', 'en')
|
||||
];
|
||||
|
||||
$packages = Package::pluck('name', 'id');
|
||||
|
||||
return inertia('Admin/Settings', [
|
||||
'company_settings' => $settings,
|
||||
'packages' => $packages
|
||||
'packages' => $packages,
|
||||
'languages' => languages()
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -40,8 +44,11 @@ class SettingController extends Controller
|
||||
'allow_registration',
|
||||
'documentation',
|
||||
'default_package',
|
||||
'receive_email_on_server_creation',
|
||||
'isolate_per_site_per_user',
|
||||
'enable_api',
|
||||
'api_token',
|
||||
'default_language'
|
||||
]) as $key => $value) {
|
||||
if ($key === 'api_token') {
|
||||
$value = encrypt($value);
|
||||
|
||||
@@ -2,12 +2,11 @@
|
||||
|
||||
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;
|
||||
use App\Http\Requests\Admin\ServerAttachRequest;
|
||||
|
||||
class SiteController extends Controller
|
||||
{
|
||||
@@ -15,7 +14,7 @@ class SiteController extends Controller
|
||||
{
|
||||
$site = Site::findOrFail($id);
|
||||
|
||||
$users = $site->users()->select('id', 'name', 'email')->get()->map(function($user){
|
||||
$users = $site->users()->select('id', 'name', 'email')->get()->map(function ($user) {
|
||||
return [
|
||||
'id' => $user->id,
|
||||
'name' => $user->name,
|
||||
|
||||
57
app/Http/Controllers/Admin/SynchronizeProviderController.php
Normal file
57
app/Http/Controllers/Admin/SynchronizeProviderController.php
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Models\Provider;
|
||||
use App\Services\Ploi\Ploi;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Http\Controllers\Controller;
|
||||
|
||||
class SynchronizeProviderController 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'));
|
||||
|
||||
$availableProviders = $ploi->user()->serverProviders()->getData();
|
||||
|
||||
$currentProviders = Provider::whereNotIn('id', array_keys((array)$availableProviders))->get();
|
||||
|
||||
return inertia('Admin/Services/Providers', [
|
||||
'availableProviders' => $availableProviders,
|
||||
'currentProviders' => $currentProviders
|
||||
]);
|
||||
}
|
||||
|
||||
public function synchronize(Request $request, $providerId)
|
||||
{
|
||||
$ploiProvider = (new Ploi)->user()->serverProviders($providerId)->getData();
|
||||
|
||||
$provider = Provider::updateOrCreate([
|
||||
'ploi_id' => $ploiProvider->id,
|
||||
], [
|
||||
'label' => $ploiProvider->label,
|
||||
'name' => $ploiProvider->name
|
||||
]);
|
||||
|
||||
foreach ($ploiProvider->provider->plans as $plan) {
|
||||
$provider->plans()->updateOrCreate([
|
||||
'plan_id' => $plan->id
|
||||
], [
|
||||
'label' => $plan->name,
|
||||
]);
|
||||
}
|
||||
|
||||
foreach ($ploiProvider->provider->regions as $region) {
|
||||
$provider->regions()->updateOrCreate([
|
||||
'region_id' => $region->id
|
||||
], [
|
||||
'label' => $region->name,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,10 +2,10 @@
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use App\Jobs\Core\UpdateSystem;
|
||||
use App\Services\VersionChecker;
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class SystemController extends Controller
|
||||
{
|
||||
|
||||
@@ -31,7 +31,8 @@ class UserController extends Controller
|
||||
return inertia('Admin/Users/Create', [
|
||||
'packages' => $packages,
|
||||
'languages' => languages(),
|
||||
'defaultPackage' => (string)setting('default_package')
|
||||
'defaultPackage' => (string)setting('default_package'),
|
||||
'defaultLanguage' => (string)setting('default_language', 'en'),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -54,7 +55,7 @@ class UserController extends Controller
|
||||
|
||||
public function show($id)
|
||||
{
|
||||
//
|
||||
// TODO: Implement show feature for a user
|
||||
}
|
||||
|
||||
public function edit($id)
|
||||
|
||||
@@ -2,11 +2,10 @@
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Api\UserRequest;
|
||||
use App\Http\Resources\Api\UserResource;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class UserController extends Controller
|
||||
{
|
||||
|
||||
@@ -8,8 +8,6 @@ class DashboardController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
$package = auth()->user()->package;
|
||||
|
||||
$logs = auth()->user()->systemLogs()
|
||||
->with('model')
|
||||
->latest()
|
||||
@@ -31,9 +29,6 @@ class DashboardController extends Controller
|
||||
return inertia('Dashboard/Index', [
|
||||
'sites' => auth()->user()->sites()->count(),
|
||||
'servers' => auth()->user()->servers()->count(),
|
||||
'package' => [
|
||||
'name' => $package->name ?? 'None'
|
||||
],
|
||||
'logs' => $logs,
|
||||
]);
|
||||
}
|
||||
|
||||
186
app/Http/Controllers/Profile/ProfileBillingController.php
Normal file
186
app/Http/Controllers/Profile/ProfileBillingController.php
Normal file
@@ -0,0 +1,186 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Profile;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use App\Models\User;
|
||||
use App\Models\Package;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Http\Controllers\Controller;
|
||||
use Stripe\Exception\InvalidRequestException;
|
||||
|
||||
class ProfileBillingController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
/* @var $user User */
|
||||
$user = auth()->user();
|
||||
|
||||
$sortByType = array_key_first($request->input('sortBy', []));
|
||||
|
||||
$packages = Package::query()
|
||||
->where(function ($query) {
|
||||
return $query
|
||||
->where('price_monthly', '>', 0)
|
||||
->whereNotNull('plan_id');
|
||||
})
|
||||
->when($request->input('sortBy.' . $sortByType), function ($query, $value) use ($sortByType) {
|
||||
if ($sortByType === 'price') {
|
||||
return $value === 'asc'
|
||||
? $query->orderBy('price_monthly', 'asc')
|
||||
: $query->orderBy('price_monthly', 'desc');
|
||||
}
|
||||
if ($sortByType === 'servers') {
|
||||
return $value === 'asc'
|
||||
? $query->orderBy('maximum_servers', 'asc')
|
||||
: $query->orderBy('maximum_servers', 'desc');
|
||||
}
|
||||
if ($sortByType === 'sites') {
|
||||
return $value === 'asc'
|
||||
? $query->orderBy('maximum_sites', 'asc')
|
||||
: $query->orderBy('maximum_sites', 'desc');
|
||||
}
|
||||
if ($sortByType === 'name') {
|
||||
return $value === 'asc'
|
||||
? $query->orderBy('name', 'asc')
|
||||
: $query->orderBy('name', 'desc');
|
||||
}
|
||||
|
||||
return $query;
|
||||
}, function ($query) {
|
||||
return $query->orderBy('price_monthly', 'asc');
|
||||
})
|
||||
->get()
|
||||
->transform(function (Package $package) {
|
||||
$currencies = [
|
||||
Package::CURRENCY_EURO => '€',
|
||||
Package::CURRENCY_USD => '$',
|
||||
Package::CURRENCY_NOK => 'KR ',
|
||||
Package::CURRENCY_CAD => 'CAD $',
|
||||
Package::CURRENCY_AUD => 'AUD $',
|
||||
];
|
||||
|
||||
$package->price_monthly = ($currencies[$package->currency] ?? '[Unknown currency]') . number_format($package->price_monthly, 2, ',', '.');
|
||||
|
||||
return $package;
|
||||
});
|
||||
|
||||
return inertia('Profile/Billing', [
|
||||
'packages' => $packages,
|
||||
'subscription' => $user->subscription('default'),
|
||||
'public_key' => config('cashier.key'),
|
||||
'data_client_secret' => function () use ($user) {
|
||||
$intent = $user->createSetupIntent();
|
||||
return $intent->client_secret;
|
||||
},
|
||||
'card' => [
|
||||
'last_four' => $user->card_last_four,
|
||||
'brand' => $user->card_brand
|
||||
],
|
||||
'filters' => [
|
||||
'sort' => [
|
||||
$sortByType => $request->input('sortBy.' . $sortByType, 'asc'),
|
||||
]
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
public function updateCard(Request $request)
|
||||
{
|
||||
/** @var User $user */
|
||||
$user = $request->user();
|
||||
|
||||
$user->createOrGetStripeCustomer([
|
||||
'name' => $user->name,
|
||||
'description' => $request->input('billing_details')
|
||||
]);
|
||||
|
||||
foreach ($user->paymentMethods() as $paymentMethod) {
|
||||
$paymentMethod->delete();
|
||||
}
|
||||
|
||||
$user->addPaymentMethod($request->get('payment_method'));
|
||||
$user->updateDefaultPaymentMethod($request->get('payment_method'));
|
||||
$user->updateDefaultPaymentMethodFromStripe();
|
||||
|
||||
return redirect()->route('profile.billing.index')->with('success', 'Your card has been added, you can now update your plan');
|
||||
}
|
||||
|
||||
public function updatePlan(Request $request)
|
||||
{
|
||||
/** @var User $user */
|
||||
$user = auth()->user();
|
||||
|
||||
if (!$user->hasStripeId() || !$user->defaultPaymentMethod()) {
|
||||
return redirect()->route('profile.billing.index')->with('error', 'You cannot change your plan without a valid creditcard, please update your billing details first');
|
||||
}
|
||||
|
||||
$plan = Package::query()->findOrFail($request->input('plan'));
|
||||
|
||||
$planId = $plan->plan_id;
|
||||
|
||||
// Only do something if the user is not already subscribed to this plan.
|
||||
if ($user->subscribedToPlan($planId, 'default')) {
|
||||
return redirect()->route('profile.billing.index')->with('error', 'You did not select a different plan');
|
||||
}
|
||||
|
||||
// If the user is already subscribed to the default plan, we have to swap it. Otherwise create a new one.
|
||||
try {
|
||||
if ($user->subscribed('default')) {
|
||||
$user->subscription('default')->swap($planId);
|
||||
} else {
|
||||
if ($coupon = $request->input('coupon')) {
|
||||
$user->newSubscription('default', $planId)
|
||||
->withCoupon($coupon)
|
||||
->create($user->defaultPaymentMethod()->id);
|
||||
} else {
|
||||
$user->newSubscription('default', $planId)->create($user->defaultPaymentMethod()->id);
|
||||
}
|
||||
}
|
||||
} catch (InvalidRequestException $exception) {
|
||||
$error = $exception->getJsonBody();
|
||||
|
||||
return redirect()->route('profile.billing.index')->with('error', Arr::get($error, 'error.message'));
|
||||
}
|
||||
|
||||
$user->package_id = $plan->id;
|
||||
$user->save();
|
||||
|
||||
return redirect()->route('profile.billing.index')->with('success', sprintf("Your plan has been updated to %s", $plan->name));
|
||||
}
|
||||
|
||||
public function cancel(Request $request)
|
||||
{
|
||||
/* @var $user \App\Models\User */
|
||||
$user = $request->user();
|
||||
|
||||
$subscription = $user->subscription('default')->cancel();
|
||||
|
||||
return redirect()->route('profile.billing.index')->with('success', __('Your subscription has been cancelled, your end date is ' . $subscription->ends_at));
|
||||
}
|
||||
|
||||
public function invoices(Request $request)
|
||||
{
|
||||
return $request->user()->invoices()->map(function ($invoice) {
|
||||
$symbol = $invoice->currency === Package::CURRENCY_EURO ? '€' : '$';
|
||||
|
||||
return [
|
||||
'id' => $invoice->id,
|
||||
'created' => Carbon::createFromTimestamp($invoice->created)->format('Y-m-d H:i:s'),
|
||||
'number' => $invoice->number,
|
||||
'status' => $invoice->status,
|
||||
'total' => $symbol . number_format($invoice->total / 100, 2, ',', '.'),
|
||||
'currency' => $invoice->currency,
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
public function pdf(Request $request, $id)
|
||||
{
|
||||
return $request->user()->downloadInvoice($id, [
|
||||
'vendor' => setting('name'),
|
||||
'product' => 'Webhosting'
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,6 @@ 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;
|
||||
|
||||
@@ -37,22 +36,4 @@ class ProfileController extends Controller
|
||||
|
||||
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)];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Profile;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\ProfileIntegrationRequest;
|
||||
use App\Models\UserProvider;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ProfileIntegrationController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
$providers = auth()->user()->providers()->latest()->get()->map(function($provider){
|
||||
return [
|
||||
'id' => $provider->id,
|
||||
'type' => $provider->type,
|
||||
'created_at' => $provider->created_at->format('Y-m-d H:i:s')
|
||||
];
|
||||
});
|
||||
|
||||
return inertia('Profile/Integrations', [
|
||||
'providers' => $providers,
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(ProfileIntegrationRequest $request)
|
||||
{
|
||||
$request->user()->providers()->updateOrCreate([
|
||||
'type' => UserProvider::TYPE_CLOUDFLARE
|
||||
],[
|
||||
'type' => UserProvider::TYPE_CLOUDFLARE,
|
||||
'token' => $request->input('meta.api_key'),
|
||||
'meta' => [
|
||||
'cloudflare_email' => encrypt($request->input('meta.cloudflare_email'))
|
||||
]
|
||||
]);
|
||||
|
||||
return redirect()->route('profile.integrations.index');
|
||||
}
|
||||
|
||||
public function destroy($providerId)
|
||||
{
|
||||
auth()->user()->providers()->findOrFail($providerId)->delete();
|
||||
|
||||
return redirect()->route('profile.integrations.index');
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
namespace App\Http\Controllers\Profile;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Rules\MatchOldPassword;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Rules\MatchOldPassword;
|
||||
use App\Http\Controllers\Controller;
|
||||
|
||||
class ProfilePasswordController extends Controller
|
||||
{
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
namespace App\Http\Controllers\Profile;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Validation\Rule;
|
||||
use App\Http\Controllers\Controller;
|
||||
|
||||
class ProfileSettingController extends Controller
|
||||
{
|
||||
@@ -12,7 +12,8 @@ class ProfileSettingController extends Controller
|
||||
{
|
||||
return inertia('Profile/Settings', [
|
||||
'profile' => [
|
||||
'theme' => auth()->user()->theme
|
||||
'theme' => auth()->user()->theme,
|
||||
'keyboard_shortcuts' => auth()->user()->keyboard_shortcuts,
|
||||
]
|
||||
]);
|
||||
}
|
||||
@@ -27,7 +28,10 @@ class ProfileSettingController extends Controller
|
||||
]
|
||||
]);
|
||||
|
||||
$request->user()->update(['theme' => $request->input('theme')]);
|
||||
$request->user()->update([
|
||||
'theme' => $request->input('theme'),
|
||||
'keyboard_shortcuts' => $request->input('keyboard_shortcuts', true),
|
||||
]);
|
||||
|
||||
return redirect()->route('profile.settings.index')->with('success', __('Instellingen zijn aangepast'));
|
||||
}
|
||||
|
||||
@@ -2,27 +2,63 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Jobs\Servers\DeleteServer;
|
||||
use App\Models\Server;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Jobs\Servers\CreateServer;
|
||||
use App\Jobs\Servers\DeleteServer;
|
||||
use App\Http\Requests\ServerRequest;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use App\Http\Resources\ServerResource;
|
||||
use App\Mail\Server\ServerCreatedEmail;
|
||||
use App\Http\Requests\ServerUpdateRequest;
|
||||
use App\Mail\Admin\Server\AdminServerCreatedEmail;
|
||||
|
||||
class ServerController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
$servers = auth()->user()->servers()->latest()->paginate();
|
||||
$servers = auth()->user()->servers()
|
||||
->withCount('sites')
|
||||
->latest()
|
||||
->paginate();
|
||||
|
||||
$providers = auth()->user()->package ? auth()->user()->package->providers()->pluck('name', 'id') : [];
|
||||
|
||||
return inertia('Servers/Index', [
|
||||
'servers' => ServerResource::collection($servers)
|
||||
'servers' => ServerResource::collection($servers),
|
||||
'dataProviders' => $providers
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
public function store(ServerRequest $request)
|
||||
{
|
||||
abort_if(!$request->user()->can('create', Server::class), 403);
|
||||
$provider = $request->user()->package->providers()->findOrFail($request->input('provider'));
|
||||
$region = $provider->regions()->findOrFail($request->input('region'));
|
||||
$plan = $provider->plans()->findOrFail($request->input('plan'));
|
||||
|
||||
// TODO
|
||||
/* @var $server \App\Models\Server */
|
||||
$server = $request->user()->servers()->create([
|
||||
'name' => $request->input('name')
|
||||
]);
|
||||
|
||||
$server->provider()->associate($provider);
|
||||
$server->providerRegion()->associate($region);
|
||||
$server->providerPlan()->associate($plan);
|
||||
$server->save();
|
||||
|
||||
dispatch(new CreateServer($server));
|
||||
|
||||
Mail::to($request->user())->send(new ServerCreatedEmail($request->user(), $server));
|
||||
|
||||
if (setting('receive_email_on_server_creation')) {
|
||||
$admins = User::query()->where('role', User::ADMIN)->get();
|
||||
|
||||
foreach ($admins as $admin) {
|
||||
Mail::to($admin)->send(new AdminServerCreatedEmail($request->user(), $server));
|
||||
}
|
||||
}
|
||||
|
||||
return redirect()->route('servers.index');
|
||||
}
|
||||
|
||||
public function show($id)
|
||||
@@ -35,10 +71,23 @@ class ServerController extends Controller
|
||||
]);
|
||||
}
|
||||
|
||||
public function update(ServerUpdateRequest $request, $id)
|
||||
{
|
||||
$server = $request->user()->servers()->findOrFail($id);
|
||||
|
||||
$server->update([
|
||||
'name' => $request->input('name')
|
||||
]);
|
||||
|
||||
return redirect()->route('servers.settings.show', $id)->with('success', __('Server information has been updated'));
|
||||
}
|
||||
|
||||
public function destroy(Request $request, $id)
|
||||
{
|
||||
$server = $request->user()->servers()->findOrFail($id);
|
||||
|
||||
$this->authorize('delete', $server);
|
||||
|
||||
dispatch(new DeleteServer($server->ploi_id));
|
||||
|
||||
$request->user()->systemLogs()->create([
|
||||
@@ -50,4 +99,14 @@ class ServerController extends Controller
|
||||
|
||||
return redirect()->route('servers.index')->with('success', __('Your server is deleted'));
|
||||
}
|
||||
|
||||
public function plansAndRegions(Request $request, $providerId)
|
||||
{
|
||||
$provider = $request->user()->package->providers()->findOrFail($providerId);
|
||||
|
||||
return [
|
||||
'regions' => $provider->regions()->pluck('label', 'id'),
|
||||
'plans' => $provider->plans()->pluck('label', 'id'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ServerSettingController extends Controller
|
||||
{
|
||||
public function show($id)
|
||||
|
||||
@@ -3,11 +3,13 @@
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Server;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Http\Request;
|
||||
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;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
|
||||
class SiteController extends Controller
|
||||
{
|
||||
@@ -37,9 +39,12 @@ class SiteController extends Controller
|
||||
$server = Server::query()
|
||||
->doesntHave('users')
|
||||
->withCount('sites')
|
||||
->having('sites_count', '<', DB::raw('maximum_sites'))
|
||||
->inRandomOrder()
|
||||
->first();
|
||||
|
||||
if ($server && $server->sites_count >= $server->maximum_sites) {
|
||||
$server = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$server) {
|
||||
@@ -72,13 +77,16 @@ class SiteController extends Controller
|
||||
|
||||
return inertia('Sites/Show', [
|
||||
'site' => $site,
|
||||
'system_user' => $site->getSystemUser(false),
|
||||
'ip_address' => $site->server->ip
|
||||
]);
|
||||
}
|
||||
|
||||
public function destroy($id)
|
||||
public function destroy(Request $request, $id)
|
||||
{
|
||||
$site = auth()->user()->sites()->findOrFail($id);
|
||||
$site = $request->user()->sites()->findOrFail($id);
|
||||
|
||||
$this->authorize('delete', $site);
|
||||
|
||||
dispatch(new DeleteSite($site->server->ploi_id, $site->ploi_id));
|
||||
|
||||
@@ -86,4 +94,28 @@ class SiteController extends Controller
|
||||
|
||||
return redirect()->route('sites.index')->with('success', __('Your website is deleted'));
|
||||
}
|
||||
|
||||
public function requestFtpPassword(Request $request, $id)
|
||||
{
|
||||
if ($request->user()->requires_password_for_ftp) {
|
||||
$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);
|
||||
}
|
||||
}
|
||||
|
||||
$site = $request->user()->sites()->findOrFail($id);
|
||||
|
||||
$systemUser = $site->getSystemUser();
|
||||
|
||||
return ['ftp_password' => decrypt(Arr::get($systemUser, 'ftp_password'))];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,12 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Requests\SiteDnsRequest;
|
||||
use App\Models\Site;
|
||||
use App\Models\UserProvider;
|
||||
use App\Services\Cloudflare;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
class SiteDnsController extends Controller
|
||||
{
|
||||
@@ -16,9 +21,18 @@ class SiteDnsController extends Controller
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
public function store(SiteDnsRequest $request, $id)
|
||||
{
|
||||
//
|
||||
$site = auth()->user()->sites()->findOrFail($id);
|
||||
|
||||
$dns = $this->getDnsInstance($site);
|
||||
|
||||
$dns->addRecord(
|
||||
$request->input('name'),
|
||||
$request->input('address'),
|
||||
);
|
||||
|
||||
return redirect()->route('sites.dns.index', $id)->with('success', __('DNS record has been created'));
|
||||
}
|
||||
|
||||
public function update(Request $request, $id)
|
||||
@@ -28,10 +42,31 @@ class SiteDnsController extends Controller
|
||||
|
||||
public function records($id)
|
||||
{
|
||||
$site = auth()->user()->sites()->findOrFail($id);
|
||||
|
||||
$dns = $this->getDnsInstance($site);
|
||||
|
||||
return $dns->listRecords();
|
||||
}
|
||||
|
||||
public function destroy($id)
|
||||
public function destroy($id, $recordId)
|
||||
{
|
||||
//
|
||||
$site = auth()->user()->sites()->findOrFail($id);
|
||||
|
||||
$dns = $this->getDnsInstance($site);
|
||||
|
||||
$dns->deleteRecord($recordId);
|
||||
|
||||
return redirect()->route('sites.dns.index', $id)->with('success', __('DNS record has been removed'));
|
||||
}
|
||||
|
||||
private function getDnsInstance(Site $site)
|
||||
{
|
||||
$provider = auth()->user()->providers()->where('type', UserProvider::TYPE_CLOUDFLARE)->first();
|
||||
|
||||
$cloudflare = new Cloudflare(decrypt(Arr::get($provider->meta, 'cloudflare_email')), decrypt($provider->token));
|
||||
$cloudflare->zone(decrypt($site->dns_id));
|
||||
|
||||
return $cloudflare;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@ class SiteSettingController extends Controller
|
||||
{
|
||||
$site = auth()->user()->sites()->findOrFail($id);
|
||||
|
||||
$this->authorize('update', $site);
|
||||
|
||||
$availablePhpVersions = $site->server->available_php_versions;
|
||||
|
||||
return inertia('Sites/Settings', [
|
||||
@@ -23,6 +25,8 @@ class SiteSettingController extends Controller
|
||||
{
|
||||
$site = $request->user()->sites()->findOrFail($id);
|
||||
|
||||
$this->authorize('update', $site);
|
||||
|
||||
$site->update($request->all());
|
||||
|
||||
return redirect()->route('sites.settings.show', $id)->with('success', __('Site settings have been updated'));
|
||||
@@ -32,6 +36,8 @@ class SiteSettingController extends Controller
|
||||
{
|
||||
$site = $request->user()->sites()->findOrFail($id);
|
||||
|
||||
$this->authorize('update', $site);
|
||||
|
||||
dispatch(new ChangePhpVersion($site, $request->input('version')));
|
||||
|
||||
$request->user()->systemLogs()->create([
|
||||
|
||||
@@ -45,6 +45,7 @@ class Kernel extends HttpKernel
|
||||
\App\Http\Middleware\SetLocale::class,
|
||||
\App\Http\Middleware\Demo::class,
|
||||
\App\Http\Middleware\InstallationComplete::class,
|
||||
\App\Http\Middleware\HandleInertiaRequests::class,
|
||||
],
|
||||
|
||||
'api' => [
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use App\Services\Ploi\Exceptions\Http\Unauthenticated;
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Services\Ploi\Exceptions\Http\Unauthenticated;
|
||||
|
||||
class GlobalApiAuthenticated
|
||||
{
|
||||
|
||||
126
app/Http/Middleware/HandleInertiaRequests.php
Normal file
126
app/Http/Middleware/HandleInertiaRequests.php
Normal file
@@ -0,0 +1,126 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use App\Models\Alert;
|
||||
use App\Models\UserProvider;
|
||||
use Inertia\Middleware;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
|
||||
class HandleInertiaRequests extends Middleware
|
||||
{
|
||||
/**
|
||||
* Determines the current asset version.
|
||||
*
|
||||
* @see https://inertiajs.com/asset-versioning
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return string|null
|
||||
*/
|
||||
public function version(Request $request)
|
||||
{
|
||||
return parent::version($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the props that are shared by default.
|
||||
*
|
||||
* @see https://inertiajs.com/shared-data
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return array
|
||||
*/
|
||||
public function share(Request $request)
|
||||
{
|
||||
return array_merge(parent::share($request), [
|
||||
'auth' => function () use ($request) {
|
||||
$package = auth()->user()->package ?? [];
|
||||
|
||||
$can = $package ? [
|
||||
'servers' => [
|
||||
'create' => Arr::get($package->server_permissions, 'create', false),
|
||||
'update' => Arr::get($package->server_permissions, 'update', false),
|
||||
'delete' => Arr::get($package->server_permissions, 'delete', false),
|
||||
],
|
||||
'sites' => [
|
||||
'create' => Arr::get($package->site_permissions, 'create', false),
|
||||
'update' => Arr::get($package->site_permissions, 'update', false),
|
||||
'delete' => Arr::get($package->site_permissions, 'delete', false),
|
||||
]
|
||||
] : [];
|
||||
|
||||
return [
|
||||
'user' => Auth::user() ? [
|
||||
'id' => Auth::user()->id,
|
||||
'name' => Auth::user()->name,
|
||||
'email' => Auth::user()->email,
|
||||
'role' => Auth::user()->role,
|
||||
'user_name' => Auth::user()->user_name,
|
||||
'avatar' => Auth::user()->getGravatar(),
|
||||
'theme' => Auth::user()->theme,
|
||||
'keyboard_shortcuts' => Auth::user()->keyboard_shortcuts,
|
||||
'requires_password_for_ftp' => Auth::user()->requires_password_for_ftp,
|
||||
] : null,
|
||||
'package' => auth()->user() && auth()->user()->package ? [
|
||||
'name' => auth()->user()->package->name,
|
||||
'maximum_sites' => auth()->user()->package->maximum_sites
|
||||
] : [
|
||||
'name' => __('None')
|
||||
],
|
||||
'can' => $can,
|
||||
'integrations' => [
|
||||
'cloudflare' => (bool)auth()->user() ? auth()->user()->providers()->where('type', UserProvider::TYPE_CLOUDFLARE)->count() : false,
|
||||
]
|
||||
];
|
||||
},
|
||||
|
||||
'settings' => function () {
|
||||
return [
|
||||
'demo' => config('app.demo'),
|
||||
'name' => setting('name', 'Company'),
|
||||
'support' => setting('support', false),
|
||||
'documentation' => setting('documentation', false),
|
||||
'logo' => setting('logo'),
|
||||
'allow_registration' => setting('allow_registration'),
|
||||
'billing' => config('cashier.key') && config('cashier.secret')
|
||||
];
|
||||
},
|
||||
'flash' => function () {
|
||||
return [
|
||||
'success' => Session::get('success'),
|
||||
'error' => Session::get('error'),
|
||||
'info' => Session::get('info'),
|
||||
];
|
||||
},
|
||||
'errors' => function () {
|
||||
return Session::get('errors')
|
||||
? Session::get('errors')->getBag('default')->getMessages()
|
||||
: (object)[];
|
||||
},
|
||||
'errors_count' => function () {
|
||||
return Session::get('errors')
|
||||
? count(Session::get('errors')->getBag('default')->getMessages())
|
||||
: 0;
|
||||
},
|
||||
'system_alert' => function () {
|
||||
$alert = Alert::query()
|
||||
->where(function ($query) {
|
||||
return $query
|
||||
->whereNull('expires_at')
|
||||
->orWhere('expires_at', '>', now());
|
||||
})
|
||||
->first(['message', 'expires_at', 'type']);
|
||||
|
||||
if (!$alert) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [
|
||||
'message' => $alert->message,
|
||||
'type' => $alert->type
|
||||
];
|
||||
}
|
||||
]);
|
||||
}
|
||||
}
|
||||
50
app/Http/Requests/Admin/AlertRequest.php
Normal file
50
app/Http/Requests/Admin/AlertRequest.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Admin;
|
||||
|
||||
use App\Models\Alert;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class AlertRequest 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 [
|
||||
'message' => [
|
||||
'required',
|
||||
'string',
|
||||
'max:500'
|
||||
],
|
||||
'type' => [
|
||||
'required',
|
||||
'string',
|
||||
Rule::in([
|
||||
Alert::TYPE_INFO,
|
||||
Alert::TYPE_DANGER,
|
||||
Alert::TYPE_WARNING
|
||||
])
|
||||
],
|
||||
'expires_at' => [
|
||||
'nullable',
|
||||
'date',
|
||||
'date_format:Y-m-d H:i:s'
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
||||
42
app/Http/Requests/Admin/DocumentationArticleRequest.php
Normal file
42
app/Http/Requests/Admin/DocumentationArticleRequest.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Admin;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class DocumentationArticleRequest 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 [
|
||||
'title' => [
|
||||
'required',
|
||||
'string',
|
||||
'max:255'
|
||||
],
|
||||
'content' => [
|
||||
'required',
|
||||
'min:2'
|
||||
],
|
||||
'category_id' => [
|
||||
'required',
|
||||
'exists:documentation_categories,id'
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
||||
38
app/Http/Requests/Admin/DocumentationCategoryRequest.php
Normal file
38
app/Http/Requests/Admin/DocumentationCategoryRequest.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Admin;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class DocumentationCategoryRequest 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 [
|
||||
'title' => [
|
||||
'required',
|
||||
'string',
|
||||
'max:255'
|
||||
],
|
||||
'description' => [
|
||||
'nullable',
|
||||
'string'
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
namespace App\Http\Requests\Admin;
|
||||
|
||||
use App\Models\Package;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class PackageRequest extends FormRequest
|
||||
@@ -29,15 +31,56 @@ class PackageRequest extends FormRequest
|
||||
'string',
|
||||
'max:255'
|
||||
],
|
||||
'currency' => [
|
||||
'nullable',
|
||||
Rule::in([
|
||||
Package::CURRENCY_USD,
|
||||
Package::CURRENCY_EURO,
|
||||
Package::CURRENCY_NOK,
|
||||
Package::CURRENCY_AUD,
|
||||
Package::CURRENCY_CAD,
|
||||
])
|
||||
],
|
||||
'maximum_sites' => [
|
||||
'required',
|
||||
'numeric',
|
||||
'min:0',
|
||||
],
|
||||
'server_creation' => [
|
||||
'maximum_servers' => [
|
||||
'required',
|
||||
'boolean'
|
||||
'numeric',
|
||||
'min:0',
|
||||
],
|
||||
'plan_id' => [
|
||||
'nullable',
|
||||
],
|
||||
'price_monthly' => [
|
||||
'nullable',
|
||||
'numeric'
|
||||
],
|
||||
'server_permissions' => [
|
||||
'array'
|
||||
],
|
||||
'site_permissions' => [
|
||||
'array'
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
protected function prepareForValidation()
|
||||
{
|
||||
$merge = [];
|
||||
|
||||
// If we don't have the monthly price filled in, merge a default
|
||||
if (!$this->price_monthly) {
|
||||
$merge['price_monthly'] = 0.000;
|
||||
}
|
||||
|
||||
// If we don't have the currency filled in, merge a default
|
||||
if (!$this->price_monthly) {
|
||||
$merge['currency'] = Package::CURRENCY_USD;
|
||||
}
|
||||
|
||||
$this->merge($merge);
|
||||
}
|
||||
}
|
||||
|
||||
34
app/Http/Requests/Admin/ProviderRequest.php
Normal file
34
app/Http/Requests/Admin/ProviderRequest.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Admin;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class ProviderRequest 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'
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
||||
63
app/Http/Requests/ProfileIntegrationRequest.php
Normal file
63
app/Http/Requests/ProfileIntegrationRequest.php
Normal file
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use App\Models\UserProvider;
|
||||
use App\Rules\CloudflareGeneralTest;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class ProfileIntegrationRequest 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()
|
||||
{
|
||||
$rules = [
|
||||
'provider' => [
|
||||
'required',
|
||||
'string',
|
||||
Rule::in([
|
||||
UserProvider::TYPE_CLOUDFLARE
|
||||
])
|
||||
]
|
||||
];
|
||||
|
||||
if ($this->input('provider') === UserProvider::TYPE_CLOUDFLARE) {
|
||||
$rules['meta.api_key'] = [
|
||||
'required',
|
||||
'string',
|
||||
new CloudflareGeneralTest($this->input('meta.cloudflare_email'))
|
||||
];
|
||||
|
||||
$rules['meta.cloudflare_email'] = [
|
||||
'required',
|
||||
'string',
|
||||
'email'
|
||||
];
|
||||
}
|
||||
|
||||
return $rules;
|
||||
}
|
||||
|
||||
public function messages()
|
||||
{
|
||||
return [
|
||||
'meta.api_key.required' => __('The API key field is required'),
|
||||
'meta.cloudflare_email.required' => __('The Cloudflare email field is required'),
|
||||
];
|
||||
}
|
||||
}
|
||||
51
app/Http/Requests/ServerRequest.php
Normal file
51
app/Http/Requests/ServerRequest.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use App\Models\Server;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class ServerRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize()
|
||||
{
|
||||
return $this->user()->can('create', Server::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'name' => [
|
||||
'required',
|
||||
'string',
|
||||
'alpha_dash',
|
||||
'max:40'
|
||||
],
|
||||
'provider' => [
|
||||
'required',
|
||||
'not_in:0',
|
||||
'exists:provider_plans,id'
|
||||
],
|
||||
'region' => [
|
||||
'required',
|
||||
'not_in:0',
|
||||
'exists:provider_regions,id'
|
||||
],
|
||||
'plan' => [
|
||||
'required',
|
||||
'not_in:0',
|
||||
'exists:provider_plans,id'
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
||||
36
app/Http/Requests/ServerUpdateRequest.php
Normal file
36
app/Http/Requests/ServerUpdateRequest.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use App\Models\Server;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class ServerUpdateRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize()
|
||||
{
|
||||
return $this->user()->can('update', Server::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'name' => [
|
||||
'required',
|
||||
'string',
|
||||
'alpha_dash',
|
||||
'max:40'
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
||||
37
app/Http/Requests/SiteDnsRequest.php
Normal file
37
app/Http/Requests/SiteDnsRequest.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class SiteDnsRequest 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',
|
||||
],
|
||||
'address' => [
|
||||
'required',
|
||||
'ipv4'
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use App\Models\Site;
|
||||
use App\Rules\Hostname;
|
||||
use App\Rules\ValidateMaximumSites;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
@@ -15,7 +16,7 @@ class SiteRequest extends FormRequest
|
||||
*/
|
||||
public function authorize()
|
||||
{
|
||||
return auth()->check();
|
||||
return $this->user()->can('create', Site::class);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace App\Jobs\Core;
|
||||
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
@@ -12,26 +13,13 @@ 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');
|
||||
|
||||
Artisan::call('horizon:terminate');
|
||||
|
||||
cache()->forget('ploi-core-current-version');
|
||||
}
|
||||
}
|
||||
|
||||
57
app/Jobs/Servers/CreateServer.php
Normal file
57
app/Jobs/Servers/CreateServer.php
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs\Servers;
|
||||
|
||||
use App\Models\Server;
|
||||
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 CreateServer implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public $server;
|
||||
public $tries = 1;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
* @param Server $server
|
||||
*/
|
||||
public function __construct(Server $server)
|
||||
{
|
||||
$this->server = $server;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$ploi = new Ploi(config('services.ploi.token'));
|
||||
|
||||
$ploiServer = $ploi->server()->create(
|
||||
$this->server->name,
|
||||
$this->server->provider->ploi_id,
|
||||
$this->server->providerRegion->region_id,
|
||||
$this->server->providerPlan->plan_id,
|
||||
);
|
||||
|
||||
$this->server->ploi_id = $ploiServer->id;
|
||||
$this->server->save();
|
||||
|
||||
// Lets fetch the status after 5 minutes
|
||||
dispatch(new FetchServerStatus($this->server))->delay(now()->addMinutes(5));
|
||||
}
|
||||
|
||||
public function failed(\Exception $exception)
|
||||
{
|
||||
$this->server->delete();
|
||||
}
|
||||
}
|
||||
@@ -4,10 +4,10 @@ namespace App\Jobs\Servers;
|
||||
|
||||
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;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class DeleteServer implements ShouldQueue
|
||||
{
|
||||
|
||||
70
app/Jobs/Servers/FetchServerStatus.php
Normal file
70
app/Jobs/Servers/FetchServerStatus.php
Normal file
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs\Servers;
|
||||
|
||||
use App\Models\Server;
|
||||
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 FetchServerStatus implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, JobHasThresholds;
|
||||
|
||||
public $server;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
* @param Server $server
|
||||
* @param int $threshold
|
||||
*/
|
||||
public function __construct(Server $server, $threshold = 0)
|
||||
{
|
||||
$this->server = $server;
|
||||
$this->setThreshold($threshold);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
// If we tried over 10 times, game over, delete the site
|
||||
if ($this->hasReachedThresholdLimit(10)) {
|
||||
$this->server->delete();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$ploi = new Ploi;
|
||||
|
||||
$ploiServer = $ploi->server($this->server->ploi_id)->get()->getData();
|
||||
|
||||
if ($ploiServer->status !== Server::STATUS_ACTIVE) {
|
||||
$this->incrementThreshold();
|
||||
|
||||
dispatch(new self($this->server, $this->threshold))->delay(now()->addMinutes(2));
|
||||
|
||||
// Check if an IP address is present already
|
||||
if ($ploiServer->ip_address && !$this->server->ip) {
|
||||
$this->server->ip = $ploiServer->ip_address;
|
||||
$this->server->save();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->server->status = $ploiServer->status;
|
||||
$this->server->ip = $ploiServer->ip_address;
|
||||
$this->server->internal_ip = $ploiServer->internal_ip;
|
||||
$this->server->available_php_versions = $ploiServer->installed_php_versions;
|
||||
$this->server->save();
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ namespace App\Jobs\Sites;
|
||||
|
||||
use App\Models\Site;
|
||||
use App\Services\Ploi\Ploi;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
@@ -35,14 +36,14 @@ class CreateSite implements ShouldQueue
|
||||
{
|
||||
$ploi = new Ploi(config('services.ploi.token'));
|
||||
|
||||
$owner = $this->site->users()->first();
|
||||
$systemUser = $this->site->getSystemUser();
|
||||
|
||||
$ploiSite = $ploi->server($this->site->server->ploi_id)->sites()->create(
|
||||
$this->site->domain,
|
||||
'/public',
|
||||
'/',
|
||||
$owner->user_name,
|
||||
decrypt($owner->ftp_password)
|
||||
Arr::get($systemUser, 'user_name'),
|
||||
decrypt(Arr::get($systemUser, 'ftp_password'))
|
||||
);
|
||||
|
||||
$this->site->ploi_id = $ploiSite->data->id;
|
||||
|
||||
@@ -43,7 +43,7 @@ class FetchSiteStatus implements ShouldQueue
|
||||
return;
|
||||
}
|
||||
|
||||
$ploi = new Ploi(config('services.ploi.token'));
|
||||
$ploi = new Ploi;
|
||||
|
||||
$ploiSite = $ploi->server($this->site->server->ploi_id)->sites()->get($this->site->ploi_id)->getData();
|
||||
|
||||
|
||||
42
app/Mail/Admin/Server/AdminServerCreatedEmail.php
Normal file
42
app/Mail/Admin/Server/AdminServerCreatedEmail.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace App\Mail\Admin\Server;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Models\Server;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Mail\Mailable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
|
||||
class AdminServerCreatedEmail extends Mailable implements ShouldQueue
|
||||
{
|
||||
use Queueable, SerializesModels;
|
||||
|
||||
public $user;
|
||||
public $server;
|
||||
|
||||
/**
|
||||
* Create a new message instance.
|
||||
*
|
||||
* @param User $user
|
||||
* @param Server $server
|
||||
*/
|
||||
public function __construct(User $user, Server $server)
|
||||
{
|
||||
$this->user = $user;
|
||||
$this->server = $server;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the message.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function build()
|
||||
{
|
||||
return $this
|
||||
->subject(__('A user has created a new server'))
|
||||
->markdown('emails.admin.server.new-server');
|
||||
}
|
||||
}
|
||||
42
app/Mail/Server/ServerCreatedEmail.php
Normal file
42
app/Mail/Server/ServerCreatedEmail.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace App\Mail\Server;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Models\Server;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Mail\Mailable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
|
||||
class ServerCreatedEmail extends Mailable implements ShouldQueue
|
||||
{
|
||||
use Queueable, SerializesModels;
|
||||
|
||||
public $user;
|
||||
public $server;
|
||||
|
||||
/**
|
||||
* Create a new message instance.
|
||||
*
|
||||
* @param User $user
|
||||
* @param Server $server
|
||||
*/
|
||||
public function __construct(User $user, Server $server)
|
||||
{
|
||||
$this->user = $user;
|
||||
$this->server = $server;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the message.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function build()
|
||||
{
|
||||
return $this
|
||||
->subject(__('Your new server is being created'))
|
||||
->markdown('emails.server.new-server');
|
||||
}
|
||||
}
|
||||
@@ -4,10 +4,10 @@ namespace App\Mail\User;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Mail\Mailable;
|
||||
use Illuminate\Support\Facades\URL;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
|
||||
class WelcomeEmail extends Mailable implements ShouldQueue
|
||||
{
|
||||
|
||||
28
app/Models/Alert.php
Normal file
28
app/Models/Alert.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use DateTimeInterface;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Alert extends Model
|
||||
{
|
||||
const TYPE_INFO = 'info';
|
||||
const TYPE_WARNING = 'warning';
|
||||
const TYPE_DANGER = 'danger';
|
||||
|
||||
public $fillable = [
|
||||
'type',
|
||||
'message',
|
||||
'expires_at'
|
||||
];
|
||||
|
||||
public $dates = [
|
||||
'expires_at'
|
||||
];
|
||||
|
||||
protected function serializeDate(DateTimeInterface $date)
|
||||
{
|
||||
return $date->format('Y-m-d H:i:s');
|
||||
}
|
||||
}
|
||||
18
app/Models/DocumentationCategory.php
Normal file
18
app/Models/DocumentationCategory.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class DocumentationCategory extends Model
|
||||
{
|
||||
public $fillable = [
|
||||
'title',
|
||||
'description'
|
||||
];
|
||||
|
||||
public function items()
|
||||
{
|
||||
return $this->hasMany(DocumentationItem::class);
|
||||
}
|
||||
}
|
||||
@@ -6,5 +6,13 @@ use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class DocumentationItem extends Model
|
||||
{
|
||||
//
|
||||
public $fillable = [
|
||||
'title',
|
||||
'content'
|
||||
];
|
||||
|
||||
public function category()
|
||||
{
|
||||
return $this->belongsTo(DocumentationCategory::class, 'documentation_category_id');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,19 +2,32 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Casts\PermissionCast;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Package extends Model
|
||||
{
|
||||
const CURRENCY_EURO = 'eur';
|
||||
const CURRENCY_USD = 'usd';
|
||||
const CURRENCY_NOK = 'nok';
|
||||
const CURRENCY_AUD = 'aud';
|
||||
const CURRENCY_CAD = 'cad';
|
||||
|
||||
public $fillable = [
|
||||
'name',
|
||||
'plan_id', // This does not reflect a internal database relation, it reflects the plan ID from the PSP
|
||||
'currency',
|
||||
'price_hourly',
|
||||
'price_monthly',
|
||||
'maximum_sites',
|
||||
'maximum_servers',
|
||||
'server_creation'
|
||||
'site_permissions',
|
||||
'server_permissions'
|
||||
];
|
||||
|
||||
public $casts = [
|
||||
'server_creation' => 'boolean'
|
||||
'site_permissions' => PermissionCast::class,
|
||||
'server_permissions' => PermissionCast::class,
|
||||
];
|
||||
|
||||
public function users()
|
||||
@@ -22,6 +35,11 @@ class Package extends Model
|
||||
return $this->hasMany(User::class);
|
||||
}
|
||||
|
||||
public function providers()
|
||||
{
|
||||
return $this->belongsToMany(Provider::class);
|
||||
}
|
||||
|
||||
protected static function booted()
|
||||
{
|
||||
static::deleting(function ($package) {
|
||||
|
||||
@@ -8,6 +8,17 @@ class Provider extends Model
|
||||
{
|
||||
protected $guarded = [];
|
||||
|
||||
public function getNameWithLabelAttribute()
|
||||
{
|
||||
$string = $this->name;
|
||||
|
||||
if ($this->label) {
|
||||
$string .= ' (' . $this->label . ')';
|
||||
}
|
||||
|
||||
return $string;
|
||||
}
|
||||
|
||||
public function plans()
|
||||
{
|
||||
return $this->hasMany(ProviderPlan::class);
|
||||
@@ -17,4 +28,30 @@ class Provider extends Model
|
||||
{
|
||||
return $this->hasMany(ProviderRegion::class);
|
||||
}
|
||||
|
||||
public function packages()
|
||||
{
|
||||
return $this->belongsToMany(Package::class);
|
||||
}
|
||||
|
||||
public function servers()
|
||||
{
|
||||
return $this->hasMany(Server::class);
|
||||
}
|
||||
|
||||
public static function booted()
|
||||
{
|
||||
static::deleting(function(self $provider){
|
||||
$provider->regions()->delete();
|
||||
$provider->plans()->delete();
|
||||
$provider->packages()->detach();
|
||||
|
||||
foreach($provider->servers as $server){
|
||||
$server->provider_id = null;
|
||||
$server->provider_plan_id = null;
|
||||
$server->provider_region_id = null;
|
||||
$server->save();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,6 +41,21 @@ class Server extends Model
|
||||
return $this->morphMany(SystemLog::class, 'model');
|
||||
}
|
||||
|
||||
public function provider()
|
||||
{
|
||||
return $this->belongsTo(Provider::class);
|
||||
}
|
||||
|
||||
public function providerRegion()
|
||||
{
|
||||
return $this->belongsTo(ProviderRegion::class);
|
||||
}
|
||||
|
||||
public function providerPlan()
|
||||
{
|
||||
return $this->belongsTo(ProviderPlan::class);
|
||||
}
|
||||
|
||||
public static function booted()
|
||||
{
|
||||
static::deleting(function (self $server) {
|
||||
|
||||
@@ -65,6 +65,11 @@ class Site extends Model
|
||||
return $this->morphMany(SystemLog::class, 'model');
|
||||
}
|
||||
|
||||
public function systemUsers()
|
||||
{
|
||||
return $this->belongsToMany(SiteSystemUser::class, 'site_system_user_attached');
|
||||
}
|
||||
|
||||
protected function serializeDate(DateTimeInterface $date)
|
||||
{
|
||||
return $date->format('Y-m-d H:i:s');
|
||||
@@ -75,12 +80,42 @@ class Site extends Model
|
||||
return $this->status === self::STATUS_ACTIVE;
|
||||
}
|
||||
|
||||
public function getSystemUser($withPassword = true)
|
||||
{
|
||||
if (setting('isolate_per_site_per_user') && $this->systemUsers()->first()) {
|
||||
$user = $this->systemUsers()->first();
|
||||
} else {
|
||||
$user = $this->users()->first();
|
||||
}
|
||||
|
||||
return [
|
||||
'user_name' => $user->user_name,
|
||||
] + ($withPassword ? ['ftp_password' => $user->ftp_password] : []);
|
||||
}
|
||||
|
||||
public static function booted()
|
||||
{
|
||||
static::created(function (self $site) {
|
||||
$site->systemUsers()->create();
|
||||
});
|
||||
|
||||
static::deleting(function (self $site) {
|
||||
foreach ($site->databases as $database) {
|
||||
$database->delete();
|
||||
}
|
||||
|
||||
$ids = $site->systemUsers->pluck('id');
|
||||
// Detach all db users
|
||||
$site->systemUsers()->detach();
|
||||
|
||||
// Loop through ids an remove old users.
|
||||
foreach ($ids as $id) {
|
||||
$record = SiteSystemUser::find($id);
|
||||
if ($record) {
|
||||
$record->delete();
|
||||
}
|
||||
}
|
||||
|
||||
$site->redirects()->delete();
|
||||
$site->cronjobs()->delete();
|
||||
$site->certificates()->delete();
|
||||
|
||||
37
app/Models/SiteSystemUser.php
Normal file
37
app/Models/SiteSystemUser.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Casts\Encrypted;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class SiteSystemUser extends Model
|
||||
{
|
||||
public $fillable = [
|
||||
'user_name',
|
||||
'ftp_password'
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'ftp_password' => Encrypted::class,
|
||||
];
|
||||
|
||||
public function site()
|
||||
{
|
||||
return $this->belongsToMany(Site::class, 'site_system_user_attached');
|
||||
}
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
protected static function booted()
|
||||
{
|
||||
static::creating(function (self $siteSystemUser) {
|
||||
$siteSystemUser->user_name = strtolower(Str::random(10));
|
||||
$siteSystemUser->ftp_password = Str::random();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -3,17 +3,18 @@
|
||||
namespace App\Models;
|
||||
|
||||
use App\Casts\Encrypted;
|
||||
use App\Mail\User\WelcomeEmail;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use Illuminate\Support\Str;
|
||||
use Laravel\Cashier\Billable;
|
||||
use App\Mail\User\WelcomeEmail;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Contracts\Translation\HasLocalePreference;
|
||||
|
||||
class User extends Authenticatable implements HasLocalePreference
|
||||
{
|
||||
use Notifiable;
|
||||
use Billable, Notifiable;
|
||||
|
||||
const ADMIN = 'admin';
|
||||
const RESELLER = 'reseller';
|
||||
@@ -30,7 +31,9 @@ class User extends Authenticatable implements HasLocalePreference
|
||||
'notes',
|
||||
'language',
|
||||
'blocked',
|
||||
'theme'
|
||||
'theme',
|
||||
'keyboard_shortcuts',
|
||||
'requires_password_for_ftp'
|
||||
];
|
||||
|
||||
protected $hidden = [
|
||||
@@ -42,6 +45,8 @@ class User extends Authenticatable implements HasLocalePreference
|
||||
protected $casts = [
|
||||
'email_verified_at' => 'datetime',
|
||||
'ftp_password' => Encrypted::class,
|
||||
'keyboard_shortcuts' => 'boolean',
|
||||
'requires_password_for_ftp' => 'boolean'
|
||||
];
|
||||
|
||||
protected $appends = [
|
||||
@@ -119,13 +124,17 @@ class User extends Authenticatable implements HasLocalePreference
|
||||
static::creating(function (self $user) {
|
||||
$user->user_name = strtolower(Str::random(10));
|
||||
$user->ftp_password = Str::random();
|
||||
|
||||
if (!$user->language) {
|
||||
$user->language = setting('default_language', 'en');
|
||||
}
|
||||
});
|
||||
|
||||
static::created(function (self $user) {
|
||||
Mail::to($user)->send(new WelcomeEmail($user));
|
||||
});
|
||||
|
||||
static::deleting(function(self $user){
|
||||
static::deleting(function (self $user) {
|
||||
$user->systemLogs()->delete();
|
||||
$user->servers()->detach();
|
||||
$user->sites()->detach();
|
||||
|
||||
@@ -2,16 +2,29 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Casts\Encrypted;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class UserProvider extends Model
|
||||
{
|
||||
const TYPE_DNS = 'dns';
|
||||
const TYPE_CLOUDFLARE = 'cloudflare';
|
||||
|
||||
public $hidden = [
|
||||
'token'
|
||||
];
|
||||
|
||||
public $fillable = [
|
||||
'type',
|
||||
'token',
|
||||
'meta'
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'meta' => 'array',
|
||||
'token' => Encrypted::class,
|
||||
];
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
|
||||
@@ -3,20 +3,25 @@
|
||||
namespace App\Policies;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Auth\Access\HandlesAuthorization;
|
||||
|
||||
class ServerPolicy
|
||||
{
|
||||
use HandlesAuthorization;
|
||||
|
||||
/**
|
||||
* Determine whether the user can create models.
|
||||
*
|
||||
* @param \App\Models\User $user
|
||||
* @return mixed
|
||||
*/
|
||||
public function create(User $user)
|
||||
{
|
||||
return $user->package->server_creation ?? false;
|
||||
return Arr::get($user->package->server_permissions, 'create', false);
|
||||
}
|
||||
|
||||
public function update(User $user)
|
||||
{
|
||||
return Arr::get($user->package->server_permissions, 'update', false);
|
||||
}
|
||||
|
||||
public function delete(User $user)
|
||||
{
|
||||
return Arr::get($user->package->server_permissions, 'delete', false);
|
||||
}
|
||||
}
|
||||
|
||||
27
app/Policies/SitePolicy.php
Normal file
27
app/Policies/SitePolicy.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Auth\Access\HandlesAuthorization;
|
||||
|
||||
class SitePolicy
|
||||
{
|
||||
use HandlesAuthorization;
|
||||
|
||||
public function create(User $user)
|
||||
{
|
||||
return Arr::get($user->package->site_permissions, 'create', false);
|
||||
}
|
||||
|
||||
public function update(User $user)
|
||||
{
|
||||
return Arr::get($user->package->site_permissions, 'update', false);
|
||||
}
|
||||
|
||||
public function delete(User $user)
|
||||
{
|
||||
return Arr::get($user->package->site_permissions, 'delete', false);
|
||||
}
|
||||
}
|
||||
@@ -5,9 +5,7 @@ namespace App\Providers;
|
||||
use App\Models\Setting;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Pagination\UrlWindow;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Request;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
|
||||
@@ -31,68 +29,10 @@ class AppServiceProvider extends ServiceProvider
|
||||
});
|
||||
|
||||
if (!$this->app->request->is('api*')) {
|
||||
$this->registerInertia();
|
||||
$this->registerLengthAwarePaginator();
|
||||
}
|
||||
}
|
||||
|
||||
public function registerInertia()
|
||||
{
|
||||
inertia()->version(function () {
|
||||
return md5_file(public_path('mix-manifest.json'));
|
||||
});
|
||||
|
||||
inertia()->share([
|
||||
'auth' => function () {
|
||||
return [
|
||||
'user' => Auth::user() ? [
|
||||
'id' => Auth::user()->id,
|
||||
'name' => Auth::user()->name,
|
||||
'email' => Auth::user()->email,
|
||||
'role' => Auth::user()->role,
|
||||
'user_name' => Auth::user()->user_name,
|
||||
'avatar' => Auth::user()->getGravatar(),
|
||||
'theme' => Auth::user()->theme,
|
||||
] : null,
|
||||
'package' => auth()->user() && auth()->user()->package ? [
|
||||
'maximum_sites' => auth()->user()->package->maximum_sites
|
||||
] : null,
|
||||
'can' => auth()->user() && auth()->user()->package ? [
|
||||
'server_creation' => auth()->user()->package->server_creation
|
||||
] : [],
|
||||
];
|
||||
},
|
||||
|
||||
'settings' => function () {
|
||||
return [
|
||||
'demo' => config('app.demo'),
|
||||
'name' => setting('name', 'Company'),
|
||||
'support' => setting('support', false),
|
||||
'documentation' => setting('documentation', false),
|
||||
'logo' => setting('logo'),
|
||||
'allow_registration' => setting('allow_registration'),
|
||||
];
|
||||
},
|
||||
'flash' => function () {
|
||||
return [
|
||||
'success' => Session::get('success'),
|
||||
'error' => Session::get('error'),
|
||||
'info' => Session::get('info'),
|
||||
];
|
||||
},
|
||||
'errors' => function () {
|
||||
return Session::get('errors')
|
||||
? Session::get('errors')->getBag('default')->getMessages()
|
||||
: (object)[];
|
||||
},
|
||||
'errors_count' => function () {
|
||||
return Session::get('errors')
|
||||
? count(Session::get('errors')->getBag('default')->getMessages())
|
||||
: 0;
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
protected function registerLengthAwarePaginator()
|
||||
{
|
||||
$this->app->bind(LengthAwarePaginator::class, function ($app, $values) {
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Models\Site;
|
||||
use App\Models\Server;
|
||||
use App\Policies\SitePolicy;
|
||||
use App\Policies\ServerPolicy;
|
||||
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
|
||||
|
||||
@@ -15,6 +17,7 @@ class AuthServiceProvider extends ServiceProvider
|
||||
*/
|
||||
protected $policies = [
|
||||
Server::class => ServerPolicy::class,
|
||||
Site::class => SitePolicy::class,
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use Illuminate\Cache\RateLimiting\Limit;
|
||||
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\RateLimiter;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Illuminate\Cache\RateLimiting\Limit;
|
||||
use Illuminate\Support\Facades\RateLimiter;
|
||||
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
|
||||
|
||||
class RouteServiceProvider extends ServiceProvider
|
||||
{
|
||||
|
||||
58
app/Rules/CloudflareGeneralTest.php
Normal file
58
app/Rules/CloudflareGeneralTest.php
Normal file
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace App\Rules;
|
||||
|
||||
use App\Services\Cloudflare;
|
||||
use Illuminate\Contracts\Validation\Rule;
|
||||
|
||||
class CloudflareGeneralTest implements Rule
|
||||
{
|
||||
public $cloudflareEmail;
|
||||
|
||||
/**
|
||||
* Create a new rule instance.
|
||||
*
|
||||
* @param $cloudflareEmail
|
||||
*/
|
||||
public function __construct($cloudflareEmail)
|
||||
{
|
||||
$this->cloudflareEmail = $cloudflareEmail;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the validation rule passes.
|
||||
*
|
||||
* @param string $attribute
|
||||
* @param mixed $value
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function passes($attribute, $value)
|
||||
{
|
||||
if (!$this->cloudflareEmail) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
$cloudflare = new Cloudflare($this->cloudflareEmail, $value);
|
||||
|
||||
if ($cloudflare->user()) {
|
||||
return true;
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation error message.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function message()
|
||||
{
|
||||
return __('We could not authenticate you with Cloudflare, are you sure this is the right API key? Also make sure your profile e-mail matches the one in Cloudflare.');
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
namespace App\Rules;
|
||||
|
||||
use Illuminate\Contracts\Validation\Rule;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Contracts\Validation\Rule;
|
||||
|
||||
class MatchOldPassword implements Rule
|
||||
{
|
||||
|
||||
140
app/Services/Cloudflare.php
Normal file
140
app/Services/Cloudflare.php
Normal file
@@ -0,0 +1,140 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use GuzzleHttp\Exception\ClientException;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class Cloudflare
|
||||
{
|
||||
public $adapter;
|
||||
public $zoneId;
|
||||
|
||||
public function __construct($email, $key)
|
||||
{
|
||||
$key = new \Cloudflare\API\Auth\APIKey($email, $key);
|
||||
$this->adapter = new \Cloudflare\API\Adapter\Guzzle($key);
|
||||
}
|
||||
|
||||
public function domains($match = '')
|
||||
{
|
||||
$zones = new \Cloudflare\API\Endpoints\Zones($this->adapter);
|
||||
|
||||
return collect(object_get($zones->listZones($match), 'result'));
|
||||
}
|
||||
|
||||
public function zone($zoneId)
|
||||
{
|
||||
$this->zoneId = $zoneId;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function listRecords($page = 1, $perPage = 50, $order = '', $direction = '', $type = '', $name = '', $content = '', $match = 'all')
|
||||
{
|
||||
$dns = new \Cloudflare\API\Endpoints\DNS($this->adapter);
|
||||
|
||||
if (!$dns || !$this->zoneId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return collect($dns->listRecords($this->zoneId, $type, $name, $content, $page, $perPage, $order, $direction, $match)->result)
|
||||
->map(function ($record) {
|
||||
// We add this property so our UI panel can see whether a record is being edited.
|
||||
$record->edit = false;
|
||||
|
||||
$record->display_content = Str::limit($record->content, 25);
|
||||
|
||||
return $record;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param null $content
|
||||
* @param string $type
|
||||
* @param int $ttl
|
||||
* @param bool $proxied
|
||||
* @param int $priority
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function addRecord($name, $content = null, $type = 'A', $ttl = 0, $proxied = true, $priority = '0')
|
||||
{
|
||||
if ($content == null && $type = 'A') {
|
||||
$content = $_SERVER['SERVER_ADDR'];
|
||||
}
|
||||
|
||||
$dns = new \Cloudflare\API\Endpoints\DNS($this->adapter);
|
||||
|
||||
try {
|
||||
return $dns->addRecord($this->zoneId, $type, $name, $content, $ttl, $proxied, $priority);
|
||||
} catch (ClientException $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function getRecordByValues($name, $type)
|
||||
{
|
||||
$dns = new \Cloudflare\API\Endpoints\DNS($this->adapter);
|
||||
|
||||
try {
|
||||
return $dns->getRecordID($this->zoneId, $type, $name);
|
||||
} catch (ClientException $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function deleteRecord($id)
|
||||
{
|
||||
$dns = new \Cloudflare\API\Endpoints\DNS($this->adapter);
|
||||
|
||||
try {
|
||||
return $dns->deleteRecord($this->zoneId, $id);
|
||||
} catch (ClientException $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function updateRecord($id, array $data = [])
|
||||
{
|
||||
$dns = new \Cloudflare\API\Endpoints\DNS($this->adapter);
|
||||
|
||||
try {
|
||||
$record = $dns->getRecordDetails($this->zoneId, $id);
|
||||
|
||||
return $dns->updateRecordDetails($this->zoneId, $id, [
|
||||
'type' => object_get($record, 'type'),
|
||||
'name' => array_get($data, 'name'),
|
||||
'content' => array_get($data, 'content'),
|
||||
]);
|
||||
} catch (ClientException $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function toggleProxy($id)
|
||||
{
|
||||
$dns = new \Cloudflare\API\Endpoints\DNS($this->adapter);
|
||||
|
||||
try {
|
||||
$record = $dns->getRecordDetails($this->zoneId, $id);
|
||||
|
||||
return $dns->updateRecordDetails($this->zoneId, $id, [
|
||||
'type' => object_get($record, 'type'),
|
||||
'name' => object_get($record, 'name'),
|
||||
'content' => object_get($record, 'content'),
|
||||
'proxied' => !object_get($record, 'proxied')
|
||||
]);
|
||||
} catch (ClientException $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function user()
|
||||
{
|
||||
$user = new \Cloudflare\API\Endpoints\User($this->adapter);
|
||||
|
||||
return $user->getUserDetails();
|
||||
}
|
||||
}
|
||||
@@ -83,7 +83,7 @@ class Resource
|
||||
return $this->server;
|
||||
}
|
||||
|
||||
public function setServer(Server $server): self
|
||||
public function setServer(Server $server)
|
||||
{
|
||||
$this->server = $server;
|
||||
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
|
||||
namespace App\Services\Ploi\Resources;
|
||||
|
||||
use stdClass;
|
||||
use App\Services\Ploi\Ploi;
|
||||
use App\Services\Ploi\Exceptions\Http\NotValid;
|
||||
use Services\Ploi\Exceptions\Resource\RequiresId;
|
||||
|
||||
class Server extends Resource
|
||||
@@ -57,6 +59,48 @@ class Server extends Resource
|
||||
return $this->getPloi()->makeAPICall($this->getEndpoint());
|
||||
}
|
||||
|
||||
public function create(
|
||||
string $name,
|
||||
int $provider,
|
||||
int $region,
|
||||
int $plan
|
||||
): stdClass {
|
||||
|
||||
// Remove the id
|
||||
$this->setId(null);
|
||||
|
||||
// Set the options
|
||||
$options = [
|
||||
'body' => json_encode([
|
||||
'name' => $name,
|
||||
'plan' => $plan,
|
||||
'region' => $region,
|
||||
'credential' => $provider,
|
||||
'type' => 'server',
|
||||
'database_type' => 'mysql',
|
||||
'webserver_type' => 'nginx',
|
||||
'php_version' => '7.4'
|
||||
]),
|
||||
];
|
||||
|
||||
// Make the request
|
||||
try {
|
||||
$response = $this->getPloi()->makeAPICall($this->getEndpoint(), 'post', $options);
|
||||
} catch (NotValid $exception) {
|
||||
$errors = json_decode($exception->getMessage())->errors;
|
||||
|
||||
dd($errors);
|
||||
|
||||
throw $exception;
|
||||
}
|
||||
|
||||
// Set the id of the site
|
||||
$this->setId($response->getJson()->data->id);
|
||||
|
||||
// Return the data
|
||||
return $response->getData();
|
||||
}
|
||||
|
||||
public function sites($id = null): Site
|
||||
{
|
||||
return new Site($this, $id);
|
||||
|
||||
@@ -43,7 +43,7 @@ class Site extends Resource
|
||||
return $this->server;
|
||||
}
|
||||
|
||||
public function setServer(Server $server): self
|
||||
public function setServer(Server $server)
|
||||
{
|
||||
$this->server = $server;
|
||||
|
||||
|
||||
@@ -20,8 +20,14 @@ class User extends Resource
|
||||
return $this->getPloi()->makeAPICall($this->getEndpoint());
|
||||
}
|
||||
|
||||
public function serverProviders()
|
||||
public function serverProviders($id = null)
|
||||
{
|
||||
return $this->getPloi()->makeAPICall($this->getEndpoint() . '/server-providers');
|
||||
$url = $this->getEndpoint() . '/server-providers';
|
||||
|
||||
if ($id) {
|
||||
$url .= '/' . $id;
|
||||
}
|
||||
|
||||
return $this->getPloi()->makeAPICall($url);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,8 @@
|
||||
"fideloper/proxy": "^4.2",
|
||||
"fruitcake/laravel-cors": "^1.0",
|
||||
"guzzlehttp/guzzle": "^6.2|^7.0.1",
|
||||
"inertiajs/inertia-laravel": "^0.2.12",
|
||||
"inertiajs/inertia-laravel": "^0.3.1",
|
||||
"laravel/cashier": "^12.3",
|
||||
"laravel/framework": "^8.4",
|
||||
"laravel/horizon": "^5.0",
|
||||
"laravel/tinker": "^2.0",
|
||||
|
||||
1109
composer.lock
generated
1109
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -17,7 +17,7 @@ return [
|
||||
|
|
||||
*/
|
||||
|
||||
'default' => env('LOG_CHANNEL', 'daily'),
|
||||
'default' => env('LOG_CHANNEL', 'stack'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
@@ -36,8 +36,8 @@ return [
|
||||
|
||||
'channels' => [
|
||||
'stack' => [
|
||||
'driver' => 'daily',
|
||||
'channels' => ['single'],
|
||||
'driver' => 'stack',
|
||||
'channels' => ['daily'],
|
||||
'ignore_exceptions' => false,
|
||||
],
|
||||
|
||||
@@ -51,7 +51,7 @@ return [
|
||||
'driver' => 'daily',
|
||||
'path' => storage_path('logs/laravel.log'),
|
||||
'level' => 'debug',
|
||||
'days' => 14,
|
||||
'days' => 7,
|
||||
],
|
||||
|
||||
'slack' => [
|
||||
|
||||
@@ -4,7 +4,6 @@ namespace Database\Factories;
|
||||
|
||||
use App\Models\Server;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class ServerFactory extends Factory
|
||||
{
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
class UserFactory extends Factory
|
||||
{
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class CreateFailedJobsTable extends Migration
|
||||
{
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class AddPermissionsToPackagesTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('packages', function (Blueprint $table) {
|
||||
$table->json('server_permissions')->nullable()->after('maximum_servers');
|
||||
$table->json('site_permissions')->nullable()->after('server_permissions');
|
||||
|
||||
$table->dropColumn('server_creation');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('packages', function (Blueprint $table) {
|
||||
$table->dropColumn('server_permissions', 'site_permissions');
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class CreatePaymentColumns extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('packages', function (Blueprint $table) {
|
||||
$table->decimal('price_hourly', 10, 4)->after('maximum_servers')->default(0);
|
||||
$table->decimal('price_monthly', 10, 4)->after('price_hourly')->default(0);
|
||||
$table->string('plan_id')->after('price_monthly')->nullable();
|
||||
$table->string('currency')->after('plan_id')->default(\App\Models\Package::CURRENCY_USD);
|
||||
});
|
||||
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->text('billing_details')->after('notes')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('packages', function (Blueprint $table) {
|
||||
$table->dropColumn('price_hourly', 'price_monthly', 'plan_id', 'currency');
|
||||
});
|
||||
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->dropColumn('billing_details');
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class CreatePackageProviderTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('package_provider', function (Blueprint $table) {
|
||||
$table->bigInteger('package_id')->unsigned()->nullable();
|
||||
$table->foreign('package_id')->references('id')->on('packages');
|
||||
|
||||
$table->bigInteger('provider_id')->unsigned()->nullable();
|
||||
$table->foreign('provider_id')->references('id')->on('providers');
|
||||
|
||||
$table->boolean('default')->default(false);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('package_provider');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class AddAdditionalColumnsToServersTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('servers', function (Blueprint $table) {
|
||||
$table->bigInteger('provider_id')->after('maximum_sites')->unsigned()->nullable();
|
||||
$table->foreign('provider_id')->references('id')->on('providers');
|
||||
|
||||
$table->bigInteger('provider_plan_id')->after('provider_id')->unsigned()->nullable();
|
||||
$table->foreign('provider_plan_id')->references('id')->on('provider_plans');
|
||||
|
||||
$table->bigInteger('provider_region_id')->after('provider_plan_id')->unsigned()->nullable();
|
||||
$table->foreign('provider_region_id')->references('id')->on('provider_regions');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('servers', function (Blueprint $table) {
|
||||
//
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class AddKeyboardShortcutsToUsersTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->boolean('keyboard_shortcuts')->default(true)->after('blocked');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->dropColumn('keyboard_shortcuts');
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class CreateDocumentationCategoriesTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('documentation_categories', function (Blueprint $table) {
|
||||
$table->id();
|
||||
|
||||
$table->string('title')->nullable();
|
||||
$table->text('description')->nullable();
|
||||
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
Schema::table('documentation_items', function (Blueprint $table) {
|
||||
$table->bigInteger('documentation_category_id')->after('content')->unsigned()->nullable();
|
||||
$table->foreign('documentation_category_id')->references('id')->on('documentation_categories');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('documentation_categories');
|
||||
|
||||
Schema::table('documentation_items', function (Blueprint $table) {
|
||||
$table->dropColumn('documentation_category_id');
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class CreateSiteSystemUsersTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('site_system_users', function (Blueprint $table) {
|
||||
$table->id();
|
||||
|
||||
$table->string('user_name')->nullable();
|
||||
$table->text('ftp_password')->nullable();
|
||||
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('site_system_users');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class CreateSiteSystemUserAttached extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('site_system_user_attached', function (Blueprint $table) {
|
||||
$table->bigInteger('site_id')->unsigned()->nullable();
|
||||
$table->foreign('site_id')->references('id')->on('sites');
|
||||
|
||||
$table->bigInteger('site_system_user_id')->unsigned()->nullable();
|
||||
$table->foreign('site_system_user_id')->references('id')->on('site_system_users');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('site_system_user_attached');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class AddRequiresPasswordForFtpToUsersTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->boolean('requires_password_for_ftp')->after('blocked')->default(true);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->dropColumn('requires_password_for_ftp');
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateAlertsTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('alerts', function (Blueprint $table) {
|
||||
$table->id();
|
||||
|
||||
$table->text('message')->nullable();
|
||||
$table->string('type')->nullable()->default(\App\Models\Alert::TYPE_INFO);
|
||||
|
||||
$table->timestamp('expires_at')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('alerts');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddMetaToUserProvidersTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('user_providers', function (Blueprint $table) {
|
||||
$table->json('meta')->nullable()->after('token');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('user_providers', function (Blueprint $table) {
|
||||
$table->dropColumn('meta');
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,5 @@ class DatabaseSeeder extends Seeder
|
||||
*/
|
||||
public function run()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
49
package-lock.json
generated
49
package-lock.json
generated
@@ -1055,21 +1055,29 @@
|
||||
}
|
||||
},
|
||||
"@inertiajs/inertia": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@inertiajs/inertia/-/inertia-0.2.1.tgz",
|
||||
"integrity": "sha512-FwaCe1c5si1k/K6pAoMiqENBd3jALuEkvJm4Yu2hDJjA23cVJKFcjfC3sD4Us0iRfGXL3QBORmJZpvSEVzEcGw==",
|
||||
"version": "0.4.3",
|
||||
"resolved": "https://registry.npmjs.org/@inertiajs/inertia/-/inertia-0.4.3.tgz",
|
||||
"integrity": "sha512-AEzyL8dd7F7thTLDhzQTi9I+p4r6k9QtacZ2sy5XYmt1E4ZI8MX67z+pKrV9yR2oIG8YAOMEJVhgIyJIg+nJ5Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"axios": "^0.19.0",
|
||||
"nprogress": "^0.2.0"
|
||||
"axios": "^0.19.0 || ^0.20.0"
|
||||
}
|
||||
},
|
||||
"@inertiajs/inertia-vue": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@inertiajs/inertia-vue/-/inertia-vue-0.2.1.tgz",
|
||||
"integrity": "sha512-FRn3y7wOuqCNVLnUex4tTnVl1/f8CZordS+/Kl+qTTcsUxqn2VNR856TYniySqSS7OWxwkQFAoO9LZWqam/7FQ==",
|
||||
"version": "0.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@inertiajs/inertia-vue/-/inertia-vue-0.3.2.tgz",
|
||||
"integrity": "sha512-rY/K6ChlmpVTAFBTM5kf6mUn5vCpXqY2AO6ZWIF32J5rK8hv8p8dczrhCeMOrekOwVUSmI9WwTwPVhP+p+KaIA==",
|
||||
"dev": true
|
||||
},
|
||||
"@inertiajs/progress": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@inertiajs/progress/-/progress-0.1.2.tgz",
|
||||
"integrity": "sha512-XazXCp9ezY2GLfS37O+LUf6Ni1xYLtrQg+svUi+videi3Aby28d4xRCtrqLvyo3HIWQE5DvNqCPQ8BFbaPZTOA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"nprogress": "^0.2.0"
|
||||
}
|
||||
},
|
||||
"@mrmlnc/readdir-enhanced": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz",
|
||||
@@ -1108,12 +1116,6 @@
|
||||
"postcss-selector-parser": "^6.0.2"
|
||||
}
|
||||
},
|
||||
"@types/color-name": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz",
|
||||
"integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/glob": {
|
||||
"version": "7.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.2.tgz",
|
||||
@@ -1381,9 +1383,9 @@
|
||||
}
|
||||
},
|
||||
"acorn": {
|
||||
"version": "7.4.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.0.tgz",
|
||||
"integrity": "sha512-+G7P8jJmCHr+S+cLfQxygbWhXy+8YTVGzAkpEbcLo2mLoL7tij/VG41QSHACSf5QgYRhMZYHuNc6drJaO0Da+w==",
|
||||
"version": "7.4.1",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz",
|
||||
"integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==",
|
||||
"dev": true
|
||||
},
|
||||
"acorn-node": {
|
||||
@@ -2310,12 +2312,11 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"ansi-styles": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz",
|
||||
"integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==",
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/color-name": "^1.1.1",
|
||||
"color-convert": "^2.0.1"
|
||||
}
|
||||
},
|
||||
@@ -9030,9 +9031,9 @@
|
||||
}
|
||||
},
|
||||
"tailwindcss": {
|
||||
"version": "1.8.10",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-1.8.10.tgz",
|
||||
"integrity": "sha512-7QkERG/cWCzsuMqHMwjOaLMVixOGLNBiXsrkssxlE1aWfkxVbGqiuMokR2162xRyaH2mBIHKxmlf1qb3DvIPqw==",
|
||||
"version": "1.9.6",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-1.9.6.tgz",
|
||||
"integrity": "sha512-nY8WYM/RLPqGsPEGEV2z63riyQPcHYZUJpAwdyBzVpxQHOHqHE+F/fvbCeXhdF1+TA5l72vSkZrtYCB9hRcwkQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@fullhuman/postcss-purgecss": "^2.1.2",
|
||||
|
||||
17
package.json
17
package.json
@@ -11,24 +11,25 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
|
||||
"@inertiajs/inertia": "^0.2.1",
|
||||
"@inertiajs/inertia-vue": "^0.2.1",
|
||||
"@inertiajs/inertia": "^0.4.3",
|
||||
"@inertiajs/inertia-vue": "^0.3.2",
|
||||
"@inertiajs/progress": "^0.1.2",
|
||||
"@tailwindcss/ui": "^0.3.0",
|
||||
"axios": "^0.19",
|
||||
"balloon-css": "^1.2.0",
|
||||
"cross-env": "^7.0",
|
||||
"laravel-mix": "^5.0.1",
|
||||
"lodash": "^4.17.15",
|
||||
"portal-vue": "^2.1.7",
|
||||
"resolve-url-loader": "^3.1.0",
|
||||
"sass": "^1.15.2",
|
||||
"sass-loader": "^8.0.0",
|
||||
"vue-template-compiler": "^2.6.11",
|
||||
"tailwindcss": "^1.8.10",
|
||||
"tailwindcss": "^1.9.6",
|
||||
"v-click-outside": "^3.0.1",
|
||||
"vue": "^2.6.11",
|
||||
"portal-vue": "^2.1.7",
|
||||
"vue-meta": "^2.4.0",
|
||||
"vue-clipboard2": "^0.3.1",
|
||||
"vuex": "^3.5.1",
|
||||
"v-click-outside": "^3.0.1"
|
||||
"vue-meta": "^2.4.0",
|
||||
"vue-template-compiler": "^2.6.11",
|
||||
"vuex": "^3.5.1"
|
||||
}
|
||||
}
|
||||
|
||||
2
public/css/app.css
vendored
2
public/css/app.css
vendored
File diff suppressed because one or more lines are too long
2
public/js/0.js
vendored
2
public/js/0.js
vendored
File diff suppressed because one or more lines are too long
2
public/js/1.js
vendored
2
public/js/1.js
vendored
File diff suppressed because one or more lines are too long
2
public/js/10.js
vendored
2
public/js/10.js
vendored
File diff suppressed because one or more lines are too long
2
public/js/11.js
vendored
2
public/js/11.js
vendored
File diff suppressed because one or more lines are too long
2
public/js/12.js
vendored
2
public/js/12.js
vendored
File diff suppressed because one or more lines are too long
2
public/js/13.js
vendored
2
public/js/13.js
vendored
File diff suppressed because one or more lines are too long
2
public/js/14.js
vendored
2
public/js/14.js
vendored
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user