Merge branch 'feature/vuejs' into feature/vue-serverview
This commit is contained in:
commit
05478e3277
29 changed files with 2997 additions and 16217 deletions
|
@ -21,6 +21,12 @@ debconf-set-selections <<< 'mariadb-server-5.5 mysql-server/root_password_again
|
||||||
# actually install
|
# actually install
|
||||||
apt-get install -y php7.2 php7.2-cli php7.2-gd php7.2-mysql php7.2-pdo php7.2-mbstring php7.2-tokenizer php7.2-bcmath php7.2-xml php7.2-fpm php7.2-memcached php7.2-curl php7.2-zip php-xdebug mariadb-server nginx curl tar unzip git memcached > /dev/null
|
apt-get install -y php7.2 php7.2-cli php7.2-gd php7.2-mysql php7.2-pdo php7.2-mbstring php7.2-tokenizer php7.2-bcmath php7.2-xml php7.2-fpm php7.2-memcached php7.2-curl php7.2-zip php-xdebug mariadb-server nginx curl tar unzip git memcached > /dev/null
|
||||||
|
|
||||||
|
echo "Install nodejs and yarn"
|
||||||
|
curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
|
||||||
|
echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
|
||||||
|
curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash -
|
||||||
|
apt-get -y install nodejs yarn > /dev/null
|
||||||
|
|
||||||
echo "Install composer"
|
echo "Install composer"
|
||||||
curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
|
curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
|
||||||
|
|
||||||
|
|
61
BUILDING.md
61
BUILDING.md
|
@ -1,10 +1,57 @@
|
||||||
# Building Assets
|
# Local Development
|
||||||
|
Pterodactyl is now powered by Vuejs and Tailwindcss and uses webpack at its core to generate compiled assets. Release
|
||||||
|
versions of Pterodactyl will include pre-compiled, minified, and hashed assets ready-to-go.
|
||||||
|
|
||||||
```
|
However, if you are interested in running custom themes or making modifications to the Vue files you'll need a build
|
||||||
|
system in place to generate these compiled assets. To get your environment setup, you'll first need to install at least Nodejs
|
||||||
|
`8`, and it is _highly_ recommended that you also install [Yarn](https://yarnpkg.com) to manage your `node_modules`.
|
||||||
|
|
||||||
|
### Install Dependencies
|
||||||
|
```bash
|
||||||
yarn install
|
yarn install
|
||||||
|
|
||||||
php artisan vue-i18n:generate
|
|
||||||
php artisan ziggy:generate resources/assets/scripts/helpers/ziggy.js
|
|
||||||
|
|
||||||
npm run build
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The command above will download all of the dependencies necessary to get Pterodactyl assets building. After that, its as
|
||||||
|
simple as running the command below to generate assets while you're developing.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# build the compiled assets for development
|
||||||
|
yarn run build
|
||||||
|
|
||||||
|
# build the assets automatically when files are modified
|
||||||
|
yarn run watch
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Hot Module Reloading
|
||||||
|
For more advanced users, we also support 'Hot Module Reloading', allowing you to quickly see changes you're making
|
||||||
|
to the Vue template files without having to reload the page you're on. To Get started with this, you just need
|
||||||
|
to run the command below.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
PUBLIC_PATH=http://192.168.1.1:8080 yarn run serve --host 192.168.1.1
|
||||||
|
```
|
||||||
|
|
||||||
|
There are two _very important_ parts of this command to take note of and change for your specific environment. The first
|
||||||
|
is the `--host` flag, which is required and should point to the machine where the `webpack-serve` server will be running.
|
||||||
|
The second is the `PUBLIC_PATH` environment variable which is the URL pointing to the HMR server and is appended to all of
|
||||||
|
the asset URLs used in Pterodactyl.
|
||||||
|
|
||||||
|
#### Vagrant
|
||||||
|
If you want to use HMR with our Vagrant image, you can use `yarn run v:serve` as a shortcut for the correct parameters.
|
||||||
|
In order to have proper file change detection you can use the [`vagrant-notify-forwarder`](https://github.com/mhallin/vagrant-notify-forwarder) to notify file events from the host to the VM.
|
||||||
|
```sh
|
||||||
|
vagrant plugin install vagrant-notify-forwarder
|
||||||
|
vagrant reload
|
||||||
|
```
|
||||||
|
|
||||||
|
### Building for Production
|
||||||
|
Once you have your files squared away and ready for the live server, you'll be needing to generate compiled, minified, and
|
||||||
|
hashed assets to push live. To do so, run the command below:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yarn run build:production
|
||||||
|
```
|
||||||
|
|
||||||
|
This will generate a production ready `bundle.js` and `bundle.css` as well as a `manifest.json` and store them in
|
||||||
|
the `/public/assets` directory where they can then be access by clients, and read by the Panel.
|
||||||
|
|
16
app/Http/Controllers/Api/Client/AccountController.php
Normal file
16
app/Http/Controllers/Api/Client/AccountController.php
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Pterodactyl\Http\Controllers\Api\Client;
|
||||||
|
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Pterodactyl\Transformers\Api\Client\AccountTransformer;
|
||||||
|
|
||||||
|
class AccountController extends ClientApiController
|
||||||
|
{
|
||||||
|
public function index(Request $request): array
|
||||||
|
{
|
||||||
|
return $this->fractal->item($request->user())
|
||||||
|
->transformWith($this->getTransformer(AccountTransformer::class))
|
||||||
|
->toArray();
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,6 +16,7 @@ use Illuminate\Contracts\Auth\Authenticatable;
|
||||||
use Illuminate\Contracts\Encryption\Encrypter;
|
use Illuminate\Contracts\Encryption\Encrypter;
|
||||||
use Illuminate\Foundation\Auth\AuthenticatesUsers;
|
use Illuminate\Foundation\Auth\AuthenticatesUsers;
|
||||||
use Pterodactyl\Traits\Helpers\ProvidesJWTServices;
|
use Pterodactyl\Traits\Helpers\ProvidesJWTServices;
|
||||||
|
use Pterodactyl\Transformers\Api\Client\AccountTransformer;
|
||||||
use Illuminate\Contracts\Cache\Repository as CacheRepository;
|
use Illuminate\Contracts\Cache\Repository as CacheRepository;
|
||||||
use Pterodactyl\Contracts\Repository\UserRepositoryInterface;
|
use Pterodactyl\Contracts\Repository\UserRepositoryInterface;
|
||||||
|
|
||||||
|
@ -137,27 +138,37 @@ abstract class AbstractLoginController extends Controller
|
||||||
$request->session()->regenerate();
|
$request->session()->regenerate();
|
||||||
$this->clearLoginAttempts($request);
|
$this->clearLoginAttempts($request);
|
||||||
|
|
||||||
$token = $this->builder->setIssuer(config('app.url'))
|
|
||||||
->setAudience(config('app.url'))
|
|
||||||
->setId(str_random(12), true)
|
|
||||||
->setIssuedAt(Chronos::now()->getTimestamp())
|
|
||||||
->setNotBefore(Chronos::now()->getTimestamp())
|
|
||||||
->setExpiration(Chronos::now()->addSeconds(config('session.lifetime'))->getTimestamp())
|
|
||||||
->set('user', $user->only([
|
|
||||||
'id', 'uuid', 'username', 'email', 'name_first', 'name_last', 'language', 'root_admin',
|
|
||||||
]))
|
|
||||||
->sign($this->getJWTSigner(), $this->getJWTSigningKey())
|
|
||||||
->getToken();
|
|
||||||
|
|
||||||
$this->auth->guard()->login($user, true);
|
$this->auth->guard()->login($user, true);
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'complete' => true,
|
'complete' => true,
|
||||||
'intended' => $this->redirectPath(),
|
'intended' => $this->redirectPath(),
|
||||||
'token' => $token->__toString(),
|
'jwt' => $this->createJsonWebToken($user),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new JWT for the request and sign it using the signing key.
|
||||||
|
*
|
||||||
|
* @param User $user
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function createJsonWebToken(User $user): string
|
||||||
|
{
|
||||||
|
$token = $this->builder
|
||||||
|
->setIssuer('Pterodactyl Panel')
|
||||||
|
->setAudience(config('app.url'))
|
||||||
|
->setId(str_random(16), true)
|
||||||
|
->setIssuedAt(Chronos::now()->getTimestamp())
|
||||||
|
->setNotBefore(Chronos::now()->getTimestamp())
|
||||||
|
->setExpiration(Chronos::now()->addSeconds(config('session.lifetime'))->getTimestamp())
|
||||||
|
->set('user', (new AccountTransformer())->transform($user))
|
||||||
|
->sign($this->getJWTSigner(), $this->getJWTSigningKey())
|
||||||
|
->getToken();
|
||||||
|
|
||||||
|
return $token->__toString();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine if the user is logging in using an email or username,.
|
* Determine if the user is logging in using an email or username,.
|
||||||
*
|
*
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
namespace Pterodactyl\Http;
|
namespace Pterodactyl\Http;
|
||||||
|
|
||||||
use Pterodactyl\Http\Middleware\MaintenanceMiddleware;
|
|
||||||
use Pterodactyl\Models\ApiKey;
|
use Pterodactyl\Models\ApiKey;
|
||||||
use Illuminate\Auth\Middleware\Authorize;
|
use Illuminate\Auth\Middleware\Authorize;
|
||||||
use Illuminate\Auth\Middleware\Authenticate;
|
use Illuminate\Auth\Middleware\Authenticate;
|
||||||
|
@ -21,6 +20,7 @@ use Illuminate\Routing\Middleware\SubstituteBindings;
|
||||||
use Pterodactyl\Http\Middleware\AccessingValidServer;
|
use Pterodactyl\Http\Middleware\AccessingValidServer;
|
||||||
use Pterodactyl\Http\Middleware\Api\SetSessionDriver;
|
use Pterodactyl\Http\Middleware\Api\SetSessionDriver;
|
||||||
use Illuminate\View\Middleware\ShareErrorsFromSession;
|
use Illuminate\View\Middleware\ShareErrorsFromSession;
|
||||||
|
use Pterodactyl\Http\Middleware\MaintenanceMiddleware;
|
||||||
use Pterodactyl\Http\Middleware\RedirectIfAuthenticated;
|
use Pterodactyl\Http\Middleware\RedirectIfAuthenticated;
|
||||||
use Illuminate\Auth\Middleware\AuthenticateWithBasicAuth;
|
use Illuminate\Auth\Middleware\AuthenticateWithBasicAuth;
|
||||||
use Pterodactyl\Http\Middleware\Api\AuthenticateIPAccess;
|
use Pterodactyl\Http\Middleware\Api\AuthenticateIPAccess;
|
||||||
|
@ -71,7 +71,7 @@ class Kernel extends HttpKernel
|
||||||
RequireTwoFactorAuthentication::class,
|
RequireTwoFactorAuthentication::class,
|
||||||
],
|
],
|
||||||
'api' => [
|
'api' => [
|
||||||
'throttle:120,1',
|
'throttle:240,1',
|
||||||
ApiSubstituteBindings::class,
|
ApiSubstituteBindings::class,
|
||||||
SetSessionDriver::class,
|
SetSessionDriver::class,
|
||||||
'api..key:' . ApiKey::TYPE_APPLICATION,
|
'api..key:' . ApiKey::TYPE_APPLICATION,
|
||||||
|
@ -79,7 +79,7 @@ class Kernel extends HttpKernel
|
||||||
AuthenticateIPAccess::class,
|
AuthenticateIPAccess::class,
|
||||||
],
|
],
|
||||||
'client-api' => [
|
'client-api' => [
|
||||||
'throttle:60,1',
|
'throttle:240,1',
|
||||||
SubstituteClientApiBindings::class,
|
SubstituteClientApiBindings::class,
|
||||||
SetSessionDriver::class,
|
SetSessionDriver::class,
|
||||||
'api..key:' . ApiKey::TYPE_ACCOUNT,
|
'api..key:' . ApiKey::TYPE_ACCOUNT,
|
||||||
|
|
|
@ -97,6 +97,16 @@ class AuthenticateKey
|
||||||
throw new HttpException(401, null, null, ['WWW-Authenticate' => 'Bearer']);
|
throw new HttpException(401, null, null, ['WWW-Authenticate' => 'Bearer']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Run through the token validation and throw an exception if the token is not valid.
|
||||||
|
if (
|
||||||
|
$token->getClaim('nbf') > Chronos::now()->getTimestamp()
|
||||||
|
|| $token->getClaim('iss') !== 'Pterodactyl Panel'
|
||||||
|
|| $token->getClaim('aud') !== config('app.url')
|
||||||
|
|| $token->getClaim('exp') <= Chronos::now()->getTimestamp()
|
||||||
|
) {
|
||||||
|
throw new AccessDeniedHttpException;
|
||||||
|
}
|
||||||
|
|
||||||
return (new ApiKey)->forceFill([
|
return (new ApiKey)->forceFill([
|
||||||
'user_id' => object_get($token->getClaim('user'), 'id', 0),
|
'user_id' => object_get($token->getClaim('user'), 'id', 0),
|
||||||
'key_type' => ApiKey::TYPE_ACCOUNT,
|
'key_type' => ApiKey::TYPE_ACCOUNT,
|
||||||
|
|
|
@ -58,8 +58,25 @@ class AssetHashService
|
||||||
public function url(string $resource): string
|
public function url(string $resource): string
|
||||||
{
|
{
|
||||||
$file = last(explode('/', $resource));
|
$file = last(explode('/', $resource));
|
||||||
|
$data = array_get($this->manifest(), $file, $file);
|
||||||
|
|
||||||
return '/' . ltrim(str_replace($file, array_get($this->manifest(), $file, $file), $resource), '/');
|
return str_replace($file, array_get($data, 'src', $file), $resource);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the data integrity hash for a resource.
|
||||||
|
*
|
||||||
|
* @param string $resource
|
||||||
|
* @return string
|
||||||
|
*
|
||||||
|
* @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
|
||||||
|
*/
|
||||||
|
public function integrity(string $resource): string
|
||||||
|
{
|
||||||
|
$file = last(explode('/', $resource));
|
||||||
|
$data = array_get($this->manifest(), $file, $file);
|
||||||
|
|
||||||
|
return array_get($data, 'integrity', '');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -72,7 +89,11 @@ class AssetHashService
|
||||||
*/
|
*/
|
||||||
public function css(string $resource): string
|
public function css(string $resource): string
|
||||||
{
|
{
|
||||||
return '<link href="' . $this->url($resource) . '" rel="stylesheet preload" crossorigin="anonymous" referrerpolicy="no-referrer">';
|
return '<link href="' . $this->url($resource) . '"
|
||||||
|
rel="stylesheet preload"
|
||||||
|
crossorigin="anonymous"
|
||||||
|
integrity="' . $this->integrity($resource) . '"
|
||||||
|
referrerpolicy="no-referrer">';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -85,7 +106,9 @@ class AssetHashService
|
||||||
*/
|
*/
|
||||||
public function js(string $resource): string
|
public function js(string $resource): string
|
||||||
{
|
{
|
||||||
return '<script src="' . $this->url($resource) . '" crossorigin="anonymous"></script>';
|
return '<script src="' . $this->url($resource) . '"
|
||||||
|
integrity="' . $this->integrity($resource) . '"
|
||||||
|
crossorigin="anonymous"></script>';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
37
app/Transformers/Api/Client/AccountTransformer.php
Normal file
37
app/Transformers/Api/Client/AccountTransformer.php
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Pterodactyl\Transformers\Api\Client;
|
||||||
|
|
||||||
|
use Pterodactyl\Models\User;
|
||||||
|
|
||||||
|
class AccountTransformer extends BaseClientTransformer
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Return the resource name for the JSONAPI output.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getResourceName(): string
|
||||||
|
{
|
||||||
|
return 'user';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return basic information about the currently logged in user.
|
||||||
|
*
|
||||||
|
* @param \Pterodactyl\Models\User $model
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function transform(User $model)
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'id' => $model->id,
|
||||||
|
'admin' => $model->root_admin,
|
||||||
|
'username' => $model->username,
|
||||||
|
'email' => $model->email,
|
||||||
|
'first_name' => $model->name_first,
|
||||||
|
'last_name' => $model->name_last,
|
||||||
|
'language' => $model->language,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
123
gulpfile.js
123
gulpfile.js
|
@ -1,123 +0,0 @@
|
||||||
const babel = require('gulp-babel');
|
|
||||||
const concat = require('gulp-concat');
|
|
||||||
const cssmin = require('gulp-cssmin');
|
|
||||||
const del = require('del');
|
|
||||||
const exec = require('child_process').exec;
|
|
||||||
const gulp = require('gulp');
|
|
||||||
const gulpif = require('gulp-if');
|
|
||||||
const postcss = require('gulp-postcss');
|
|
||||||
const rev = require('gulp-rev');
|
|
||||||
const uglify = require('gulp-uglify-es').default;
|
|
||||||
const webpackStream = require('webpack-stream');
|
|
||||||
const webpackConfig = require('./webpack.config.js');
|
|
||||||
const sourcemaps = require('gulp-sourcemaps');
|
|
||||||
const through = require('through2');
|
|
||||||
|
|
||||||
const argv = require('yargs')
|
|
||||||
.default('production', false)
|
|
||||||
.argv;
|
|
||||||
|
|
||||||
const paths = {
|
|
||||||
manifest: './public/assets',
|
|
||||||
assets: './public/assets/{css,scripts}/*.{css,js,map}',
|
|
||||||
styles: {
|
|
||||||
src: './resources/assets/styles/main.css',
|
|
||||||
dest: './public/assets/css',
|
|
||||||
},
|
|
||||||
scripts: {
|
|
||||||
src: './resources/assets/scripts/**/*.{js,vue}',
|
|
||||||
watch: ['./resources/assets/scripts/**/*.{js,vue}', './resources/lang/locales.js'],
|
|
||||||
dest: './public/assets/scripts',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Build un-compiled CSS into a minified version.
|
|
||||||
*/
|
|
||||||
function styles() {
|
|
||||||
return gulp.src(paths.styles.src)
|
|
||||||
.pipe(sourcemaps.init())
|
|
||||||
.pipe(postcss([
|
|
||||||
require('postcss-import'),
|
|
||||||
require('tailwindcss')('./tailwind.js'),
|
|
||||||
require('precss'),
|
|
||||||
require('postcss-preset-env')({stage: 0}),
|
|
||||||
require('autoprefixer'),
|
|
||||||
]))
|
|
||||||
.pipe(gulpif(argv.production, cssmin()))
|
|
||||||
.pipe(concat('bundle.css'))
|
|
||||||
.pipe(rev())
|
|
||||||
.pipe(sourcemaps.write('.'))
|
|
||||||
.pipe(gulp.dest(paths.styles.dest))
|
|
||||||
.pipe(rev.manifest(paths.manifest + '/manifest.json', {merge: true, base: paths.manifest}))
|
|
||||||
.pipe(gulp.dest(paths.manifest));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Build all of the waiting scripts.
|
|
||||||
*/
|
|
||||||
function scripts() {
|
|
||||||
return webpackStream(webpackConfig)
|
|
||||||
.pipe(gulpif(argv.production, uglify()))
|
|
||||||
.pipe(rev())
|
|
||||||
.pipe(gulp.dest(paths.scripts.dest))
|
|
||||||
.pipe(rev.manifest(paths.manifest + '/manifest.json', {merge: true, base: paths.manifest}))
|
|
||||||
.pipe(gulp.dest(paths.manifest));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provides watchers.
|
|
||||||
*/
|
|
||||||
function watch() {
|
|
||||||
gulp.watch(['./resources/assets/styles/**/*.css'], gulp.series(function cleanStyles() {
|
|
||||||
return del(['./public/assets/css/**/*.{css,map}']);
|
|
||||||
}, styles));
|
|
||||||
|
|
||||||
gulp.watch(paths.scripts.watch, gulp.series(function cleanScripts() {
|
|
||||||
return del(['./public/assets/scripts/**/*.{js,map}']);
|
|
||||||
}, scripts));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate the language files to be consumed by front end.
|
|
||||||
*
|
|
||||||
* @returns {Promise<any>}
|
|
||||||
*/
|
|
||||||
function i18n() {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
exec('php artisan vue-i18n:generate', {}, (err, stdout, stderr) => {
|
|
||||||
return err ? reject(err) : resolve({ stdout, stderr });
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate the routes file to be used in Vue files.
|
|
||||||
*
|
|
||||||
* @returns {Promise<any>}
|
|
||||||
*/
|
|
||||||
function routes() {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
exec('php artisan ziggy:generate resources/assets/scripts/helpers/ziggy.js', {}, (err, stdout, stderr) => {
|
|
||||||
return err ? reject(err) : resolve({ stdout, stderr });
|
|
||||||
});
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cleanup unused versions of hashed assets.
|
|
||||||
*/
|
|
||||||
function clean() {
|
|
||||||
return del([paths.assets]);
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.clean = clean;
|
|
||||||
exports.i18n = i18n;
|
|
||||||
exports.routes = routes;
|
|
||||||
exports.styles = styles;
|
|
||||||
exports.scripts = scripts;
|
|
||||||
exports.watch = watch;
|
|
||||||
|
|
||||||
gulp.task('components', gulp.parallel(i18n, routes));
|
|
||||||
gulp.task('scripts', gulp.series(clean, scripts));
|
|
||||||
gulp.task('default', gulp.series(clean, i18n, routes, styles, scripts));
|
|
13718
package-lock.json
generated
13718
package-lock.json
generated
File diff suppressed because it is too large
Load diff
64
package.json
64
package.json
|
@ -1,14 +1,19 @@
|
||||||
{
|
{
|
||||||
"name": "pterodactyl-panel",
|
"name": "pterodactyl-panel",
|
||||||
|
"dependencies": {
|
||||||
|
"vue": "^2.5.7",
|
||||||
|
"vue-axios": "^2.1.1",
|
||||||
|
"vue-router": "^3.0.1",
|
||||||
|
"vuex": "^3.0.1",
|
||||||
|
"vuex-i18n": "^1.10.5",
|
||||||
|
"vuex-router-sync": "^5.0.0"
|
||||||
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.0.0-beta.49",
|
"@babel/core": "^7.0.0-beta.49",
|
||||||
"@babel/plugin-proposal-object-rest-spread": "^7.0.0-beta.49",
|
"@babel/plugin-proposal-object-rest-spread": "^7.0.0-beta.49",
|
||||||
"@babel/plugin-transform-async-to-generator": "^7.0.0-beta.49",
|
"@babel/plugin-transform-async-to-generator": "^7.0.0-beta.49",
|
||||||
"@babel/plugin-transform-runtime": "^7.0.0-beta.49",
|
"@babel/plugin-transform-runtime": "^7.0.0-beta.49",
|
||||||
"@babel/preset-env": "^7.0.0-beta.49",
|
"@babel/preset-env": "^7.0.0-beta.49",
|
||||||
"@fortawesome/fontawesome": "^1.1.8",
|
|
||||||
"@fortawesome/fontawesome-free-solid": "^5.0.13",
|
|
||||||
"@fortawesome/vue-fontawesome": "0.0.22",
|
|
||||||
"autoprefixer": "^8.2.0",
|
"autoprefixer": "^8.2.0",
|
||||||
"axios": "^0.18.0",
|
"axios": "^0.18.0",
|
||||||
"babel-cli": "6.18.0",
|
"babel-cli": "6.18.0",
|
||||||
|
@ -17,59 +22,46 @@
|
||||||
"babel-plugin-transform-object-assign": "^6.22.0",
|
"babel-plugin-transform-object-assign": "^6.22.0",
|
||||||
"babel-plugin-transform-runtime": "^6.23.0",
|
"babel-plugin-transform-runtime": "^6.23.0",
|
||||||
"babel-plugin-transform-strict-mode": "^6.18.0",
|
"babel-plugin-transform-strict-mode": "^6.18.0",
|
||||||
"babel-preset-es2015": "^6.24.1",
|
|
||||||
"babel-register": "^6.26.0",
|
"babel-register": "^6.26.0",
|
||||||
|
"clean-webpack-plugin": "^0.1.19",
|
||||||
"css-loader": "^0.28.11",
|
"css-loader": "^0.28.11",
|
||||||
"del": "^3.0.0",
|
"extract-text-webpack-plugin": "^4.0.0-beta.0",
|
||||||
"gulp": "^4.0.0",
|
"glob-all": "^3.1.0",
|
||||||
"gulp-babel": "^7.0.1",
|
"html-webpack-plugin": "^3.2.0",
|
||||||
"gulp-cli": "^2.0.1",
|
|
||||||
"gulp-concat": "^2.6.1",
|
|
||||||
"gulp-cssmin": "^0.2.0",
|
|
||||||
"gulp-if": "^2.0.2",
|
|
||||||
"gulp-postcss": "^7.0.1",
|
|
||||||
"gulp-rename": "^1.2.2",
|
|
||||||
"gulp-rev": "^8.1.1",
|
|
||||||
"gulp-sourcemaps": "^2.6.4",
|
|
||||||
"gulp-uglify-es": "^1.0.1",
|
|
||||||
"jquery": "^3.3.1",
|
"jquery": "^3.3.1",
|
||||||
"jwt-decode": "^2.2.0",
|
"jwt-decode": "^2.2.0",
|
||||||
"lodash": "^4.17.5",
|
"lodash": "^4.17.5",
|
||||||
"luxon": "^1.2.1",
|
"luxon": "^1.2.1",
|
||||||
"postcss": "^6.0.21",
|
"postcss": "^6.0.21",
|
||||||
"postcss-import": "^11.1.0",
|
"postcss-import": "^11.1.0",
|
||||||
|
"postcss-loader": "^2.1.5",
|
||||||
"postcss-preset-env": "^3.4.0",
|
"postcss-preset-env": "^3.4.0",
|
||||||
"postcss-scss": "^1.0.4",
|
|
||||||
"precss": "^3.1.2",
|
"precss": "^3.1.2",
|
||||||
"pug-plain-loader": "^1.0.0",
|
"purgecss-webpack-plugin": "^1.1.0",
|
||||||
|
"style-loader": "^0.21.0",
|
||||||
"tailwindcss": "^0.5.1",
|
"tailwindcss": "^0.5.1",
|
||||||
"through2": "^2.0.3",
|
"uglifyjs-webpack-plugin": "^1.2.5",
|
||||||
"vee-validate": "^2.0.9",
|
|
||||||
"vue": "^2.5.7",
|
|
||||||
"vue-axios": "^2.1.1",
|
|
||||||
"vue-devtools": "^3.1.9",
|
"vue-devtools": "^3.1.9",
|
||||||
"vue-feather-icons": "^4.7.1",
|
"vue-feather-icons": "^4.7.1",
|
||||||
"vue-loader": "^14.2.2",
|
"vue-loader": "^14.2.2",
|
||||||
"vue-mc": "^0.2.4",
|
"vue-mc": "^0.2.4",
|
||||||
"vue-router": "^3.0.1",
|
|
||||||
"vue-template-compiler": "^2.5.16",
|
"vue-template-compiler": "^2.5.16",
|
||||||
"vueify-insert-css": "^1.0.0",
|
"vueify-insert-css": "^1.0.0",
|
||||||
"vuex": "^3.0.1",
|
|
||||||
"vuex-i18n": "^1.10.5",
|
|
||||||
"webpack": "^4.4.1",
|
"webpack": "^4.4.1",
|
||||||
"webpack-stream": "^4.0.3",
|
"webpack-assets-manifest": "^3.0.1",
|
||||||
"xterm": "^3.4.1",
|
"webpack-cli": "^3.0.2",
|
||||||
"yargs": "^11.0.0"
|
"webpack-hot-client": "^4.0.2",
|
||||||
|
"webpack-manifest-plugin": "^2.0.3",
|
||||||
|
"webpack-serve": "^1.0.2",
|
||||||
|
"webpack-shell-plugin": "^0.5.0",
|
||||||
|
"webpack-stream": "^4.0.3"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build:filemanager": "./node_modules/babel-cli/bin/babel.js public/themes/pterodactyl/js/frontend/files/src --source-maps --out-file public/themes/pterodactyl/js/frontend/files/filemanager.min.js",
|
"build:filemanager": "./node_modules/babel-cli/bin/babel.js public/themes/pterodactyl/js/frontend/files/src --source-maps --out-file public/themes/pterodactyl/js/frontend/files/filemanager.min.js",
|
||||||
"watch": "./node_modules/gulp-cli/bin/gulp.js watch",
|
"watch": "NODE_ENV=development ./node_modules/.bin/webpack --watch --progress",
|
||||||
"build": "./node_modules/gulp-cli/bin/gulp.js default",
|
"build": "NODE_ENV=development ./node_modules/.bin/webpack --progress",
|
||||||
"build:components": "./node_modules/gulp-cli/bin/gulp.js components",
|
"build:production": "NODE_ENV=production ./node_modules/.bin/webpack",
|
||||||
"build:styles": "./node_modules/gulp-cli/bin/gulp.js styles",
|
"serve": "webpack-serve --hot --config ./webpack.config.js",
|
||||||
"build:scripts": "./node_modules/gulp-cli/bin/gulp.js scripts"
|
"v:serve": "PUBLIC_PATH=http://192.168.50.2:8080 NODE_ENV=development webpack-serve --hot --config ./webpack.config.js --host 192.168.50.2 --no-clipboard"
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"vuex-router-sync": "^5.0.0"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
18
resources/assets/index.html
Normal file
18
resources/assets/index.html
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Pterodactyl Dev</title>
|
||||||
|
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="pterodactyl">
|
||||||
|
<router-view></router-view>
|
||||||
|
<div class="w-full m-auto mt-0 container">
|
||||||
|
<p class="text-right text-grey-dark text-xs">
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -3,16 +3,13 @@ import Vuex from 'vuex';
|
||||||
import vuexI18n from 'vuex-i18n';
|
import vuexI18n from 'vuex-i18n';
|
||||||
import VueRouter from 'vue-router';
|
import VueRouter from 'vue-router';
|
||||||
|
|
||||||
|
require('./bootstrap');
|
||||||
|
|
||||||
// Helpers
|
// Helpers
|
||||||
import { Ziggy } from './helpers/ziggy';
|
import { Ziggy } from './helpers/ziggy';
|
||||||
import Locales from './../../../resources/lang/locales';
|
import Locales from './../../../resources/lang/locales';
|
||||||
import { flash } from './mixins/flash';
|
import { flash } from './mixins/flash';
|
||||||
|
|
||||||
import fontawesome from '@fortawesome/fontawesome';
|
|
||||||
import faSolid from '@fortawesome/fontawesome-free-solid';
|
|
||||||
import FontAwesomeIcon from '@fortawesome/vue-fontawesome';
|
|
||||||
fontawesome.library.add(faSolid);
|
|
||||||
|
|
||||||
import { routes } from './routes';
|
import { routes } from './routes';
|
||||||
import createStore from './store';
|
import createStore from './store';
|
||||||
|
|
||||||
|
@ -27,9 +24,10 @@ const router = new VueRouter({
|
||||||
Vue.use(Vuex);
|
Vue.use(Vuex);
|
||||||
const store = createStore(router);
|
const store = createStore(router);
|
||||||
|
|
||||||
|
Vue.config.productionTip = false;
|
||||||
|
|
||||||
const route = require('./../../../vendor/tightenco/ziggy/src/js/route').default;
|
const route = require('./../../../vendor/tightenco/ziggy/src/js/route').default;
|
||||||
|
|
||||||
Vue.config.productionTip = false;
|
|
||||||
Vue.mixin({ methods: { route } });
|
Vue.mixin({ methods: { route } });
|
||||||
Vue.mixin(flash);
|
Vue.mixin(flash);
|
||||||
|
|
||||||
|
@ -38,9 +36,8 @@ Vue.use(vuexI18n.plugin, store);
|
||||||
Vue.i18n.add('en', Locales.en);
|
Vue.i18n.add('en', Locales.en);
|
||||||
Vue.i18n.set('en');
|
Vue.i18n.set('en');
|
||||||
|
|
||||||
Vue.component('font-awesome-icon', FontAwesomeIcon);
|
if (module.hot) {
|
||||||
|
module.hot.accept();
|
||||||
|
}
|
||||||
require('./bootstrap');
|
|
||||||
|
|
||||||
const app = new Vue({ store, router }).$mount('#pterodactyl');
|
const app = new Vue({ store, router }).$mount('#pterodactyl');
|
||||||
|
|
21
resources/assets/scripts/bootstrap.js
vendored
21
resources/assets/scripts/bootstrap.js
vendored
|
@ -1,3 +1,5 @@
|
||||||
|
import axios from './helpers/axios';
|
||||||
|
|
||||||
window._ = require('lodash');
|
window._ = require('lodash');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -10,24 +12,7 @@ try {
|
||||||
window.$ = window.jQuery = require('jquery');
|
window.$ = window.jQuery = require('jquery');
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
|
|
||||||
/**
|
window.axios = axios;
|
||||||
* We'll load the axios HTTP library which allows us to easily issue requests
|
|
||||||
* to our Laravel back-end. This library automatically handles sending the
|
|
||||||
* CSRF token as a header based on the value of the "XSRF" token cookie.
|
|
||||||
*/
|
|
||||||
|
|
||||||
window.axios = require('axios');
|
|
||||||
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
|
|
||||||
window.axios.defaults.headers.common['Accept'] = 'application/json';
|
|
||||||
window.axios.defaults.headers.common['Authorization'] = 'Bearer ' + localStorage.token || '';
|
|
||||||
|
|
||||||
if (typeof phpdebugbar !== 'undefined') {
|
|
||||||
window.axios.interceptors.response.use(function (response) {
|
|
||||||
phpdebugbar.ajaxHandler.handle(response.request);
|
|
||||||
|
|
||||||
return response;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Next we will register the CSRF Token as a common header with Axios so that
|
* Next we will register the CSRF Token as a common header with Axios so that
|
||||||
|
|
|
@ -77,32 +77,21 @@
|
||||||
this.$data.showSpinner = true;
|
this.$data.showSpinner = true;
|
||||||
|
|
||||||
this.clearFlashes();
|
this.clearFlashes();
|
||||||
axios.post(this.route('auth.login'), {
|
this.$store.dispatch('auth/login', { user: this.$props.user.email, password: this.$props.user.password })
|
||||||
user: this.$props.user.email,
|
.then(response => {
|
||||||
password: this.$props.user.password,
|
if (response.complete) {
|
||||||
})
|
return window.location = response.intended;
|
||||||
.then(function (response) {
|
|
||||||
// If there is a 302 redirect or some other odd behavior (basically, response that isnt
|
|
||||||
// in JSON format) throw an error and don't try to continue with the login.
|
|
||||||
if (!(response.data instanceof Object)) {
|
|
||||||
throw new Error('An error was encountered while processing this request.');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.data.complete) {
|
this.$props.user.password = '';
|
||||||
localStorage.setItem('token', response.data.token);
|
this.$data.showSpinner = false;
|
||||||
self.$store.dispatch('login');
|
this.$router.push({name: 'checkpoint', query: {token: response.login_token}});
|
||||||
return window.location = response.data.intended;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.$props.user.password = '';
|
|
||||||
self.$data.showSpinner = false;
|
|
||||||
self.$router.push({name: 'checkpoint', query: {token: response.data.login_token}});
|
|
||||||
})
|
})
|
||||||
.catch(function (err) {
|
.catch(err => {
|
||||||
self.$props.user.password = '';
|
this.$props.user.password = '';
|
||||||
self.$data.showSpinner = false;
|
this.$data.showSpinner = false;
|
||||||
self.$refs.password.focus();
|
this.$refs.password.focus();
|
||||||
self.$store.dispatch('logout');
|
this.$store.dispatch('auth/logout');
|
||||||
|
|
||||||
if (!err.response) {
|
if (!err.response) {
|
||||||
return console.error(err);
|
return console.error(err);
|
||||||
|
|
|
@ -29,6 +29,7 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
|
import Server from '../../models/server';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import Flash from '../Flash';
|
import Flash from '../Flash';
|
||||||
import ServerBox from './ServerBox';
|
import ServerBox from './ServerBox';
|
||||||
|
@ -75,7 +76,6 @@
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle a search for servers but only call the search function every 500ms
|
* Handle a search for servers but only call the search function every 500ms
|
||||||
* at the fastest.
|
* at the fastest.
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="server-box animate fadein">
|
<div class="server-box animate fadein">
|
||||||
<router-link :to="{ name: 'server', params: { id: server.identifier }}" class="content">
|
<router-link :to="{ name: 'server', params: { serverID: server.identifier }}" class="content">
|
||||||
<div class="float-right">
|
<div class="float-right">
|
||||||
<div class="indicator" :class="status"></div>
|
<div class="indicator" :class="status"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -38,32 +38,32 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="sidenav">
|
<div class="sidenav">
|
||||||
<router-link :to="{ name: 'server' }">
|
<router-link :to="{ name: 'server', params: { serverID: this.$route.params.serverID } }">
|
||||||
<font-awesome-icon class="mr-2" fixed-with icon="terminal"/>
|
<terminal-icon style="height: 1em;"></terminal-icon>
|
||||||
Console
|
Console
|
||||||
</router-link>
|
</router-link>
|
||||||
<router-link :to="{ name: 'server-files' }">
|
<router-link :to="{ name: 'server-files' }">
|
||||||
<font-awesome-icon class="mr-2" fixed-with icon="folder-open"/>
|
<folder-icon style="height: 1em;"></folder-icon>
|
||||||
Files
|
Files
|
||||||
</router-link>
|
</router-link>
|
||||||
<router-link :to="{ name: 'server-subusers' }">
|
<router-link :to="{ name: 'server-subusers' }">
|
||||||
<font-awesome-icon class="mr-2" fixed-with icon="users"/>
|
<users-icon style="height: 1em;"></users-icon>
|
||||||
Subusers
|
Subusers
|
||||||
</router-link>
|
</router-link>
|
||||||
<router-link :to="{ name: 'server-schedules' }">
|
<router-link :to="{ name: 'server-schedules' }">
|
||||||
<font-awesome-icon class="mr-2" fixed-with icon="calendar-alt"/>
|
<calendar-icon style="height: 1em;"></calendar-icon>
|
||||||
Schedules
|
Schedules
|
||||||
</router-link>
|
</router-link>
|
||||||
<router-link :to="{ name: 'server-databases' }">
|
<router-link :to="{ name: 'server-databases' }">
|
||||||
<font-awesome-icon class="mr-2" fixed-with icon="database"/>
|
<database-icon style="height: 1em;"></database-icon>
|
||||||
Databases
|
Databases
|
||||||
</router-link>
|
</router-link>
|
||||||
<router-link :to="{ name: 'server-allocations' }">
|
<router-link :to="{ name: 'server-allocations' }">
|
||||||
<font-awesome-icon class="mr-2" fixed-with icon="globe"/>
|
<globe-icon style="height: 1em;"></globe-icon>
|
||||||
Allocations
|
Allocations
|
||||||
</router-link>
|
</router-link>
|
||||||
<router-link :to="{ name: 'server-settings' }">
|
<router-link :to="{ name: 'server-settings' }">
|
||||||
<font-awesome-icon class="mr-2" fixed-with icon="cog"/>
|
<settings-icon style="height: 1em;"></settings-icon>
|
||||||
Settings
|
Settings
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
|
@ -77,10 +77,14 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { TerminalIcon, FolderIcon, UsersIcon, CalendarIcon, DatabaseIcon, GlobeIcon, SettingsIcon } from 'vue-feather-icons'
|
||||||
import ServerConsole from "./ServerConsole";
|
import ServerConsole from "./ServerConsole";
|
||||||
import Navigation from '../core/Navigation';
|
import Navigation from '../core/Navigation';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {Navigation, ServerConsole}
|
components: {
|
||||||
|
Navigation, ServerConsole, TerminalIcon, FolderIcon, UsersIcon,
|
||||||
|
CalendarIcon, DatabaseIcon, GlobeIcon, SettingsIcon
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
1
resources/assets/scripts/helpers/.gitignore
vendored
Normal file
1
resources/assets/scripts/helpers/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
ziggy.js
|
22
resources/assets/scripts/helpers/axios.js
Normal file
22
resources/assets/scripts/helpers/axios.js
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import User from './../models/user';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We'll load the axios HTTP library which allows us to easily issue requests
|
||||||
|
* to our Laravel back-end. This library automatically handles sending the
|
||||||
|
* CSRF token as a header based on the value of the "XSRF" token cookie.
|
||||||
|
*/
|
||||||
|
|
||||||
|
let axios = require('axios');
|
||||||
|
axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
|
||||||
|
axios.defaults.headers.common['Accept'] = 'application/json';
|
||||||
|
axios.defaults.headers.common['Authorization'] = `Bearer ${User.getToken()}`;
|
||||||
|
|
||||||
|
if (typeof phpdebugbar !== 'undefined') {
|
||||||
|
axios.interceptors.response.use(function (response) {
|
||||||
|
phpdebugbar.ajaxHandler.handle(response.request);
|
||||||
|
|
||||||
|
return response;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export default axios;
|
File diff suppressed because one or more lines are too long
|
@ -1,114 +1,21 @@
|
||||||
import { Collection, Model } from 'vue-mc';
|
export default class Server {
|
||||||
|
constructor({
|
||||||
/**
|
identifier,
|
||||||
* A generic server model used throughout the code base.
|
uuid,
|
||||||
*/
|
name,
|
||||||
export class Server extends Model {
|
node,
|
||||||
/**
|
description,
|
||||||
* Identifier the primary identifier for this model.
|
allocation,
|
||||||
*
|
limits,
|
||||||
* @returns {{identifier: string}}
|
feature_limits
|
||||||
*/
|
}) {
|
||||||
static options() {
|
this.identifier = identifier;
|
||||||
return {
|
this.uuid = uuid;
|
||||||
identifier: 'identifier',
|
this.name = name;
|
||||||
};
|
this.node = node;
|
||||||
}
|
this.description = description;
|
||||||
|
this.allocation = allocation;
|
||||||
/**
|
this.limits = limits;
|
||||||
* Return the defaults for this model.
|
this.feature_limits = feature_limits;
|
||||||
*
|
|
||||||
* @returns {object}
|
|
||||||
*/
|
|
||||||
static defaults() {
|
|
||||||
return {
|
|
||||||
uuid: null,
|
|
||||||
identifier: null,
|
|
||||||
name: '',
|
|
||||||
description: '',
|
|
||||||
node: '',
|
|
||||||
limits: {
|
|
||||||
memory: 0,
|
|
||||||
swap: 0,
|
|
||||||
disk: 0,
|
|
||||||
io: 0,
|
|
||||||
cpu: 0,
|
|
||||||
},
|
|
||||||
allocation: {
|
|
||||||
ip: null,
|
|
||||||
port: null,
|
|
||||||
},
|
|
||||||
feature_limits: {
|
|
||||||
databases: 0,
|
|
||||||
allocations: 0,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Mutations to apply to items in this model.
|
|
||||||
*
|
|
||||||
* @returns {{name: StringConstructor, description: StringConstructor}}
|
|
||||||
*/
|
|
||||||
static mutations() {
|
|
||||||
return {
|
|
||||||
uuid: String,
|
|
||||||
identifier: String,
|
|
||||||
name: String,
|
|
||||||
description: String,
|
|
||||||
node: String,
|
|
||||||
limits: {
|
|
||||||
memory: Number,
|
|
||||||
swap: Number,
|
|
||||||
disk: Number,
|
|
||||||
io: Number,
|
|
||||||
cpu: Number,
|
|
||||||
},
|
|
||||||
allocation: {
|
|
||||||
ip: String,
|
|
||||||
port: Number,
|
|
||||||
},
|
|
||||||
feature_limits: {
|
|
||||||
databases: Number,
|
|
||||||
allocations: Number,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Routes to use when building models.
|
|
||||||
*
|
|
||||||
* @returns {{fetch: string}}
|
|
||||||
*/
|
|
||||||
static routes() {
|
|
||||||
return {
|
|
||||||
fetch: '/api/client/servers/{identifier}',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ServerCollection extends Collection {
|
|
||||||
static model() {
|
|
||||||
return Server;
|
|
||||||
}
|
|
||||||
|
|
||||||
static defaults() {
|
|
||||||
return {
|
|
||||||
orderBy: identifier,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
static routes() {
|
|
||||||
return {
|
|
||||||
fetch: '/api/client',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
get todo() {
|
|
||||||
return this.sum('done');
|
|
||||||
}
|
|
||||||
|
|
||||||
get done() {
|
|
||||||
return this.todo === 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,48 +1,63 @@
|
||||||
import { Collection, Model } from 'vue-mc';
|
import isString from 'lodash/isString';
|
||||||
import JwtDecode from 'jwt-decode';
|
import jwtDecode from 'jwt-decode';
|
||||||
|
|
||||||
export class User extends Model {
|
export default class User {
|
||||||
static defaults() {
|
/**
|
||||||
return {
|
* Get a new user model from the JWT.
|
||||||
id: null,
|
*
|
||||||
uuid: '',
|
* @return {User | null}
|
||||||
username: '',
|
*/
|
||||||
email: '',
|
static fromToken(token) {
|
||||||
name_first: '',
|
if (!isString(token)) {
|
||||||
name_last: '',
|
token = localStorage.getItem('token');
|
||||||
language: 'en',
|
|
||||||
root_admin: false,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!isString(token) || token.length < 1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = jwtDecode(token);
|
||||||
|
if (data.user) {
|
||||||
|
return new User(data.user);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
static mutations() {
|
/**
|
||||||
return {
|
* Return the JWT for the authenticated user.
|
||||||
id: Number,
|
*
|
||||||
uuid: String,
|
* @returns {string | null}
|
||||||
username: String,
|
*/
|
||||||
email: String,
|
static getToken()
|
||||||
name_first: String,
|
{
|
||||||
name_last: String,
|
return localStorage.getItem('token');
|
||||||
language: String,
|
|
||||||
root_admin: Boolean,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromJWT(token) {
|
/**
|
||||||
return new User(JwtDecode(token).user || {});
|
* Create a new user model.
|
||||||
}
|
*
|
||||||
}
|
* @param {Boolean} admin
|
||||||
|
* @param {String} username
|
||||||
export class UserCollection extends Collection {
|
* @param {String} email
|
||||||
static model() {
|
* @param {String} first_name
|
||||||
return User;
|
* @param {String} last_name
|
||||||
}
|
* @param {String} language
|
||||||
|
*/
|
||||||
get todo() {
|
constructor({
|
||||||
return this.sum('done');
|
admin,
|
||||||
}
|
username,
|
||||||
|
email,
|
||||||
get done() {
|
first_name,
|
||||||
return this.todo === 0;
|
last_name,
|
||||||
|
language,
|
||||||
|
}) {
|
||||||
|
this.admin = admin;
|
||||||
|
this.username = username;
|
||||||
|
this.email = email;
|
||||||
|
this.name = `${first_name} ${last_name}`;
|
||||||
|
this.first_name = first_name;
|
||||||
|
this.last_name = last_name;
|
||||||
|
this.language = language;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
import Vuex from 'vuex';
|
import Vuex from 'vuex';
|
||||||
import { sync } from 'vuex-router-sync';
|
import { sync } from 'vuex-router-sync';
|
||||||
import { serverModule } from "./modules/server";
|
import { serverModule } from "./modules/server";
|
||||||
import { userModule } from './modules/user'
|
import { userModule } from './modules/user';
|
||||||
|
import { authModule } from "./modules/auth";
|
||||||
|
|
||||||
const createStore = (router) => {
|
const createStore = (router) => {
|
||||||
const store = new Vuex.Store({
|
const store = new Vuex.Store({
|
||||||
//strict: process.env.NODE_ENV !== 'production',
|
strict: process.env.NODE_ENV !== 'production',
|
||||||
modules: {
|
modules: {
|
||||||
userModule,
|
userModule,
|
||||||
serverModule,
|
serverModule,
|
||||||
|
authModule,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
sync(store, router);
|
sync(store, router);
|
||||||
|
|
71
resources/assets/scripts/store/modules/auth.js
Normal file
71
resources/assets/scripts/store/modules/auth.js
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
import User from './../../models/user';
|
||||||
|
const route = require('./../../../../../vendor/tightenco/ziggy/src/js/route').default;
|
||||||
|
|
||||||
|
export const authModule = {
|
||||||
|
namespaced: true,
|
||||||
|
state: {
|
||||||
|
user: User.fromToken(),
|
||||||
|
},
|
||||||
|
getters: {
|
||||||
|
/**
|
||||||
|
* Return the currently authenticated user.
|
||||||
|
*
|
||||||
|
* @param state
|
||||||
|
* @returns {User|null}
|
||||||
|
*/
|
||||||
|
currentUser: function (state) {
|
||||||
|
return state.user;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setters: {},
|
||||||
|
actions: {
|
||||||
|
login: ({commit}, {user, password}) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
window.axios.post(route('auth.login'), {user, password})
|
||||||
|
.then(response => {
|
||||||
|
commit('logout');
|
||||||
|
|
||||||
|
// If there is a 302 redirect or some other odd behavior (basically, response that isnt
|
||||||
|
// in JSON format) throw an error and don't try to continue with the login.
|
||||||
|
if (!(response.data instanceof Object)) {
|
||||||
|
return reject(new Error('An error was encountered while processing this request.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.data.complete) {
|
||||||
|
commit('login', {jwt: response.data.jwt});
|
||||||
|
return resolve({
|
||||||
|
complete: true,
|
||||||
|
intended: response.data.intended,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolve({
|
||||||
|
complete: false,
|
||||||
|
token: response.data.login_token,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(reject);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
logout: function ({commit}) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
window.axios.get(route('auth.logout'))
|
||||||
|
.then(() => {
|
||||||
|
commit('logout');
|
||||||
|
return resolve();
|
||||||
|
})
|
||||||
|
.catch(reject);
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mutations: {
|
||||||
|
login: function (state, {jwt}) {
|
||||||
|
localStorage.setItem('token', jwt);
|
||||||
|
state.user = User.fromToken(jwt);
|
||||||
|
},
|
||||||
|
logout: function (state) {
|
||||||
|
localStorage.removeItem('token');
|
||||||
|
state.user = null;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
|
@ -19,7 +19,7 @@
|
||||||
@show
|
@show
|
||||||
|
|
||||||
@section('assets')
|
@section('assets')
|
||||||
{!! $asset->css('assets/css/bundle.css') !!}
|
{!! $asset->css('main.css') !!}
|
||||||
@show
|
@show
|
||||||
|
|
||||||
@include('layouts.scripts')
|
@include('layouts.scripts')
|
||||||
|
@ -33,7 +33,7 @@
|
||||||
@yield('below-container')
|
@yield('below-container')
|
||||||
@show
|
@show
|
||||||
@section('scripts')
|
@section('scripts')
|
||||||
{!! $asset->js('assets/scripts/app.js') !!}
|
{!! $asset->js('main.js') !!}
|
||||||
@show
|
@show
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -12,6 +12,10 @@ use Pterodactyl\Http\Middleware\Api\Client\AuthenticateClientAccess;
|
||||||
*/
|
*/
|
||||||
Route::get('/', 'ClientController@index')->name('api.client.index');
|
Route::get('/', 'ClientController@index')->name('api.client.index');
|
||||||
|
|
||||||
|
Route::group(['prefix' => '/account'], function () {
|
||||||
|
Route::get('/', 'AccountController@index')->name('api.client.account');
|
||||||
|
});
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
| Client Control API
|
| Client Control API
|
||||||
|
|
|
@ -1,42 +1,145 @@
|
||||||
|
const _ = require('lodash');
|
||||||
|
const path = require('path');
|
||||||
|
const tailwind = require('tailwindcss');
|
||||||
|
const glob = require('glob-all');
|
||||||
|
|
||||||
|
const AssetsManifestPlugin = require('webpack-assets-manifest');
|
||||||
|
const CleanPlugin = require('clean-webpack-plugin');
|
||||||
|
const ExtractTextPlugin = require('extract-text-webpack-plugin');
|
||||||
|
const ShellPlugin = require('webpack-shell-plugin');
|
||||||
|
const PurgeCssPlugin = require('purgecss-webpack-plugin');
|
||||||
|
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
|
||||||
|
// Custom PurgeCSS extractor for Tailwind that allows special characters in
|
||||||
|
// class names.
|
||||||
|
//
|
||||||
|
// https://github.com/FullHuman/purgecss#extractor
|
||||||
|
class TailwindExtractor {
|
||||||
|
static extract (content) {
|
||||||
|
return content.match(/[A-z0-9-:\/]+/g) || [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const basePlugins = [
|
||||||
|
new CleanPlugin(path.resolve(__dirname, 'public/assets')),
|
||||||
|
new ShellPlugin({
|
||||||
|
onBuildStart: [
|
||||||
|
'php artisan vue-i18n:generate',
|
||||||
|
'php artisan ziggy:generate resources/assets/scripts/helpers/ziggy.js',
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
new ExtractTextPlugin('bundle-[hash].css', {
|
||||||
|
allChunks: true,
|
||||||
|
}),
|
||||||
|
new AssetsManifestPlugin({
|
||||||
|
writeToDisk: true,
|
||||||
|
publicPath: true,
|
||||||
|
integrity: true,
|
||||||
|
integrityHashes: ['sha384'],
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
const productionPlugins = [
|
||||||
|
new PurgeCssPlugin({
|
||||||
|
paths: glob.sync([
|
||||||
|
path.join(__dirname, 'resources/assets/scripts/**/*.vue'),
|
||||||
|
path.join(__dirname, 'resources/themes/pterodactyl/**/*.blade.php'),
|
||||||
|
]),
|
||||||
|
extractors: [
|
||||||
|
{
|
||||||
|
extractor: TailwindExtractor,
|
||||||
|
extensions: ['html', 'js', 'php', 'vue'],
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
new UglifyJsPlugin({
|
||||||
|
include: [
|
||||||
|
path.join(__dirname, 'resources/assets/scripts'),
|
||||||
|
path.join(__dirname, 'node_modules'),
|
||||||
|
path.join(__dirname, 'vendor/tightenco'),
|
||||||
|
],
|
||||||
|
cache: true,
|
||||||
|
parallel: 2,
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
entry: './resources/assets/scripts/app.js',
|
mode: process.env.NODE_ENV,
|
||||||
|
devtool: process.env.NODE_ENV === 'production' ? false : 'source-map',
|
||||||
|
performance: {
|
||||||
|
hints: false,
|
||||||
|
},
|
||||||
|
// Passing an array loads them all but only exports the last.
|
||||||
|
entry: ['./resources/assets/styles/main.css', './resources/assets/scripts/app.js'],
|
||||||
output: {
|
output: {
|
||||||
filename: 'app.js',
|
path: path.resolve(__dirname, 'public/assets'),
|
||||||
|
filename: 'bundle-[hash].js',
|
||||||
|
publicPath: _.get(process.env, 'PUBLIC_PATH', '') + '/assets/',
|
||||||
|
crossOriginLoading: 'anonymous',
|
||||||
},
|
},
|
||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
{
|
{
|
||||||
test: /\.vue$/,
|
test: /\.vue$/,
|
||||||
loader: 'vue-loader',
|
loader: 'vue-loader',
|
||||||
options: {
|
|
||||||
postcss: [
|
|
||||||
require('postcss-import'),
|
|
||||||
require('postcss-preset-env')({stage: 0}),
|
|
||||||
require('tailwindcss')('./tailwind.js'),
|
|
||||||
require('autoprefixer'),
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.js$/,
|
test: /\.js$/,
|
||||||
exclude: /(node_modules|vendor)/,
|
include: [
|
||||||
use: [{
|
path.resolve(__dirname, 'resources'),
|
||||||
loader: "babel-loader"
|
],
|
||||||
}]
|
loader: 'babel-loader?cacheDirectory',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.pug$/,
|
test: /\.css$/,
|
||||||
loader: 'pug-plain-loader'
|
include: [
|
||||||
},
|
path.resolve(__dirname, 'resources'),
|
||||||
|
],
|
||||||
|
use: ExtractTextPlugin.extract({
|
||||||
|
fallback: 'style-loader',
|
||||||
|
use: [{
|
||||||
|
loader: 'css-loader',
|
||||||
|
options: {
|
||||||
|
sourceMap: true,
|
||||||
|
importLoaders: 1,
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
loader: 'postcss-loader',
|
||||||
|
options: {
|
||||||
|
ident: 'postcss',
|
||||||
|
sourceMap: true,
|
||||||
|
plugins: [
|
||||||
|
require('postcss-import'),
|
||||||
|
tailwind('./tailwind.js'),
|
||||||
|
require('postcss-preset-env')({stage: 0}),
|
||||||
|
require('precss'),
|
||||||
|
require('autoprefixer'),
|
||||||
|
require('cssnano'),
|
||||||
|
]
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
}),
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
// 'vue': 'vue/dist/vue.js'
|
|
||||||
'vue$': 'vue/dist/vue.esm.js'
|
'vue$': 'vue/dist/vue.esm.js'
|
||||||
},
|
},
|
||||||
extensions: ['*', '.js', '.vue', '.json']
|
extensions: ['.js', '.vue', '.json'],
|
||||||
|
symlinks: false,
|
||||||
},
|
},
|
||||||
plugins: [],
|
plugins: process.env.NODE_ENV === 'production' ? basePlugins.concat(productionPlugins) : basePlugins,
|
||||||
devtool: 'source-map',
|
serve: {
|
||||||
|
content: "./public/",
|
||||||
|
dev: {
|
||||||
|
publicPath: "/assets/",
|
||||||
|
headers: {
|
||||||
|
"Access-Control-Allow-Origin": "*",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
hot: {
|
||||||
|
hmr: true,
|
||||||
|
reload: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue