Merge branch 'develop' into matthewpi/security-keys-backport
This commit is contained in:
commit
f631ac1946
1153 changed files with 25099 additions and 37002 deletions
20
.env.ci
20
.env.ci
|
@ -1,20 +0,0 @@
|
||||||
APP_ENV=testing
|
|
||||||
APP_DEBUG=true
|
|
||||||
APP_KEY=SomeRandomString3232RandomString
|
|
||||||
APP_THEME=pterodactyl
|
|
||||||
APP_TIMEZONE=UTC
|
|
||||||
APP_URL=http://localhost/
|
|
||||||
APP_ENVIRONMENT_ONLY=true
|
|
||||||
|
|
||||||
DB_CONNECTION=mysql
|
|
||||||
DB_HOST=127.0.0.1
|
|
||||||
DB_DATABASE=testing
|
|
||||||
DB_USERNAME=root
|
|
||||||
DB_PASSWORD=
|
|
||||||
|
|
||||||
CACHE_DRIVER=array
|
|
||||||
SESSION_DRIVER=array
|
|
||||||
MAIL_DRIVER=array
|
|
||||||
QUEUE_DRIVER=sync
|
|
||||||
|
|
||||||
HASHIDS_SALT=test123
|
|
|
@ -1,7 +1,6 @@
|
||||||
APP_ENV=production
|
APP_ENV=production
|
||||||
APP_DEBUG=false
|
APP_DEBUG=false
|
||||||
APP_KEY=
|
APP_KEY=
|
||||||
APP_THEME=pterodactyl
|
|
||||||
APP_TIMEZONE=UTC
|
APP_TIMEZONE=UTC
|
||||||
APP_URL=http://panel.example.com
|
APP_URL=http://panel.example.com
|
||||||
APP_LOCALE=en
|
APP_LOCALE=en
|
||||||
|
@ -41,4 +40,4 @@ MAIL_FROM_NAME="Pterodactyl Panel"
|
||||||
# mail servers such as Gmail to reject your mail.
|
# mail servers such as Gmail to reject your mail.
|
||||||
#
|
#
|
||||||
# @see: https://github.com/pterodactyl/panel/pull/3110
|
# @see: https://github.com/pterodactyl/panel/pull/3110
|
||||||
# SERVER_NAME=panel.example.com
|
# MAIL_EHLO_DOMAIN=panel.example.com
|
||||||
|
|
28
.eslintrc.js
28
.eslintrc.js
|
@ -1,9 +1,3 @@
|
||||||
const prettier = {
|
|
||||||
singleQuote: true,
|
|
||||||
jsxSingleQuote: true,
|
|
||||||
printWidth: 120,
|
|
||||||
};
|
|
||||||
|
|
||||||
/** @type {import('eslint').Linter.Config} */
|
/** @type {import('eslint').Linter.Config} */
|
||||||
module.exports = {
|
module.exports = {
|
||||||
parser: '@typescript-eslint/parser',
|
parser: '@typescript-eslint/parser',
|
||||||
|
@ -21,33 +15,29 @@ module.exports = {
|
||||||
version: 'detect',
|
version: 'detect',
|
||||||
},
|
},
|
||||||
linkComponents: [
|
linkComponents: [
|
||||||
{name: 'Link', linkAttribute: 'to'},
|
{ name: 'Link', linkAttribute: 'to' },
|
||||||
{name: 'NavLink', linkAttribute: 'to'},
|
{ name: 'NavLink', linkAttribute: 'to' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
env: {
|
env: {
|
||||||
browser: true,
|
browser: true,
|
||||||
es6: true,
|
es6: true,
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: ['react', 'react-hooks', 'prettier', '@typescript-eslint'],
|
||||||
'react',
|
|
||||||
'react-hooks',
|
|
||||||
'prettier',
|
|
||||||
'@typescript-eslint',
|
|
||||||
],
|
|
||||||
extends: [
|
extends: [
|
||||||
// 'standard',
|
// 'standard',
|
||||||
'eslint:recommended',
|
'eslint:recommended',
|
||||||
'plugin:react/recommended',
|
'plugin:react/recommended',
|
||||||
|
'plugin:react/jsx-runtime',
|
||||||
'plugin:@typescript-eslint/recommended',
|
'plugin:@typescript-eslint/recommended',
|
||||||
'plugin:jest-dom/recommended',
|
|
||||||
],
|
],
|
||||||
rules: {
|
rules: {
|
||||||
eqeqeq: 'error',
|
eqeqeq: 'error',
|
||||||
'prettier/prettier': ['error', prettier],
|
'prettier/prettier': ['error', {}, { usePrettierrc: true }],
|
||||||
// TypeScript can infer this significantly better than eslint ever can.
|
// TypeScript can infer this significantly better than eslint ever can.
|
||||||
'react/prop-types': 0,
|
'react/prop-types': 0,
|
||||||
'react/display-name': 0,
|
'react/display-name': 0,
|
||||||
|
'react/no-unknown-property': ['error', {ignore: ['css']}],
|
||||||
'@typescript-eslint/no-explicit-any': 0,
|
'@typescript-eslint/no-explicit-any': 0,
|
||||||
'@typescript-eslint/no-non-null-assertion': 0,
|
'@typescript-eslint/no-non-null-assertion': 0,
|
||||||
// This setup is required to avoid a spam of errors when running eslint about React being
|
// This setup is required to avoid a spam of errors when running eslint about React being
|
||||||
|
@ -56,7 +46,7 @@ module.exports = {
|
||||||
// @see https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-use-before-define.md#how-to-use
|
// @see https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-use-before-define.md#how-to-use
|
||||||
'no-use-before-define': 0,
|
'no-use-before-define': 0,
|
||||||
'@typescript-eslint/no-use-before-define': 'warn',
|
'@typescript-eslint/no-use-before-define': 'warn',
|
||||||
'@typescript-eslint/no-unused-vars': ['warn', {argsIgnorePattern: '^_', varsIgnorePattern: '^_'}],
|
'@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }],
|
||||||
'@typescript-eslint/ban-ts-comment': ['error', {'ts-expect-error': 'allow-with-description'}],
|
'@typescript-eslint/ban-ts-comment': ['error', { 'ts-expect-error': 'allow-with-description' }],
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
2
.github/ISSUE_TEMPLATE/config.yml
vendored
2
.github/ISSUE_TEMPLATE/config.yml
vendored
|
@ -1,4 +1,4 @@
|
||||||
blank_issues_enabled: false
|
blank_issues_enabled: true
|
||||||
contact_links:
|
contact_links:
|
||||||
- name: Installation Help
|
- name: Installation Help
|
||||||
url: https://discord.gg/pterodactyl
|
url: https://discord.gg/pterodactyl
|
||||||
|
|
21
.github/docker/Caddyfile
vendored
Normal file
21
.github/docker/Caddyfile
vendored
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
:8080 {
|
||||||
|
root * /var/www/pterodactyl/public/
|
||||||
|
file_server
|
||||||
|
|
||||||
|
header {
|
||||||
|
-Server
|
||||||
|
-X-Powered-By
|
||||||
|
Referrer-Policy "same-origin"
|
||||||
|
X-Frame-Options "deny"
|
||||||
|
X-XSS-Protection "1; mode=block"
|
||||||
|
X-Content-Type-Options "nosniff"
|
||||||
|
}
|
||||||
|
|
||||||
|
encode gzip zstd
|
||||||
|
|
||||||
|
php_fastcgi 127.0.0.1:9000 {
|
||||||
|
trusted_proxies 172.20.0.0/16
|
||||||
|
}
|
||||||
|
|
||||||
|
try_files {path} {path}/ /index.php?{query}
|
||||||
|
}
|
76
.github/docker/README.md
vendored
76
.github/docker/README.md
vendored
|
@ -1,76 +0,0 @@
|
||||||
# Pterodactyl Panel - Docker Image
|
|
||||||
This is a ready to use docker image for the panel.
|
|
||||||
|
|
||||||
## Requirements
|
|
||||||
This docker image requires some additional software to function. The software can either be provided in other containers (see the [docker-compose.yml](https://github.com/pterodactyl/panel/blob/develop/docker-compose.example.yml) as an example) or as existing instances.
|
|
||||||
|
|
||||||
A mysql database is required. We recommend the stock [MariaDB Image](https://hub.docker.com/_/mariadb/) image if you prefer to run it in a docker container. As a non-containerized option we recommend mariadb.
|
|
||||||
|
|
||||||
A caching software is required as well. We recommend the stock [Redis Image](https://hub.docker.com/_/redis/) image. You can choose any of the [supported options](#cache-drivers).
|
|
||||||
|
|
||||||
You can provide additional settings using a custom `.env` file or by setting the appropriate environment variables in the docker-compose file.
|
|
||||||
|
|
||||||
## Setup
|
|
||||||
|
|
||||||
Start the docker container and the required dependencies (either provide existing ones or start containers as well, see the [docker-compose.yml](https://github.com/pterodactyl/panel/blob/develop/docker-compose.example.yml) file as an example.
|
|
||||||
|
|
||||||
After the startup is complete you'll need to create a user.
|
|
||||||
If you are running the docker container without docker-compose, use:
|
|
||||||
```
|
|
||||||
docker exec -it <container id> php artisan p:user:make
|
|
||||||
```
|
|
||||||
If you are using docker compose use
|
|
||||||
```
|
|
||||||
docker-compose exec panel php artisan p:user:make
|
|
||||||
```
|
|
||||||
|
|
||||||
## Environment Variables
|
|
||||||
There are multiple environment variables to configure the panel when not providing your own `.env` file, see the following table for details on each available option.
|
|
||||||
|
|
||||||
Note: If your `APP_URL` starts with `https://` you need to provide an `LE_EMAIL` as well so Certificates can be generated.
|
|
||||||
|
|
||||||
| Variable | Description | Required |
|
|
||||||
| ------------------- | ------------------------------------------------------------------------------ | -------- |
|
|
||||||
| `APP_URL` | The URL the panel will be reachable with (including protocol) | yes |
|
|
||||||
| `APP_TIMEZONE` | The timezone to use for the panel | yes |
|
|
||||||
| `LE_EMAIL` | The email used for letsencrypt certificate generation | yes |
|
|
||||||
| `DB_HOST` | The host of the mysql instance | yes |
|
|
||||||
| `DB_PORT` | The port of the mysql instance | yes |
|
|
||||||
| `DB_DATABASE` | The name of the mysql database | yes |
|
|
||||||
| `DB_USERNAME` | The mysql user | yes |
|
|
||||||
| `DB_PASSWORD` | The mysql password for the specified user | yes |
|
|
||||||
| `CACHE_DRIVER` | The cache driver (see [Cache drivers](#cache-drivers) for detais) | yes |
|
|
||||||
| `SESSION_DRIVER` | | yes |
|
|
||||||
| `QUEUE_DRIVER` | | yes |
|
|
||||||
| `REDIS_HOST` | The hostname or IP address of the redis database | yes |
|
|
||||||
| `REDIS_PASSWORD` | The password used to secure the redis database | maybe |
|
|
||||||
| `REDIS_PORT` | The port the redis database is using on the host | maybe |
|
|
||||||
| `MAIL_DRIVER` | The email driver (see [Mail drivers](#mail-drivers) for details) | yes |
|
|
||||||
| `MAIL_FROM` | The email that should be used as the sender email | yes |
|
|
||||||
| `MAIL_HOST` | The host of your mail driver instance | maybe |
|
|
||||||
| `MAIL_PORT` | The port of your mail driver instance | maybe |
|
|
||||||
| `MAIL_USERNAME` | The username for your mail driver | maybe |
|
|
||||||
| `MAIL_PASSWORD` | The password for your mail driver | maybe |
|
|
||||||
|
|
||||||
|
|
||||||
### Cache drivers
|
|
||||||
You can choose between different cache drivers depending on what you prefer.
|
|
||||||
We recommend redis when using docker as it can be started in a container easily.
|
|
||||||
|
|
||||||
| Driver | Description | Required variables |
|
|
||||||
| -------- | ------------------------------------ | ------------------------------------------------------ |
|
|
||||||
| redis | host where redis is running | `REDIS_HOST` |
|
|
||||||
| redis | port redis is running on | `REDIS_PORT` |
|
|
||||||
| redis | redis database password | `REDIS_PASSWORD` |
|
|
||||||
|
|
||||||
### Mail drivers
|
|
||||||
You can choose between different mail drivers according to your needs.
|
|
||||||
Every driver requires `MAIL_FROM` to be set.
|
|
||||||
|
|
||||||
| Driver | Description | Required variables |
|
|
||||||
| -------- | ------------------------------------ | ------------------------------------------------------------- |
|
|
||||||
| mail | uses the installed php mail | |
|
|
||||||
| mandrill | [Mandrill](http://www.mandrill.com/) | `MAIL_USERNAME` |
|
|
||||||
| postmark | [Postmark](https://postmarkapp.com/) | `MAIL_USERNAME` |
|
|
||||||
| mailgun | [Mailgun](https://www.mailgun.com/) | `MAIL_USERNAME`, `MAIL_HOST` |
|
|
||||||
| smtp | Any SMTP server can be configured | `MAIL_USERNAME`, `MAIL_HOST`, `MAIL_PASSWORD`, `MAIL_PORT` |
|
|
51
.github/docker/default.conf
vendored
51
.github/docker/default.conf
vendored
|
@ -1,51 +0,0 @@
|
||||||
# If using Ubuntu this file should be placed in:
|
|
||||||
# /etc/nginx/sites-available/
|
|
||||||
#
|
|
||||||
# If using CentOS this file should be placed in:
|
|
||||||
# /etc/nginx/conf.d/
|
|
||||||
#
|
|
||||||
server {
|
|
||||||
listen 80;
|
|
||||||
server_name _;
|
|
||||||
|
|
||||||
root /app/public;
|
|
||||||
index index.html index.htm index.php;
|
|
||||||
charset utf-8;
|
|
||||||
|
|
||||||
location / {
|
|
||||||
try_files $uri $uri/ /index.php?$query_string;
|
|
||||||
}
|
|
||||||
|
|
||||||
location = /favicon.ico { access_log off; log_not_found off; }
|
|
||||||
location = /robots.txt { access_log off; log_not_found off; }
|
|
||||||
|
|
||||||
access_log off;
|
|
||||||
error_log /var/log/nginx/pterodactyl.app-error.log error;
|
|
||||||
|
|
||||||
# allow larger file uploads and longer script runtimes
|
|
||||||
client_max_body_size 100m;
|
|
||||||
client_body_timeout 120s;
|
|
||||||
|
|
||||||
sendfile off;
|
|
||||||
|
|
||||||
location ~ \.php$ {
|
|
||||||
fastcgi_split_path_info ^(.+\.php)(/.+)$;
|
|
||||||
# the fastcgi_pass path needs to be changed accordingly when using CentOS
|
|
||||||
fastcgi_pass 127.0.0.1:9000;
|
|
||||||
fastcgi_index index.php;
|
|
||||||
include fastcgi_params;
|
|
||||||
fastcgi_param PHP_VALUE "upload_max_filesize = 100M \n post_max_size=100M";
|
|
||||||
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
|
||||||
fastcgi_param HTTP_PROXY "";
|
|
||||||
fastcgi_intercept_errors off;
|
|
||||||
fastcgi_buffer_size 16k;
|
|
||||||
fastcgi_buffers 4 16k;
|
|
||||||
fastcgi_connect_timeout 300;
|
|
||||||
fastcgi_send_timeout 300;
|
|
||||||
fastcgi_read_timeout 300;
|
|
||||||
}
|
|
||||||
|
|
||||||
location ~ /\.ht {
|
|
||||||
deny all;
|
|
||||||
}
|
|
||||||
}
|
|
70
.github/docker/default_ssl.conf
vendored
70
.github/docker/default_ssl.conf
vendored
|
@ -1,70 +0,0 @@
|
||||||
# If using Ubuntu this file should be placed in:
|
|
||||||
# /etc/nginx/sites-available/
|
|
||||||
#
|
|
||||||
server {
|
|
||||||
listen 80;
|
|
||||||
server_name <domain>;
|
|
||||||
return 301 https://$server_name$request_uri;
|
|
||||||
}
|
|
||||||
|
|
||||||
server {
|
|
||||||
listen 443 ssl http2;
|
|
||||||
server_name <domain>;
|
|
||||||
|
|
||||||
root /app/public;
|
|
||||||
index index.php;
|
|
||||||
|
|
||||||
access_log /var/log/nginx/pterodactyl.app-access.log;
|
|
||||||
error_log /var/log/nginx/pterodactyl.app-error.log error;
|
|
||||||
|
|
||||||
# allow larger file uploads and longer script runtimes
|
|
||||||
client_max_body_size 100m;
|
|
||||||
client_body_timeout 120s;
|
|
||||||
|
|
||||||
sendfile off;
|
|
||||||
|
|
||||||
# strengthen ssl security
|
|
||||||
ssl_certificate /etc/letsencrypt/live/<domain>/fullchain.pem;
|
|
||||||
ssl_certificate_key /etc/letsencrypt/live/<domain>/privkey.pem;
|
|
||||||
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
|
|
||||||
ssl_prefer_server_ciphers on;
|
|
||||||
ssl_session_cache shared:SSL:10m;
|
|
||||||
ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:ECDHE-RSA-AES128-GCM-SHA256:AES256+EECDH:DHE-RSA-AES128-GCM-SHA256:AES256+EDH:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4";
|
|
||||||
|
|
||||||
# See the link below for more SSL information:
|
|
||||||
# https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html
|
|
||||||
#
|
|
||||||
# ssl_dhparam /etc/ssl/certs/dhparam.pem;
|
|
||||||
|
|
||||||
# Add headers to serve security related headers
|
|
||||||
add_header Strict-Transport-Security "max-age=15768000; preload;";
|
|
||||||
add_header X-Content-Type-Options nosniff;
|
|
||||||
add_header X-XSS-Protection "1; mode=block";
|
|
||||||
add_header X-Robots-Tag none;
|
|
||||||
add_header Content-Security-Policy "frame-ancestors 'self'";
|
|
||||||
|
|
||||||
location / {
|
|
||||||
try_files $uri $uri/ /index.php?$query_string;
|
|
||||||
}
|
|
||||||
|
|
||||||
location ~ \.php$ {
|
|
||||||
fastcgi_split_path_info ^(.+\.php)(/.+)$;
|
|
||||||
fastcgi_pass 127.0.0.1:9000;
|
|
||||||
fastcgi_index index.php;
|
|
||||||
include fastcgi_params;
|
|
||||||
fastcgi_param PHP_VALUE "upload_max_filesize = 100M \n post_max_size=100M";
|
|
||||||
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
|
||||||
fastcgi_param HTTP_PROXY "";
|
|
||||||
fastcgi_intercept_errors off;
|
|
||||||
fastcgi_buffer_size 16k;
|
|
||||||
fastcgi_buffers 4 16k;
|
|
||||||
fastcgi_connect_timeout 300;
|
|
||||||
fastcgi_send_timeout 300;
|
|
||||||
fastcgi_read_timeout 300;
|
|
||||||
include /etc/nginx/fastcgi_params;
|
|
||||||
}
|
|
||||||
|
|
||||||
location ~ /\.ht {
|
|
||||||
deny all;
|
|
||||||
}
|
|
||||||
}
|
|
81
.github/docker/entrypoint.sh
vendored
81
.github/docker/entrypoint.sh
vendored
|
@ -1,81 +0,0 @@
|
||||||
#!/bin/ash -e
|
|
||||||
cd /app
|
|
||||||
|
|
||||||
mkdir -p /var/log/panel/logs/ /var/log/supervisord/ /var/log/nginx/ /var/log/php7/ \
|
|
||||||
&& chmod 777 /var/log/panel/logs/ \
|
|
||||||
&& ln -s /var/log/panel/logs/ /app/storage/logs/
|
|
||||||
|
|
||||||
## check for .env file and generate app keys if missing
|
|
||||||
if [ -f /app/var/.env ]; then
|
|
||||||
echo "external vars exist."
|
|
||||||
rm -rf /app/.env
|
|
||||||
ln -s /app/var/.env /app/
|
|
||||||
else
|
|
||||||
echo "external vars don't exist."
|
|
||||||
rm -rf /app/.env
|
|
||||||
touch /app/var/.env
|
|
||||||
|
|
||||||
## manually generate a key because key generate --force fails
|
|
||||||
if [ -z $APP_KEY ]; then
|
|
||||||
echo -e "Generating key."
|
|
||||||
APP_KEY=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1)
|
|
||||||
echo -e "Generated app key: $APP_KEY"
|
|
||||||
echo -e "APP_KEY=$APP_KEY" > /app/var/.env
|
|
||||||
else
|
|
||||||
echo -e "APP_KEY exists in environment, using that."
|
|
||||||
echo -e "APP_KEY=$APP_KEY" > /app/var/.env
|
|
||||||
fi
|
|
||||||
|
|
||||||
ln -s /app/var/.env /app/
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Checking if https is required."
|
|
||||||
if [ -f /etc/nginx/http.d/panel.conf ]; then
|
|
||||||
echo "Using nginx config already in place."
|
|
||||||
if [ $LE_EMAIL ]; then
|
|
||||||
echo "Checking for cert update"
|
|
||||||
certbot certonly -d $(echo $APP_URL | sed 's~http[s]*://~~g') --standalone -m $LE_EMAIL --agree-tos -n
|
|
||||||
else
|
|
||||||
echo "No letsencrypt email is set"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo "Checking if letsencrypt email is set."
|
|
||||||
if [ -z $LE_EMAIL ]; then
|
|
||||||
echo "No letsencrypt email is set using http config."
|
|
||||||
cp .github/docker/default.conf /etc/nginx/http.d/panel.conf
|
|
||||||
else
|
|
||||||
echo "writing ssl config"
|
|
||||||
cp .github/docker/default_ssl.conf /etc/nginx/http.d/panel.conf
|
|
||||||
echo "updating ssl config for domain"
|
|
||||||
sed -i "s|<domain>|$(echo $APP_URL | sed 's~http[s]*://~~g')|g" /etc/nginx/http.d/panel.conf
|
|
||||||
echo "generating certs"
|
|
||||||
certbot certonly -d $(echo $APP_URL | sed 's~http[s]*://~~g') --standalone -m $LE_EMAIL --agree-tos -n
|
|
||||||
fi
|
|
||||||
echo "Removing the default nginx config"
|
|
||||||
rm -rf /etc/nginx/http.d/default.conf
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -z $DB_PORT ]]; then
|
|
||||||
echo -e "DB_PORT not specified, defaulting to 3306"
|
|
||||||
DB_PORT=3306
|
|
||||||
fi
|
|
||||||
|
|
||||||
## check for DB up before starting the panel
|
|
||||||
echo "Checking database status."
|
|
||||||
until nc -z -v -w30 $DB_HOST $DB_PORT
|
|
||||||
do
|
|
||||||
echo "Waiting for database connection..."
|
|
||||||
# wait for 1 seconds before check again
|
|
||||||
sleep 1
|
|
||||||
done
|
|
||||||
|
|
||||||
## make sure the db is set up
|
|
||||||
echo -e "Migrating and Seeding D.B"
|
|
||||||
php artisan migrate --seed --force
|
|
||||||
|
|
||||||
## start cronjobs for the queue
|
|
||||||
echo -e "Starting cron jobs."
|
|
||||||
crond -L /var/log/crond -l 5
|
|
||||||
|
|
||||||
echo -e "Starting supervisord."
|
|
||||||
exec "$@"
|
|
21
.github/docker/php-fpm.conf
vendored
Normal file
21
.github/docker/php-fpm.conf
vendored
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
[global]
|
||||||
|
error_log = /dev/stderr
|
||||||
|
daemonize = no
|
||||||
|
|
||||||
|
[www]
|
||||||
|
user = nobody
|
||||||
|
group = nobody
|
||||||
|
|
||||||
|
listen = 127.0.0.1:9000
|
||||||
|
|
||||||
|
pm = dynamic
|
||||||
|
pm.start_servers = 4
|
||||||
|
pm.min_spare_servers = 4
|
||||||
|
pm.max_spare_servers = 16
|
||||||
|
pm.max_children = 64
|
||||||
|
pm.max_requests = 256
|
||||||
|
|
||||||
|
clear_env = no
|
||||||
|
catch_workers_output = yes
|
||||||
|
|
||||||
|
decorate_workers_output = no
|
78
.github/docker/supervisord.conf
vendored
78
.github/docker/supervisord.conf
vendored
|
@ -1,39 +1,57 @@
|
||||||
[unix_http_server]
|
|
||||||
file=/tmp/supervisor.sock ; path to your socket file
|
|
||||||
|
|
||||||
[supervisord]
|
[supervisord]
|
||||||
logfile=/var/log/supervisord/supervisord.log ; supervisord log file
|
logfile=/dev/stdout
|
||||||
logfile_maxbytes=50MB ; maximum size of logfile before rotation
|
logfile_maxbytes=0
|
||||||
logfile_backups=2 ; number of backed up logfiles
|
loglevel=info
|
||||||
loglevel=error ; info, debug, warn, trace
|
minfds=1024
|
||||||
pidfile=/var/run/supervisord.pid ; pidfile location
|
minprocs=200
|
||||||
nodaemon=false ; run supervisord as a daemon
|
nodaemon=true
|
||||||
minfds=1024 ; number of startup file descriptors
|
pidfile=/dev/null
|
||||||
minprocs=200 ; number of process descriptors
|
|
||||||
user=root ; default user
|
|
||||||
childlogdir=/var/log/supervisord/ ; where child log files will live
|
|
||||||
|
|
||||||
[rpcinterface:supervisor]
|
[unix_http_server]
|
||||||
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
|
file=/tmp/supervisor.sock
|
||||||
|
|
||||||
[supervisorctl]
|
[supervisorctl]
|
||||||
serverurl=unix:///tmp/supervisor.sock ; use a unix:// URL for a unix socket
|
serverurl=unix:///tmp/supervisor.sock
|
||||||
|
|
||||||
[program:php-fpm]
|
[rpcinterface:supervisor]
|
||||||
command=/usr/local/sbin/php-fpm -F
|
supervisor.rpcinterface_factory=supervisor.rpcinterface:make_main_rpcinterface
|
||||||
autostart=true
|
|
||||||
autorestart=true
|
|
||||||
|
|
||||||
[program:queue-worker]
|
[program:caddy]
|
||||||
command=/usr/local/bin/php /app/artisan queue:work --queue=high,standard,low --sleep=3 --tries=3
|
command=/usr/local/bin/caddy run --config /etc/caddy/Caddyfile
|
||||||
user=nginx
|
|
||||||
autostart=true
|
|
||||||
autorestart=true
|
|
||||||
|
|
||||||
[program:nginx]
|
|
||||||
command=/usr/sbin/nginx -g 'daemon off;'
|
|
||||||
autostart=true
|
autostart=true
|
||||||
autorestart=true
|
autorestart=true
|
||||||
priority=10
|
priority=10
|
||||||
stdout_events_enabled=true
|
stdout_logfile=/dev/stdout
|
||||||
stderr_events_enabled=true
|
stdout_logfile_maxbytes=0
|
||||||
|
stderr_logfile=/dev/stderr
|
||||||
|
stderr_logfile_maxbytes=0
|
||||||
|
|
||||||
|
[program:php-fpm]
|
||||||
|
command=/usr/sbin/php-fpm --nodaemonize
|
||||||
|
autostart=true
|
||||||
|
autorestart=true
|
||||||
|
stdout_logfile=/dev/stdout
|
||||||
|
stdout_logfile_maxbytes=0
|
||||||
|
stderr_logfile=/dev/stderr
|
||||||
|
stderr_logfile_maxbytes=0
|
||||||
|
environment=LOG_CHANNEL="stderr"
|
||||||
|
|
||||||
|
[program:queue-worker]
|
||||||
|
command=/usr/bin/php /var/www/pterodactyl/artisan queue:work --queue=standard --sleep=3 --tries=3
|
||||||
|
autostart=true
|
||||||
|
autorestart=true
|
||||||
|
stdout_logfile=/dev/stdout
|
||||||
|
stdout_logfile_maxbytes=0
|
||||||
|
stderr_logfile=/dev/stderr
|
||||||
|
stderr_logfile_maxbytes=0
|
||||||
|
environment=LOG_CHANNEL="stderr"
|
||||||
|
|
||||||
|
[program:yacron]
|
||||||
|
command=/usr/local/bin/yacron -c /etc/yacron.yaml
|
||||||
|
autostart=true
|
||||||
|
autorestart=true
|
||||||
|
stdout_logfile=/dev/stdout
|
||||||
|
stdout_logfile_maxbytes=0
|
||||||
|
stderr_logfile=/dev/stderr
|
||||||
|
stderr_logfile_maxbytes=0
|
||||||
|
environment=LOG_CHANNEL="stderr"
|
||||||
|
|
16
.github/docker/www.conf
vendored
16
.github/docker/www.conf
vendored
|
@ -1,16 +0,0 @@
|
||||||
[www]
|
|
||||||
|
|
||||||
user = nginx
|
|
||||||
group = nginx
|
|
||||||
|
|
||||||
listen = 127.0.0.1:9000
|
|
||||||
listen.owner = nginx
|
|
||||||
listen.group = nginx
|
|
||||||
listen.mode = 0750
|
|
||||||
|
|
||||||
pm = ondemand
|
|
||||||
pm.max_children = 9
|
|
||||||
pm.process_idle_timeout = 10s
|
|
||||||
pm.max_requests = 200
|
|
||||||
|
|
||||||
clear_env = no
|
|
8
.github/docker/yacron.yaml
vendored
Normal file
8
.github/docker/yacron.yaml
vendored
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
jobs:
|
||||||
|
- name: scheduler
|
||||||
|
command:
|
||||||
|
- /usr/bin/php
|
||||||
|
- /var/www/pterodactyl/artisan
|
||||||
|
- schedule:run
|
||||||
|
schedule: "* * * * *"
|
||||||
|
utc: true
|
35
.github/workflows/build.yaml
vendored
35
.github/workflows/build.yaml
vendored
|
@ -1,35 +0,0 @@
|
||||||
name: Build
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- "develop"
|
|
||||||
- "1.0-develop"
|
|
||||||
pull_request:
|
|
||||||
branches:
|
|
||||||
- "develop"
|
|
||||||
- "1.0-develop"
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
ui:
|
|
||||||
name: UI
|
|
||||||
runs-on: ubuntu-20.04
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
node-version: [16]
|
|
||||||
steps:
|
|
||||||
- name: Code Checkout
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: Setup Node
|
|
||||||
uses: actions/setup-node@v3
|
|
||||||
with:
|
|
||||||
node-version: ${{ matrix.node-version }}
|
|
||||||
cache: "yarn"
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: yarn install --frozen-lockfile
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
run: yarn build:production
|
|
72
.github/workflows/ci.yaml
vendored
72
.github/workflows/ci.yaml
vendored
|
@ -1,72 +0,0 @@
|
||||||
name: Tests
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- "develop"
|
|
||||||
- "1.0-develop"
|
|
||||||
pull_request:
|
|
||||||
branches:
|
|
||||||
- "develop"
|
|
||||||
- "1.0-develop"
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
tests:
|
|
||||||
name: Tests
|
|
||||||
runs-on: ubuntu-20.04
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
php: [8.1]
|
|
||||||
database: ["mariadb:10.2", "mysql:8"]
|
|
||||||
services:
|
|
||||||
database:
|
|
||||||
image: ${{ matrix.database }}
|
|
||||||
env:
|
|
||||||
MYSQL_ALLOW_EMPTY_PASSWORD: yes
|
|
||||||
MYSQL_DATABASE: testing
|
|
||||||
ports:
|
|
||||||
- 3306
|
|
||||||
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
|
|
||||||
steps:
|
|
||||||
- name: Code Checkout
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: Get cache directory
|
|
||||||
id: composer-cache
|
|
||||||
run: |
|
|
||||||
echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: Cache
|
|
||||||
uses: actions/cache@v3
|
|
||||||
with:
|
|
||||||
path: ${{ steps.composer-cache.outputs.dir }}
|
|
||||||
key: ${{ runner.os }}-composer-${{ matrix.php }}-${{ hashFiles('**/composer.lock') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-composer-${{ matrix.php }}-
|
|
||||||
|
|
||||||
- name: Setup PHP
|
|
||||||
uses: shivammathur/setup-php@v2
|
|
||||||
with:
|
|
||||||
php-version: ${{ matrix.php }}
|
|
||||||
extensions: bcmath, cli, curl, gd, mbstring, mysql, openssl, pdo, tokenizer, xml, zip
|
|
||||||
tools: composer:v2
|
|
||||||
coverage: none
|
|
||||||
|
|
||||||
- name: Setup .env
|
|
||||||
run: cp .env.ci .env
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: composer install --no-interaction --no-progress --no-suggest --prefer-dist
|
|
||||||
|
|
||||||
- name: Unit tests
|
|
||||||
run: vendor/bin/phpunit --bootstrap vendor/autoload.php tests/Unit
|
|
||||||
if: ${{ always() }}
|
|
||||||
env:
|
|
||||||
DB_HOST: UNIT_NO_DB
|
|
||||||
|
|
||||||
- name: Integration tests
|
|
||||||
run: vendor/bin/phpunit tests/Integration
|
|
||||||
env:
|
|
||||||
DB_PORT: ${{ job.services.database.ports[3306] }}
|
|
||||||
DB_USERNAME: root
|
|
60
.github/workflows/docker.yaml
vendored
60
.github/workflows/docker.yaml
vendored
|
@ -1,61 +1,67 @@
|
||||||
name: Publish Docker Image
|
name: Docker
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- "develop"
|
- develop
|
||||||
- "release/v*"
|
- 1.0-develop
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- develop
|
||||||
|
- 1.0-develop
|
||||||
|
release:
|
||||||
|
types:
|
||||||
|
- published
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
push:
|
push:
|
||||||
name: Push Image to GitHub Packages
|
name: Push
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-20.04
|
||||||
# Always run against a tag, even if the commit into the tag has [docker skip]
|
|
||||||
# within the commit message.
|
|
||||||
if: "!contains(github.ref, 'develop') || (!contains(github.event.head_commit.message, 'skip docker') && !contains(github.event.head_commit.message, 'docker skip'))"
|
if: "!contains(github.ref, 'develop') || (!contains(github.event.head_commit.message, 'skip docker') && !contains(github.event.head_commit.message, 'docker skip'))"
|
||||||
steps:
|
steps:
|
||||||
- name: Code Checkout
|
- name: Code checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Docker Metadata
|
- name: Docker metadata
|
||||||
uses: docker/metadata-action@v4
|
|
||||||
id: docker_meta
|
id: docker_meta
|
||||||
|
uses: docker/metadata-action@v4
|
||||||
with:
|
with:
|
||||||
images: ghcr.io/pterodactyl/panel
|
images: ghcr.io/pterodactyl/panel
|
||||||
|
flavor: |
|
||||||
|
latest=false
|
||||||
|
tags: |
|
||||||
|
type=raw,value=latest,enable=${{ github.event_name == 'release' && github.event.action == 'published' && github.event.release.prerelease == false }}
|
||||||
|
type=ref,event=tag
|
||||||
|
type=ref,event=branch
|
||||||
|
|
||||||
- name: Setup QEMU
|
- name: Setup QEMU
|
||||||
uses: docker/setup-qemu-action@v2
|
uses: docker/setup-qemu-action@v2
|
||||||
|
|
||||||
- name: Setup Docker Buildx
|
- name: Setup Docker buildx
|
||||||
uses: docker/setup-buildx-action@v2
|
uses: docker/setup-buildx-action@v2
|
||||||
|
|
||||||
- name: Docker Login
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@v2
|
uses: docker/login-action@v2
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
password: ${{ secrets.REGISTRY_TOKEN }}
|
password: ${{ secrets.REGISTRY_TOKEN }}
|
||||||
|
|
||||||
- name: Release production build
|
- name: Update version
|
||||||
uses: docker/build-push-action@v2
|
if: "github.event_name == 'release' && github.event.action == 'published'"
|
||||||
if: "contains(github.ref, 'release/v')"
|
env:
|
||||||
with:
|
REF: ${{ github.event.release.tag_name }}
|
||||||
context: .
|
run: |
|
||||||
file: ./Dockerfile
|
sed -i "s/ 'version' => 'canary',/ 'version' => '${REF:1}',/" config/app.php
|
||||||
push: true
|
|
||||||
platforms: linux/amd64,linux/arm64
|
|
||||||
tags: ${{ steps.docker_meta.outputs.tags }}
|
|
||||||
labels: ${{ steps.docker_meta.outputs.labels }}
|
|
||||||
|
|
||||||
- name: Release development build
|
- name: Build and Push
|
||||||
uses: docker/build-push-action@v2
|
uses: docker/build-push-action@v3
|
||||||
if: "contains(github.ref, 'develop')"
|
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: ./Dockerfile
|
file: ./Containerfile
|
||||||
push: ${{ github.event_name != 'pull_request' }}
|
push: ${{ github.event_name != 'pull_request' }}
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64 #,linux/arm64
|
||||||
tags: ${{ steps.docker_meta.outputs.tags }}
|
|
||||||
labels: ${{ steps.docker_meta.outputs.labels }}
|
labels: ${{ steps.docker_meta.outputs.labels }}
|
||||||
|
tags: ${{ steps.docker_meta.outputs.tags }}
|
||||||
cache-from: type=gha
|
cache-from: type=gha
|
||||||
cache-to: type=gha,mode=max
|
cache-to: type=gha,mode=max
|
||||||
|
|
209
.github/workflows/laravel.yaml
vendored
Normal file
209
.github/workflows/laravel.yaml
vendored
Normal file
|
@ -0,0 +1,209 @@
|
||||||
|
name: Laravel
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- "develop"
|
||||||
|
- "1.0-develop"
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- "develop"
|
||||||
|
- "1.0-develop"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
analysis:
|
||||||
|
name: Static Analysis
|
||||||
|
runs-on: ubuntu-20.04
|
||||||
|
env:
|
||||||
|
APP_ENV: testing
|
||||||
|
APP_DEBUG: "true"
|
||||||
|
APP_KEY: SomeRandomString3232RandomString
|
||||||
|
CACHE_DRIVER: array
|
||||||
|
MAIL_MAILER: array
|
||||||
|
SESSION_DRIVER: array
|
||||||
|
QUEUE_CONNECTION: sync
|
||||||
|
steps:
|
||||||
|
- name: Code checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Setup PHP
|
||||||
|
uses: shivammathur/setup-php@v2
|
||||||
|
with:
|
||||||
|
php-version: 8.1
|
||||||
|
extensions: bcmath, cli, curl, gd, mbstring, mysql, openssl, pdo, tokenizer, xml, zip
|
||||||
|
tools: composer:v2
|
||||||
|
coverage: none
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: composer install --no-interaction --no-progress --no-suggest --prefer-dist
|
||||||
|
|
||||||
|
- name: Analyze
|
||||||
|
run: vendor/bin/phpstan analyse
|
||||||
|
|
||||||
|
lint:
|
||||||
|
name: Lint
|
||||||
|
runs-on: ubuntu-20.04
|
||||||
|
steps:
|
||||||
|
- name: Code checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Setup PHP
|
||||||
|
uses: shivammathur/setup-php@v2
|
||||||
|
with:
|
||||||
|
php-version: 8.1
|
||||||
|
extensions: bcmath, curl, gd, mbstring, mysql, openssl, pdo, tokenizer, xml, zip
|
||||||
|
tools: composer:v2
|
||||||
|
coverage: none
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: composer install --no-interaction --no-progress --no-suggest --prefer-dist
|
||||||
|
|
||||||
|
- name: PHP CS Fixer
|
||||||
|
run: vendor/bin/php-cs-fixer fix --dry-run --diff
|
||||||
|
|
||||||
|
mysql:
|
||||||
|
name: Tests
|
||||||
|
runs-on: ubuntu-20.04
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
php: [8.1]
|
||||||
|
database: ["mariadb:10.2", "mariadb:10.9", "mysql:8"]
|
||||||
|
services:
|
||||||
|
database:
|
||||||
|
image: docker.io/library/${{ matrix.database }}
|
||||||
|
env:
|
||||||
|
MYSQL_ALLOW_EMPTY_PASSWORD: yes
|
||||||
|
MYSQL_DATABASE: testing
|
||||||
|
ports:
|
||||||
|
- 3306/tcp
|
||||||
|
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
|
||||||
|
env:
|
||||||
|
APP_ENV: testing
|
||||||
|
APP_DEBUG: "true"
|
||||||
|
APP_KEY: SomeRandomString3232RandomString
|
||||||
|
APP_THEME: pterodactyl
|
||||||
|
APP_TIMEZONE: UTC
|
||||||
|
APP_URL: http://localhost/
|
||||||
|
APP_ENVIRONMENT_ONLY: "true"
|
||||||
|
CACHE_DRIVER: array
|
||||||
|
MAIL_MAILER: array
|
||||||
|
SESSION_DRIVER: array
|
||||||
|
QUEUE_CONNECTION: sync
|
||||||
|
HASHIDS_SALT: test123
|
||||||
|
DB_CONNECTION: mysql
|
||||||
|
DB_HOST: 127.0.0.1
|
||||||
|
DB_DATABASE: testing
|
||||||
|
DB_USERNAME: root
|
||||||
|
steps:
|
||||||
|
- name: Code checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Get cache directory
|
||||||
|
id: composer-cache
|
||||||
|
run: |
|
||||||
|
echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Cache
|
||||||
|
uses: actions/cache@v3
|
||||||
|
with:
|
||||||
|
path: ${{ steps.composer-cache.outputs.dir }}
|
||||||
|
key: ${{ runner.os }}-composer-${{ matrix.php }}-${{ hashFiles('**/composer.lock') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-composer-${{ matrix.php }}-
|
||||||
|
|
||||||
|
- name: Setup PHP
|
||||||
|
uses: shivammathur/setup-php@v2
|
||||||
|
with:
|
||||||
|
php-version: ${{ matrix.php }}
|
||||||
|
extensions: bcmath, cli, curl, gd, mbstring, mysql, openssl, pdo, tokenizer, xml, zip
|
||||||
|
tools: composer:v2
|
||||||
|
coverage: none
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: composer install --no-interaction --no-progress --no-suggest --prefer-dist
|
||||||
|
|
||||||
|
- name: Unit tests
|
||||||
|
run: vendor/bin/phpunit --bootstrap vendor/autoload.php tests/Unit
|
||||||
|
env:
|
||||||
|
DB_HOST: UNIT_NO_DB
|
||||||
|
|
||||||
|
- name: Integration tests
|
||||||
|
run: vendor/bin/phpunit tests/Integration
|
||||||
|
env:
|
||||||
|
DB_PORT: ${{ job.services.database.ports[3306] }}
|
||||||
|
|
||||||
|
postgres:
|
||||||
|
name: Tests
|
||||||
|
runs-on: ubuntu-20.04
|
||||||
|
if: "!contains(github.event.head_commit.message, 'skip ci') && !contains(github.event.head_commit.message, 'ci skip')"
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
php: [8.1]
|
||||||
|
database: ["postgres:13", "postgres:14", "postgres:15"]
|
||||||
|
services:
|
||||||
|
database:
|
||||||
|
image: docker.io/library/${{ matrix.database }}
|
||||||
|
env:
|
||||||
|
POSTGRES_USER: postgres
|
||||||
|
POSTGRES_PASSWORD: postgres
|
||||||
|
POSTGRES_DB: testing
|
||||||
|
ports:
|
||||||
|
- 5432/tcp
|
||||||
|
options: --health-cmd=pg_isready --health-interval=10s --health-timeout=5s --health-retries=3
|
||||||
|
env:
|
||||||
|
APP_ENV: testing
|
||||||
|
APP_DEBUG: "true"
|
||||||
|
APP_KEY: SomeRandomString3232RandomString
|
||||||
|
APP_THEME: pterodactyl
|
||||||
|
APP_TIMEZONE: UTC
|
||||||
|
APP_URL: http://localhost/
|
||||||
|
APP_ENVIRONMENT_ONLY: "true"
|
||||||
|
CACHE_DRIVER: array
|
||||||
|
MAIL_MAILER: array
|
||||||
|
SESSION_DRIVER: array
|
||||||
|
QUEUE_CONNECTION: sync
|
||||||
|
HASHIDS_SALT: test123
|
||||||
|
DB_CONNECTION: pgsql
|
||||||
|
DB_HOST: 127.0.0.1
|
||||||
|
DB_DATABASE: testing
|
||||||
|
DB_USERNAME: postgres
|
||||||
|
DB_PASSWORD: postgres
|
||||||
|
steps:
|
||||||
|
- name: Code checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Get cache directory
|
||||||
|
id: composer-cache
|
||||||
|
run: |
|
||||||
|
echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Cache
|
||||||
|
uses: actions/cache@v3
|
||||||
|
with:
|
||||||
|
path: ${{ steps.composer-cache.outputs.dir }}
|
||||||
|
key: ${{ runner.os }}-composer-${{ matrix.php }}-${{ hashFiles('**/composer.lock') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-composer-${{ matrix.php }}-
|
||||||
|
|
||||||
|
- name: Setup PHP
|
||||||
|
uses: shivammathur/setup-php@v2
|
||||||
|
with:
|
||||||
|
php-version: ${{ matrix.php }}
|
||||||
|
extensions: bcmath, cli, curl, gd, mbstring, mysql, openssl, pdo, tokenizer, xml, zip
|
||||||
|
tools: composer:v2
|
||||||
|
coverage: none
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: composer install --no-interaction --no-progress --no-suggest --prefer-dist
|
||||||
|
|
||||||
|
- name: Unit tests
|
||||||
|
run: vendor/bin/phpunit --bootstrap vendor/autoload.php tests/Unit
|
||||||
|
env:
|
||||||
|
DB_HOST: UNIT_NO_DB
|
||||||
|
|
||||||
|
- name: Integration tests
|
||||||
|
run: vendor/bin/phpunit tests/Integration
|
||||||
|
env:
|
||||||
|
DB_PORT: ${{ job.services.database.ports[5432] }}
|
36
.github/workflows/lint.yaml
vendored
36
.github/workflows/lint.yaml
vendored
|
@ -1,36 +0,0 @@
|
||||||
name: Lint
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- "develop"
|
|
||||||
- "1.0-develop"
|
|
||||||
pull_request:
|
|
||||||
branches:
|
|
||||||
- "develop"
|
|
||||||
- "1.0-develop"
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
lint:
|
|
||||||
name: Lint
|
|
||||||
runs-on: ubuntu-20.04
|
|
||||||
steps:
|
|
||||||
- name: Code Checkout
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: Setup PHP
|
|
||||||
uses: shivammathur/setup-php@v2
|
|
||||||
with:
|
|
||||||
php-version: "8.1"
|
|
||||||
extensions: bcmath, curl, gd, mbstring, mysql, openssl, pdo, tokenizer, xml, zip
|
|
||||||
tools: composer:v2
|
|
||||||
coverage: none
|
|
||||||
|
|
||||||
- name: Setup .env
|
|
||||||
run: cp .env.ci .env
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: composer install --no-interaction --no-progress --no-suggest --prefer-dist
|
|
||||||
|
|
||||||
- name: PHP CS Fixer
|
|
||||||
run: vendor/bin/php-cs-fixer fix --dry-run --diff
|
|
26
.github/workflows/release.yaml
vendored
26
.github/workflows/release.yaml
vendored
|
@ -10,20 +10,20 @@ jobs:
|
||||||
name: Release
|
name: Release
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-20.04
|
||||||
steps:
|
steps:
|
||||||
- name: Code Checkout
|
- name: Code checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: 16
|
node-version: 18
|
||||||
cache: "yarn"
|
cache: yarn
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: yarn install --frozen-lockfile
|
run: yarn install --frozen-lockfile
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: yarn build:production
|
run: yarn build
|
||||||
|
|
||||||
- name: Create release branch and bump version
|
- name: Create release branch and bump version
|
||||||
env:
|
env:
|
||||||
|
@ -36,21 +36,19 @@ jobs:
|
||||||
git push -u origin $BRANCH
|
git push -u origin $BRANCH
|
||||||
sed -i "s/ 'version' => 'canary',/ 'version' => '${REF:11}',/" config/app.php
|
sed -i "s/ 'version' => 'canary',/ 'version' => '${REF:11}',/" config/app.php
|
||||||
git add config/app.php
|
git add config/app.php
|
||||||
git commit -m "bump version for release"
|
git commit -m "ci(release): bump version"
|
||||||
git push
|
git push
|
||||||
|
|
||||||
- name: Create release archive
|
- name: Create release archive
|
||||||
run: |
|
run: |
|
||||||
rm -rf node_modules/ test/ codecov.yml CODE_OF_CONDUCT.md CONTRIBUTING.md phpunit.xml Vagrantfile
|
rm -rf node_modules tests CODE_OF_CONDUCT.md CONTRIBUTING.md flake.lock flake.nix phpstan.neon phpunit.xml shell.nix
|
||||||
tar -czf panel.tar.gz * .env.example .eslintignore .eslintrc.js
|
tar -czf panel.tar.gz * .editorconfig .env.example .eslintignore .eslintrc.js .gitignore .prettierrc.json
|
||||||
|
|
||||||
- name: Extract changelog
|
- name: Extract changelog
|
||||||
id: extract_changelog
|
|
||||||
env:
|
env:
|
||||||
REF: ${{ github.ref }}
|
REF: ${{ github.ref }}
|
||||||
run: |
|
run: |
|
||||||
sed -n "/^## ${REF:10}/,/^## /{/^## /b;p}" CHANGELOG.md > ./RELEASE_CHANGELOG
|
sed -n "/^## ${REF:10}/,/^## /{/^## /b;p}" CHANGELOG.md > ./RELEASE_CHANGELOG
|
||||||
echo ::set-output name=version_name::`sed -nr "s/^## (${REF:10} .*)$/\1/p" CHANGELOG.md`
|
|
||||||
|
|
||||||
- name: Create checksum and add to changelog
|
- name: Create checksum and add to changelog
|
||||||
run: |
|
run: |
|
||||||
|
@ -58,17 +56,15 @@ jobs:
|
||||||
echo -e "\n#### SHA256 Checksum\n\n\`\`\`\n$SUM\n\`\`\`\n" >> ./RELEASE_CHANGELOG
|
echo -e "\n#### SHA256 Checksum\n\n\`\`\`\n$SUM\n\`\`\`\n" >> ./RELEASE_CHANGELOG
|
||||||
echo $SUM > checksum.txt
|
echo $SUM > checksum.txt
|
||||||
|
|
||||||
- name: Create Release
|
- name: Create release
|
||||||
id: create_release
|
id: create_release
|
||||||
uses: actions/create-release@v1
|
uses: softprops/action-gh-release@v1
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
with:
|
with:
|
||||||
tag_name: ${{ github.ref }}
|
|
||||||
release_name: ${{ steps.extract_changelog.outputs.version_name }}
|
|
||||||
body_path: ./RELEASE_CHANGELOG
|
|
||||||
draft: true
|
draft: true
|
||||||
prerelease: ${{ contains(github.ref, 'beta') || contains(github.ref, 'alpha') }}
|
prerelease: ${{ contains(github.ref, 'rc') || contains(github.ref, 'beta') || contains(github.ref, 'alpha') }}
|
||||||
|
body_path: ./RELEASE_CHANGELOG
|
||||||
|
|
||||||
- name: Upload release archive
|
- name: Upload release archive
|
||||||
id: upload-release-archive
|
id: upload-release-archive
|
||||||
|
|
57
.github/workflows/ui.yaml
vendored
Normal file
57
.github/workflows/ui.yaml
vendored
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
name: UI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- "develop"
|
||||||
|
- "1.0-develop"
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- "develop"
|
||||||
|
- "1.0-develop"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
lint:
|
||||||
|
name: Lint
|
||||||
|
runs-on: ubuntu-20.04
|
||||||
|
steps:
|
||||||
|
- name: Code checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Setup Node
|
||||||
|
uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: 18
|
||||||
|
cache: yarn
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: yarn install --frozen-lockfile
|
||||||
|
|
||||||
|
- name: Lint
|
||||||
|
run: yarn run lint
|
||||||
|
|
||||||
|
tests:
|
||||||
|
name: Tests
|
||||||
|
runs-on: ubuntu-20.04
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
node: [16, 18]
|
||||||
|
steps:
|
||||||
|
- name: Code checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Setup Node
|
||||||
|
uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: ${{ matrix.node }}
|
||||||
|
cache: yarn
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: yarn install --frozen-lockfile
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: yarn run build
|
||||||
|
|
||||||
|
- name: Tests
|
||||||
|
run: yarn run test
|
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -29,3 +29,8 @@ misc
|
||||||
coverage.xml
|
coverage.xml
|
||||||
resources/lang/locales.js
|
resources/lang/locales.js
|
||||||
.phpunit.result.cache
|
.phpunit.result.cache
|
||||||
|
|
||||||
|
/public/build
|
||||||
|
/public/hot
|
||||||
|
result
|
||||||
|
docker-compose.yaml
|
||||||
|
|
4
.prettierignore
Normal file
4
.prettierignore
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
.github
|
||||||
|
public
|
||||||
|
node_modules
|
||||||
|
resources/views
|
22
.prettierrc.json
Normal file
22
.prettierrc.json
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
"printWidth": 120,
|
||||||
|
"tabWidth": 4,
|
||||||
|
"useTabs": false,
|
||||||
|
"semi": true,
|
||||||
|
"singleQuote": true,
|
||||||
|
"jsxSingleQuote": false,
|
||||||
|
"trailingComma": "all",
|
||||||
|
"bracketSpacing": true,
|
||||||
|
"arrowParens": "avoid",
|
||||||
|
"endOfLine": "lf",
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": ["**/*.md"],
|
||||||
|
"options": {
|
||||||
|
"tabWidth": 2,
|
||||||
|
"useTabs": false,
|
||||||
|
"singleQuote": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
61
CHANGELOG.md
61
CHANGELOG.md
|
@ -3,10 +3,67 @@ This file is a running track of new features and fixes to each version of the pa
|
||||||
|
|
||||||
This project follows [Semantic Versioning](http://semver.org) guidelines.
|
This project follows [Semantic Versioning](http://semver.org) guidelines.
|
||||||
|
|
||||||
## [Unreleased]
|
## v1.11.2
|
||||||
### Changed
|
### Changed
|
||||||
* Changed minimum PHP version is now 8.0 instead of `7.4`.
|
* Telemetry no longer sends a map of Egg and Nest UUIDs to the number of servers using them.
|
||||||
|
* Increased the timeout for the decompress files endpoint in the client API from 15 seconds to 15 minutes.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
* Fixed Panel Docker image having a `v` prefix in the version displayed in the admin area.
|
||||||
|
* Fixed emails using the wrong queue name, causing them to not be sent.
|
||||||
|
* Fixed the settings keys used for configuring SMTP settings, causing settings to not save properly.
|
||||||
|
* Fixed the `MAIL_EHLO_DOMAIN` environment variable not being properly backwards compatible with the old `SERVER_NAME` variable.
|
||||||
|
|
||||||
|
## v1.11.1
|
||||||
|
### Fixed
|
||||||
|
* Fixed Panel Docker image showing `canary` as it's version.
|
||||||
|
|
||||||
|
## v1.11.0
|
||||||
|
### Changed (since 1.10.4)
|
||||||
|
* Changed minimum PHP version requirement from `7.4` to `8.0`.
|
||||||
* Upgraded from Laravel 8 to Laravel 9.
|
* Upgraded from Laravel 8 to Laravel 9.
|
||||||
|
* This release requires Wings v1.11.x in order for Server Transfers to work.
|
||||||
|
* `MB` byte suffixes are now displayed as `MiB` to more accurately reflect the actual value.
|
||||||
|
* Server re-installation failures are tracked independently of the initial installation process.
|
||||||
|
|
||||||
|
### Fixed (since 1.10.4)
|
||||||
|
* Node maintenance mode now properly blocks access to servers.
|
||||||
|
* Fixed the length validation on the Minecraft Forge egg.
|
||||||
|
* Fixed the password in the JDBC string not being properly URL encoded.
|
||||||
|
* Fixed an issue where Wings would throw a validation error while attempting to upload activity logs.
|
||||||
|
* Properly handle a missing `Content-Length` header in the response from the daemon.
|
||||||
|
* Ensure activity log properties are always returned as an object instead of an empty array.
|
||||||
|
|
||||||
|
### Added (since 1.10.4)
|
||||||
|
* Added the `server:settings.description` activity log event for when a server description is changed.
|
||||||
|
* Added the ability to cancel file uploads in the file manager for a server.
|
||||||
|
* Added a telemetry service to collect anonymous metrics from the panel, this feature is *enabled* by default and can be toggled using the `PTERODACTYL_TELEMETRY_ENABLED` environment variable.
|
||||||
|
|
||||||
|
## v1.11.0-rc.2
|
||||||
|
### Changed
|
||||||
|
* `MB` byte suffixes are now displayed as `MiB` to more accurately reflect the actual value.
|
||||||
|
* Server re-installation failures are tracked independently of the initial installation process.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
* Properly handle a missing `Content-Length` header in the response from the daemon.
|
||||||
|
* Ensure activity log properties are always returned as an object instead of an empty array.
|
||||||
|
|
||||||
|
### Added
|
||||||
|
* Added the `server:settings.description` activity log event for when a server description is changed.
|
||||||
|
* Added the ability to cancel file uploads in the file manager for a server.
|
||||||
|
* Added a telemetry service to collect anonymous metrics from the panel, this feature is disabled by default and can be toggled using the `PTERODACTYL_TELEMETRY_ENABLED` environment variable.
|
||||||
|
|
||||||
|
## v1.11.0-rc.1
|
||||||
|
### Changed
|
||||||
|
* Changed minimum PHP version requirement from `7.4` to `8.0`.
|
||||||
|
* Upgraded from Laravel 8 to Laravel 9.
|
||||||
|
* This release requires Wings v1.11.x in order for Server Transfers to work.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
* Node maintenance mode now properly blocks access to servers.
|
||||||
|
* Fixed the length validation on the Minecraft Forge egg.
|
||||||
|
* Fixed the password in the JDBC string not being properly URL encoded.
|
||||||
|
* Fixed an issue where Wings would throw a validation error while attempting to upload activity logs.
|
||||||
|
|
||||||
## v1.10.4
|
## v1.10.4
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
76
Containerfile
Normal file
76
Containerfile
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
# Stage 0 - Caddy
|
||||||
|
FROM --platform=$TARGETOS/$TARGETARCH docker.io/library/caddy:latest AS caddy
|
||||||
|
|
||||||
|
# Stage 1 - Builder
|
||||||
|
FROM --platform=$TARGETOS/$TARGETARCH registry.access.redhat.com/ubi9/nodejs-16-minimal AS builder
|
||||||
|
|
||||||
|
RUN npm install -g yarn
|
||||||
|
|
||||||
|
WORKDIR /var/www/pterodactyl
|
||||||
|
|
||||||
|
COPY --chown=1001:0 public ./public
|
||||||
|
COPY --chown=1001:0 resources/scripts ./resources/scripts
|
||||||
|
COPY --chown=1001:0 .eslintignore .eslintrc.js .prettierrc.json package.json tailwind.config.js tsconfig.json vite.config.ts yarn.lock .
|
||||||
|
|
||||||
|
RUN /opt/app-root/src/.npm-global/bin/yarn install --frozen-lockfile \
|
||||||
|
&& /opt/app-root/src/.npm-global/bin/yarn build \
|
||||||
|
&& rm -rf resources/scripts .eslintignore .eslintrc.yml .yarnrc.yml package.json tailwind.config.js tsconfig.json vite.config.ts yarn.lock node_modules
|
||||||
|
|
||||||
|
COPY --chown=1001:0 app ./app
|
||||||
|
COPY --chown=1001:0 bootstrap ./bootstrap
|
||||||
|
COPY --chown=1001:0 config ./config
|
||||||
|
COPY --chown=1001:0 database ./database
|
||||||
|
COPY --chown=1001:0 resources/lang ./resources/lang
|
||||||
|
COPY --chown=1001:0 resources/views ./resources/views
|
||||||
|
COPY --chown=1001:0 routes ./routes
|
||||||
|
COPY --chown=1001:0 .env.example ./.env
|
||||||
|
COPY --chown=1001:0 artisan CHANGELOG.md composer.json composer.lock LICENSE.md README.md SECURITY.md .
|
||||||
|
|
||||||
|
# Stage 2 - Final
|
||||||
|
FROM --platform=$TARGETOS/$TARGETARCH registry.access.redhat.com/ubi9/ubi-minimal
|
||||||
|
|
||||||
|
RUN microdnf update -y \
|
||||||
|
&& rpm --install https://dl.fedoraproject.org/pub/epel/epel-release-latest-9.noarch.rpm \
|
||||||
|
&& rpm --install https://rpms.remirepo.net/enterprise/remi-release-9.rpm \
|
||||||
|
&& microdnf update -y \
|
||||||
|
&& microdnf install -y ca-certificates shadow-utils tar tzdata unzip wget \
|
||||||
|
&& microdnf module -y reset php \
|
||||||
|
&& microdnf module -y enable php:remi-8.1 \
|
||||||
|
&& microdnf install -y cronie php-{bcmath,cli,common,fpm,gd,gmp,intl,json,mbstring,mysqlnd,opcache,pdo,pecl-redis5,pecl-zip,phpiredis,pgsql,process,sodium,xml,zstd} supervisor \
|
||||||
|
&& curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer \
|
||||||
|
&& rm /etc/php-fpm.d/www.conf \
|
||||||
|
&& useradd --home-dir /var/lib/caddy --create-home caddy \
|
||||||
|
&& mkdir /etc/caddy \
|
||||||
|
&& wget -O /usr/local/bin/yacron https://github.com/gjcarneiro/yacron/releases/download/0.17.0/yacron-0.17.0-x86_64-unknown-linux-gnu \
|
||||||
|
&& chmod 755 /usr/local/bin/yacron \
|
||||||
|
&& microdnf remove -y tar wget \
|
||||||
|
&& microdnf clean all
|
||||||
|
|
||||||
|
COPY --chown=caddy:caddy --from=builder /var/www/pterodactyl /var/www/pterodactyl
|
||||||
|
|
||||||
|
WORKDIR /var/www/pterodactyl
|
||||||
|
|
||||||
|
RUN mkdir -p /tmp/pterodactyl/cache /tmp/pterodactyl/framework/{cache,sessions,views} storage/framework \
|
||||||
|
&& rm -rf bootstrap/cache storage/framework/sessions storage/framework/views storage/framework/cache \
|
||||||
|
&& ln -s /tmp/pterodactyl/cache /var/www/pterodactyl/bootstrap/cache \
|
||||||
|
&& ln -s /tmp/pterodactyl/framework/cache /var/www/pterodactyl/storage/framework/cache \
|
||||||
|
&& ln -s /tmp/pterodactyl/framework/sessions /var/www/pterodactyl/storage/framework/sessions \
|
||||||
|
&& ln -s /tmp/pterodactyl/framework/views /var/www/pterodactyl/storage/framework/views \
|
||||||
|
&& chmod -R 755 /var/www/pterodactyl/storage/* /tmp/pterodactyl/cache \
|
||||||
|
&& chown -R caddy:caddy /var/www/pterodactyl /tmp/pterodactyl/{cache,framework}
|
||||||
|
|
||||||
|
USER caddy
|
||||||
|
ENV USER=caddy
|
||||||
|
|
||||||
|
RUN composer install --no-dev --optimize-autoloader \
|
||||||
|
&& rm -rf bootstrap/cache/*.php \
|
||||||
|
&& rm -rf .env storage/logs/*.log
|
||||||
|
|
||||||
|
COPY --from=caddy /usr/bin/caddy /usr/local/bin/caddy
|
||||||
|
COPY .github/docker/Caddyfile /etc/caddy/Caddyfile
|
||||||
|
COPY .github/docker/php-fpm.conf /etc/php-fpm.conf
|
||||||
|
COPY .github/docker/supervisord.conf /etc/supervisord.conf
|
||||||
|
COPY .github/docker/yacron.yaml /etc/yacron.yaml
|
||||||
|
|
||||||
|
EXPOSE 8080
|
||||||
|
CMD ["/usr/bin/supervisord", "--configuration=/etc/supervisord.conf"]
|
40
Dockerfile
40
Dockerfile
|
@ -1,40 +0,0 @@
|
||||||
# Stage 0:
|
|
||||||
# Build the assets that are needed for the frontend. This build stage is then discarded
|
|
||||||
# since we won't need NodeJS anymore in the future. This Docker image ships a final production
|
|
||||||
# level distribution of Pterodactyl.
|
|
||||||
FROM --platform=$TARGETOS/$TARGETARCH mhart/alpine-node:14
|
|
||||||
WORKDIR /app
|
|
||||||
COPY . ./
|
|
||||||
RUN yarn install --frozen-lockfile \
|
|
||||||
&& yarn run build:production
|
|
||||||
|
|
||||||
# Stage 1:
|
|
||||||
# Build the actual container with all of the needed PHP dependencies that will run the application.
|
|
||||||
FROM --platform=$TARGETOS/$TARGETARCH php:8.1-fpm-alpine
|
|
||||||
WORKDIR /app
|
|
||||||
COPY . ./
|
|
||||||
COPY --from=0 /app/public/assets ./public/assets
|
|
||||||
RUN apk add --no-cache --update ca-certificates dcron curl git supervisor tar unzip nginx libpng-dev libxml2-dev libzip-dev certbot certbot-nginx \
|
|
||||||
&& docker-php-ext-configure zip \
|
|
||||||
&& docker-php-ext-install bcmath gd pdo_mysql zip \
|
|
||||||
&& curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer \
|
|
||||||
&& cp .env.example .env \
|
|
||||||
&& mkdir -p bootstrap/cache/ storage/logs storage/framework/sessions storage/framework/views storage/framework/cache \
|
|
||||||
&& chmod 777 -R bootstrap storage \
|
|
||||||
&& composer install --no-dev --optimize-autoloader \
|
|
||||||
&& rm -rf .env bootstrap/cache/*.php \
|
|
||||||
&& chown -R nginx:nginx .
|
|
||||||
|
|
||||||
RUN rm /usr/local/etc/php-fpm.conf \
|
|
||||||
&& echo "* * * * * /usr/local/bin/php /app/artisan schedule:run >> /dev/null 2>&1" >> /var/spool/cron/crontabs/root \
|
|
||||||
&& echo "0 23 * * * certbot renew --nginx --quiet" >> /var/spool/cron/crontabs/root \
|
|
||||||
&& sed -i s/ssl_session_cache/#ssl_session_cache/g /etc/nginx/nginx.conf \
|
|
||||||
&& mkdir -p /var/run/php /var/run/nginx
|
|
||||||
|
|
||||||
COPY .github/docker/default.conf /etc/nginx/http.d/default.conf
|
|
||||||
COPY .github/docker/www.conf /usr/local/etc/php-fpm.conf
|
|
||||||
COPY .github/docker/supervisord.conf /etc/supervisord.conf
|
|
||||||
|
|
||||||
EXPOSE 80 443
|
|
||||||
ENTRYPOINT [ "/bin/ash", ".github/docker/entrypoint.sh" ]
|
|
||||||
CMD [ "supervisord", "-n", "-c", "/etc/supervisord.conf" ]
|
|
25
README.md
25
README.md
|
@ -1,6 +1,6 @@
|
||||||
[![Logo Image](https://cdn.pterodactyl.io/logos/new/pterodactyl_logo.png)](https://pterodactyl.io)
|
[![Logo Image](https://cdn.pterodactyl.io/logos/new/pterodactyl_logo.png)](https://pterodactyl.io)
|
||||||
|
|
||||||
![GitHub Workflow Status](https://img.shields.io/github/workflow/status/pterodactyl/panel/tests?label=Tests&style=for-the-badge)
|
![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/pterodactyl/panel/laravel.yaml?label=Tests&style=for-the-badge&branch=develop)
|
||||||
![Discord](https://img.shields.io/discord/122900397965705216?label=Discord&logo=Discord&logoColor=white&style=for-the-badge)
|
![Discord](https://img.shields.io/discord/122900397965705216?label=Discord&logo=Discord&logoColor=white&style=for-the-badge)
|
||||||
![GitHub Releases](https://img.shields.io/github/downloads/pterodactyl/panel/latest/total?style=for-the-badge)
|
![GitHub Releases](https://img.shields.io/github/downloads/pterodactyl/panel/latest/total?style=for-the-badge)
|
||||||
![GitHub contributors](https://img.shields.io/github/contributors/pterodactyl/panel?style=for-the-badge)
|
![GitHub contributors](https://img.shields.io/github/contributors/pterodactyl/panel?style=for-the-badge)
|
||||||
|
@ -27,17 +27,18 @@ Stop settling for less. Make game servers a first class citizen on your platform
|
||||||
I would like to extend my sincere thanks to the following sponsors for helping fund Pterodactyl's developement.
|
I would like to extend my sincere thanks to the following sponsors for helping fund Pterodactyl's developement.
|
||||||
[Interested in becoming a sponsor?](https://github.com/sponsors/matthewpi)
|
[Interested in becoming a sponsor?](https://github.com/sponsors/matthewpi)
|
||||||
|
|
||||||
| Company | About |
|
| Company | About |
|
||||||
|-----------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|-----------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
| [**WISP**](https://wisp.gg) | Extra features. |
|
| [**WISP**](https://wisp.gg) | Extra features. |
|
||||||
| [**Fragnet**](https://fragnet.net) | Providing low latency, high-end game hosting solutions to gamers, game studios and eSports platforms. |
|
| [**RocketNode**](https://rocketnode.com/) | Innovative game server hosting combined with a straightforward control panel, affordable prices, and Rocket-Fast support. |
|
||||||
| [**RocketNode**](https://rocketnode.com/) | Innovative game server hosting combined with a straightforward control panel, affordable prices, and Rocket-Fast support. |
|
| [**Aussie Server Hosts**](https://aussieserverhosts.com/) | No frills Australian Owned and operated High Performance Server hosting for some of the most demanding games serving Australia and New Zealand. |
|
||||||
| [**Aussie Server Hosts**](https://aussieserverhosts.com/) | No frills Australian Owned and operated High Performance Server hosting for some of the most demanding games serving Australia and New Zealand. |
|
| [**BisectHosting**](https://www.bisecthosting.com/) | BisectHosting provides Minecraft, Valheim and other server hosting services with the highest reliability and lightning fast support since 2012. |
|
||||||
| [**BisectHosting**](https://www.bisecthosting.com/) | BisectHosting provides Minecraft, Valheim and other server hosting services with the highest reliability and lightning fast support since 2012. |
|
| [**MineStrator**](https://minestrator.com/) | Looking for the most highend French hosting company for your minecraft server? More than 24,000 members on our discord trust us. Give us a try! |
|
||||||
| [**MineStrator**](https://minestrator.com/) | Looking for the most highend French hosting company for your minecraft server? More than 24,000 members on our discord trust us. Give us a try! |
|
| [**Skynode**](https://www.skynode.pro/) | Skynode provides blazing fast game servers along with a top-notch user experience. Whatever our clients are looking for, we're able to provide it! |
|
||||||
| [**Skynode**](https://www.skynode.pro/) | Skynode provides blazing fast game servers along with a top-notch user experience. Whatever our clients are looking for, we're able to provide it! |
|
| [**VibeGAMES**](https://vibegames.net/) | VibeGAMES is a game server provider that specializes in DDOS protection for the games we offer. We have multiple locations in the US, Brazil, France, Germany, Singapore, Australia and South Africa. |
|
||||||
| [**VibeGAMES**](https://vibegames.net/) | VibeGAMES is a game server provider that specializes in DDOS protection for the games we offer. We have multiple locations in the US, Brazil, France, Germany, Singapore, Australia and South Africa. |
|
| [**Pterodactyl Market**](https://pterodactylmarket.com/) | Pterodactyl Market is a one-and-stop shop for Pterodactyl. In our market, you can find Add-ons, Themes, Eggs, and more for Pterodactyl. |
|
||||||
| [**Pterodactyl Market**](https://pterodactylmarket.com/) | Pterodactyl Market is a one-and-stop shop for Pterodactyl. In our market, you can find Add-ons, Themes, Eggs, and more for Pterodactyl. |
|
| [**UltraServers**](https://ultraservers.com/) | Deploy premium games hosting with the click of a button. Manage and swap games with ease and let us take care of the rest. We currently support Minecraft, Rust, ARK, 7 Days to Die, Garys MOD, CS:GO, Satisfactory and others. |
|
||||||
|
| [**Realms Hosting**](https://realmshosting.com/) | Want to build your Gaming Empire? Use Realms Hosting today to kick start your game server hosting with outstanding DDOS Protection, 24/7 Support, Cheap Prices and a Custom Control Panel. | |
|
||||||
|
|
||||||
### Supported Games
|
### Supported Games
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
# Security Policy
|
# Security Policy
|
||||||
|
|
||||||
## Supported Versions
|
## Supported Versions
|
||||||
|
|
||||||
The following versions of Pterodactyl are receiving active support and maintenance. Any security vulnerabilities discovered must be reproducible in supported versions.
|
The following versions of Pterodactyl are receiving active support and maintenance. Any security vulnerabilities discovered must be reproducible in supported versions.
|
||||||
|
|
||||||
| Panel | Daemon | Supported |
|
| Panel | Daemon | Supported |
|
||||||
|--------|--------------|--------------------|
|
|--------|--------------|--------------------|
|
||||||
| 1.10.x | wings@1.7.x | :white_check_mark: |
|
| 1.10.x | wings@1.7.x | :white_check_mark: |
|
||||||
|
| 1.11.x | wings@1.11.x | :white_check_mark: |
|
||||||
| 0.7.x | daemon@0.6.x | :x: |
|
| 0.7.x | daemon@0.6.x | :x: |
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
namespace Pterodactyl\Console\Commands\Environment;
|
namespace Pterodactyl\Console\Commands\Environment;
|
||||||
|
|
||||||
use DateTimeZone;
|
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
use Illuminate\Contracts\Console\Kernel;
|
use Illuminate\Contracts\Console\Kernel;
|
||||||
use Pterodactyl\Traits\Commands\EnvironmentWriterTrait;
|
use Pterodactyl\Traits\Commands\EnvironmentWriterTrait;
|
||||||
|
@ -44,7 +43,8 @@ class AppSettingsCommand extends Command
|
||||||
{--redis-host= : Redis host to use for connections.}
|
{--redis-host= : Redis host to use for connections.}
|
||||||
{--redis-pass= : Password used to connect to redis.}
|
{--redis-pass= : Password used to connect to redis.}
|
||||||
{--redis-port= : Port to connect to redis over.}
|
{--redis-port= : Port to connect to redis over.}
|
||||||
{--settings-ui= : Enable or disable the settings UI.}';
|
{--settings-ui= : Enable or disable the settings UI.}
|
||||||
|
{--telemetry= : Enable or disable anonymous telemetry.}';
|
||||||
|
|
||||||
protected array $variables = [];
|
protected array $variables = [];
|
||||||
|
|
||||||
|
@ -88,7 +88,7 @@ class AppSettingsCommand extends Command
|
||||||
$this->output->comment('The timezone should match one of PHP\'s supported timezones. If you are unsure, please reference https://php.net/manual/en/timezones.php.');
|
$this->output->comment('The timezone should match one of PHP\'s supported timezones. If you are unsure, please reference https://php.net/manual/en/timezones.php.');
|
||||||
$this->variables['APP_TIMEZONE'] = $this->option('timezone') ?? $this->anticipate(
|
$this->variables['APP_TIMEZONE'] = $this->option('timezone') ?? $this->anticipate(
|
||||||
'Application Timezone',
|
'Application Timezone',
|
||||||
DateTimeZone::listIdentifiers(),
|
\DateTimeZone::listIdentifiers(),
|
||||||
config('app.timezone')
|
config('app.timezone')
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -119,6 +119,12 @@ class AppSettingsCommand extends Command
|
||||||
$this->variables['APP_ENVIRONMENT_ONLY'] = $this->confirm('Enable UI based settings editor?', true) ? 'false' : 'true';
|
$this->variables['APP_ENVIRONMENT_ONLY'] = $this->confirm('Enable UI based settings editor?', true) ? 'false' : 'true';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->output->comment('Please reference https://pterodactyl.io/panel/1.0/additional_configuration.html#telemetry for more detailed information regarding telemetry data and collection.');
|
||||||
|
$this->variables['PTERODACTYL_TELEMETRY_ENABLED'] = $this->option('telemetry') ?? $this->confirm(
|
||||||
|
'Enable sending anonymous telemetry data?',
|
||||||
|
config('pterodactyl.telemetry.enabled', true)
|
||||||
|
) ? 'true' : 'false';
|
||||||
|
|
||||||
// Make sure session cookies are set as "secure" when using HTTPS
|
// Make sure session cookies are set as "secure" when using HTTPS
|
||||||
if (str_starts_with($this->variables['APP_URL'], 'https://')) {
|
if (str_starts_with($this->variables['APP_URL'], 'https://')) {
|
||||||
$this->variables['SESSION_SECURE_COOKIE'] = 'true';
|
$this->variables['SESSION_SECURE_COOKIE'] = 'true';
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
namespace Pterodactyl\Console\Commands\Environment;
|
namespace Pterodactyl\Console\Commands\Environment;
|
||||||
|
|
||||||
use PDOException;
|
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
use Illuminate\Contracts\Console\Kernel;
|
use Illuminate\Contracts\Console\Kernel;
|
||||||
use Illuminate\Database\DatabaseManager;
|
use Illuminate\Database\DatabaseManager;
|
||||||
|
@ -72,7 +71,7 @@ class DatabaseSettingsCommand extends Command
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$this->testMySQLConnection();
|
$this->testMySQLConnection();
|
||||||
} catch (PDOException $exception) {
|
} catch (\PDOException $exception) {
|
||||||
$this->output->error(sprintf('Unable to connect to the MySQL server using the provided credentials. The error returned was "%s".', $exception->getMessage()));
|
$this->output->error(sprintf('Unable to connect to the MySQL server using the provided credentials. The error returned was "%s".', $exception->getMessage()));
|
||||||
$this->output->error('Your connection credentials have NOT been saved. You will need to provide valid connection information before proceeding.');
|
$this->output->error('Your connection credentials have NOT been saved. You will need to provide valid connection information before proceeding.');
|
||||||
|
|
||||||
|
|
|
@ -57,7 +57,7 @@ class EmailSettingsCommand extends Command
|
||||||
$this->{$method}();
|
$this->{$method}();
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->variables['MAIL_FROM'] = $this->option('email') ?? $this->ask(
|
$this->variables['MAIL_FROM_ADDRESS'] = $this->option('email') ?? $this->ask(
|
||||||
trans('command/messages.environment.mail.ask_mail_from'),
|
trans('command/messages.environment.mail.ask_mail_from'),
|
||||||
$this->config->get('mail.from.address')
|
$this->config->get('mail.from.address')
|
||||||
);
|
);
|
||||||
|
@ -67,12 +67,6 @@ class EmailSettingsCommand extends Command
|
||||||
$this->config->get('mail.from.name')
|
$this->config->get('mail.from.name')
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->variables['MAIL_ENCRYPTION'] = $this->option('encryption') ?? $this->choice(
|
|
||||||
trans('command/messages.environment.mail.ask_encryption'),
|
|
||||||
['tls' => 'TLS', 'ssl' => 'SSL', '' => 'None'],
|
|
||||||
$this->config->get('mail.encryption', 'tls')
|
|
||||||
);
|
|
||||||
|
|
||||||
$this->writeToEnvironment($this->variables);
|
$this->writeToEnvironment($this->variables);
|
||||||
|
|
||||||
$this->line('Updating stored environment configuration file.');
|
$this->line('Updating stored environment configuration file.');
|
||||||
|
@ -102,6 +96,12 @@ class EmailSettingsCommand extends Command
|
||||||
$this->variables['MAIL_PASSWORD'] = $this->option('password') ?? $this->secret(
|
$this->variables['MAIL_PASSWORD'] = $this->option('password') ?? $this->secret(
|
||||||
trans('command/messages.environment.mail.ask_smtp_password')
|
trans('command/messages.environment.mail.ask_smtp_password')
|
||||||
);
|
);
|
||||||
|
|
||||||
|
$this->variables['MAIL_ENCRYPTION'] = $this->option('encryption') ?? $this->choice(
|
||||||
|
trans('command/messages.environment.mail.ask_encryption'),
|
||||||
|
['tls' => 'TLS', 'ssl' => 'SSL', '' => 'None'],
|
||||||
|
$this->config->get('mail.mailers.smtp.encryption', 'tls')
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -15,7 +15,7 @@ class InfoCommand extends Command
|
||||||
/**
|
/**
|
||||||
* VersionCommand constructor.
|
* VersionCommand constructor.
|
||||||
*/
|
*/
|
||||||
public function __construct(private ConfigRepository $config, private SoftwareVersionService $versionService)
|
public function __construct(private ConfigRepository $config, private SoftwareVersionService $softwareVersionService)
|
||||||
{
|
{
|
||||||
parent::__construct();
|
parent::__construct();
|
||||||
}
|
}
|
||||||
|
@ -27,9 +27,9 @@ class InfoCommand extends Command
|
||||||
{
|
{
|
||||||
$this->output->title('Version Information');
|
$this->output->title('Version Information');
|
||||||
$this->table([], [
|
$this->table([], [
|
||||||
['Panel Version', $this->config->get('app.version')],
|
['Panel Version', $this->softwareVersionService->getCurrentVersion()],
|
||||||
['Latest Version', $this->versionService->getPanel()],
|
['Latest Version', $this->softwareVersionService->getLatestPanel()],
|
||||||
['Up-to-Date', $this->versionService->isLatestPanel() ? 'Yes' : $this->formatText('No', 'bg=red')],
|
['Up-to-Date', $this->softwareVersionService->isLatestPanel() ? 'Yes' : $this->formatText('No', 'bg=red')],
|
||||||
['Unique Identifier', $this->config->get('pterodactyl.service.author')],
|
['Unique Identifier', $this->config->get('pterodactyl.service.author')],
|
||||||
], 'compact');
|
], 'compact');
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
namespace Pterodactyl\Console\Commands\Maintenance;
|
namespace Pterodactyl\Console\Commands\Maintenance;
|
||||||
|
|
||||||
use SplFileInfo;
|
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
use Illuminate\Contracts\Filesystem\Filesystem;
|
use Illuminate\Contracts\Filesystem\Filesystem;
|
||||||
|
@ -35,7 +34,7 @@ class CleanServiceBackupFilesCommand extends Command
|
||||||
{
|
{
|
||||||
$files = $this->disk->files('services/.bak');
|
$files = $this->disk->files('services/.bak');
|
||||||
|
|
||||||
collect($files)->each(function (SplFileInfo $file) {
|
collect($files)->each(function (\SplFileInfo $file) {
|
||||||
$lastModified = Carbon::createFromTimestamp($this->disk->lastModified($file->getPath()));
|
$lastModified = Carbon::createFromTimestamp($this->disk->lastModified($file->getPath()));
|
||||||
if ($lastModified->diffInMinutes(Carbon::now()) > self::BACKUP_THRESHOLD_MINUTES) {
|
if ($lastModified->diffInMinutes(Carbon::now()) > self::BACKUP_THRESHOLD_MINUTES) {
|
||||||
$this->disk->delete($file->getPath());
|
$this->disk->delete($file->getPath());
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
namespace Pterodactyl\Console\Commands\Maintenance;
|
namespace Pterodactyl\Console\Commands\Maintenance;
|
||||||
|
|
||||||
use Carbon\CarbonImmutable;
|
use Carbon\CarbonImmutable;
|
||||||
use InvalidArgumentException;
|
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
use Pterodactyl\Repositories\Eloquent\BackupRepository;
|
use Pterodactyl\Repositories\Eloquent\BackupRepository;
|
||||||
|
|
||||||
|
@ -25,7 +24,7 @@ class PruneOrphanedBackupsCommand extends Command
|
||||||
{
|
{
|
||||||
$since = $this->option('prune-age') ?? config('backups.prune_age', 360);
|
$since = $this->option('prune-age') ?? config('backups.prune_age', 360);
|
||||||
if (!$since || !is_digit($since)) {
|
if (!$since || !is_digit($since)) {
|
||||||
throw new InvalidArgumentException('The "--prune-age" argument must be a value greater than 0.');
|
throw new \InvalidArgumentException('The "--prune-age" argument must be a value greater than 0.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$query = $this->backupRepository->getBuilder()
|
$query = $this->backupRepository->getBuilder()
|
||||||
|
|
|
@ -21,6 +21,6 @@ class UpCommand extends BaseUpCommand
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
return parent::handle() ?? 0;
|
return parent::handle();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
namespace Pterodactyl\Console\Commands\Schedule;
|
namespace Pterodactyl\Console\Commands\Schedule;
|
||||||
|
|
||||||
use Exception;
|
use Exception;
|
||||||
use Throwable;
|
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
use Pterodactyl\Models\Schedule;
|
use Pterodactyl\Models\Schedule;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
|
@ -68,7 +67,7 @@ class ProcessRunnableCommand extends Command
|
||||||
'schedule' => $schedule->name,
|
'schedule' => $schedule->name,
|
||||||
'hash' => $schedule->hashid,
|
'hash' => $schedule->hashid,
|
||||||
]));
|
]));
|
||||||
} catch (Throwable|Exception $exception) {
|
} catch (\Throwable|\Exception $exception) {
|
||||||
Log::error($exception, ['schedule_id' => $schedule->id]);
|
Log::error($exception, ['schedule_id' => $schedule->id]);
|
||||||
|
|
||||||
$this->error("An error was encountered while processing Schedule #$schedule->id: " . $exception->getMessage());
|
$this->error("An error was encountered while processing Schedule #$schedule->id: " . $exception->getMessage());
|
||||||
|
|
34
app/Console/Commands/TelemetryCommand.php
Normal file
34
app/Console/Commands/TelemetryCommand.php
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Pterodactyl\Console\Commands;
|
||||||
|
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
use Symfony\Component\VarDumper\VarDumper;
|
||||||
|
use Pterodactyl\Services\Telemetry\TelemetryCollectionService;
|
||||||
|
|
||||||
|
class TelemetryCommand extends Command
|
||||||
|
{
|
||||||
|
protected $description = 'Displays all the data that would be sent to the Pterodactyl Telemetry Service if telemetry collection is enabled.';
|
||||||
|
|
||||||
|
protected $signature = 'p:telemetry';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TelemetryCommand constructor.
|
||||||
|
*/
|
||||||
|
public function __construct(private TelemetryCollectionService $telemetryCollectionService)
|
||||||
|
{
|
||||||
|
parent::__construct();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle execution of command.
|
||||||
|
*
|
||||||
|
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$this->output->info('Collecting telemetry data, this may take a while...');
|
||||||
|
|
||||||
|
VarDumper::dump($this->telemetryCollectionService->collect());
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
namespace Pterodactyl\Console\Commands;
|
namespace Pterodactyl\Console\Commands;
|
||||||
|
|
||||||
use Closure;
|
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
use Pterodactyl\Console\Kernel;
|
use Pterodactyl\Console\Kernel;
|
||||||
use Symfony\Component\Process\Process;
|
use Symfony\Component\Process\Process;
|
||||||
|
@ -177,7 +176,7 @@ class UpgradeCommand extends Command
|
||||||
$this->info('Panel has been successfully upgraded. Please ensure you also update any Wings instances: https://pterodactyl.io/wings/1.0/upgrading.html');
|
$this->info('Panel has been successfully upgraded. Please ensure you also update any Wings instances: https://pterodactyl.io/wings/1.0/upgrading.html');
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function withProgress(ProgressBar $bar, Closure $callback)
|
protected function withProgress(ProgressBar $bar, \Closure $callback)
|
||||||
{
|
{
|
||||||
$bar->clear();
|
$bar->clear();
|
||||||
$callback();
|
$callback();
|
||||||
|
|
|
@ -44,10 +44,10 @@ class DeleteUserCommand extends Command
|
||||||
if ($this->input->isInteractive()) {
|
if ($this->input->isInteractive()) {
|
||||||
$tableValues = [];
|
$tableValues = [];
|
||||||
foreach ($results as $user) {
|
foreach ($results as $user) {
|
||||||
$tableValues[] = [$user->id, $user->email, $user->name];
|
$tableValues[] = [$user->id, $user->email, $user->username];
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->table(['User ID', 'Email', 'Name'], $tableValues);
|
$this->table(['User ID', 'Email', 'Username'], $tableValues);
|
||||||
if (!$deleteUser = $this->ask(trans('command/messages.user.select_search_user'))) {
|
if (!$deleteUser = $this->ask(trans('command/messages.user.select_search_user'))) {
|
||||||
return $this->handle();
|
return $this->handle();
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,8 +30,6 @@ class MakeUserCommand extends Command
|
||||||
$root_admin = $this->option('admin') ?? $this->confirm(trans('command/messages.user.ask_admin'));
|
$root_admin = $this->option('admin') ?? $this->confirm(trans('command/messages.user.ask_admin'));
|
||||||
$email = $this->option('email') ?? $this->ask(trans('command/messages.user.ask_email'));
|
$email = $this->option('email') ?? $this->ask(trans('command/messages.user.ask_email'));
|
||||||
$username = $this->option('username') ?? $this->ask(trans('command/messages.user.ask_username'));
|
$username = $this->option('username') ?? $this->ask(trans('command/messages.user.ask_username'));
|
||||||
$name_first = $this->option('name-first') ?? $this->ask(trans('command/messages.user.ask_name_first'));
|
|
||||||
$name_last = $this->option('name-last') ?? $this->ask(trans('command/messages.user.ask_name_last'));
|
|
||||||
|
|
||||||
if (is_null($password = $this->option('password')) && !$this->option('no-password')) {
|
if (is_null($password = $this->option('password')) && !$this->option('no-password')) {
|
||||||
$this->warn(trans('command/messages.user.ask_password_help'));
|
$this->warn(trans('command/messages.user.ask_password_help'));
|
||||||
|
@ -39,12 +37,11 @@ class MakeUserCommand extends Command
|
||||||
$password = $this->secret(trans('command/messages.user.ask_password'));
|
$password = $this->secret(trans('command/messages.user.ask_password'));
|
||||||
}
|
}
|
||||||
|
|
||||||
$user = $this->creationService->handle(compact('email', 'username', 'name_first', 'name_last', 'password', 'root_admin'));
|
$user = $this->creationService->handle(compact('email', 'username', 'password', 'root_admin'));
|
||||||
$this->table(['Field', 'Value'], [
|
$this->table(['Field', 'Value'], [
|
||||||
['UUID', $user->uuid],
|
['UUID', $user->uuid],
|
||||||
['Email', $user->email],
|
['Email', $user->email],
|
||||||
['Username', $user->username],
|
['Username', $user->username],
|
||||||
['Name', $user->name],
|
|
||||||
['Admin', $user->root_admin ? 'Yes' : 'No'],
|
['Admin', $user->root_admin ? 'Yes' : 'No'],
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,10 +2,13 @@
|
||||||
|
|
||||||
namespace Pterodactyl\Console;
|
namespace Pterodactyl\Console;
|
||||||
|
|
||||||
|
use Ramsey\Uuid\Uuid;
|
||||||
use Pterodactyl\Models\ActivityLog;
|
use Pterodactyl\Models\ActivityLog;
|
||||||
use Illuminate\Console\Scheduling\Schedule;
|
use Illuminate\Console\Scheduling\Schedule;
|
||||||
use Illuminate\Database\Console\PruneCommand;
|
use Illuminate\Database\Console\PruneCommand;
|
||||||
|
use Pterodactyl\Repositories\Eloquent\SettingsRepository;
|
||||||
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
|
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
|
||||||
|
use Pterodactyl\Services\Telemetry\TelemetryCollectionService;
|
||||||
use Pterodactyl\Console\Commands\Schedule\ProcessRunnableCommand;
|
use Pterodactyl\Console\Commands\Schedule\ProcessRunnableCommand;
|
||||||
use Pterodactyl\Console\Commands\Maintenance\PruneOrphanedBackupsCommand;
|
use Pterodactyl\Console\Commands\Maintenance\PruneOrphanedBackupsCommand;
|
||||||
use Pterodactyl\Console\Commands\Maintenance\CleanServiceBackupFilesCommand;
|
use Pterodactyl\Console\Commands\Maintenance\CleanServiceBackupFilesCommand;
|
||||||
|
@ -37,5 +40,34 @@ class Kernel extends ConsoleKernel
|
||||||
if (config('activity.prune_days')) {
|
if (config('activity.prune_days')) {
|
||||||
$schedule->command(PruneCommand::class, ['--model' => [ActivityLog::class]])->daily();
|
$schedule->command(PruneCommand::class, ['--model' => [ActivityLog::class]])->daily();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (config('pterodactyl.telemetry.enabled')) {
|
||||||
|
$this->registerTelemetry($schedule);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* I wonder what this does.
|
||||||
|
*
|
||||||
|
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
|
||||||
|
* @throws \Illuminate\Contracts\Container\BindingResolutionException
|
||||||
|
*/
|
||||||
|
private function registerTelemetry(Schedule $schedule): void
|
||||||
|
{
|
||||||
|
$settingsRepository = app()->make(SettingsRepository::class);
|
||||||
|
|
||||||
|
$uuid = $settingsRepository->get('app:telemetry:uuid');
|
||||||
|
if (is_null($uuid)) {
|
||||||
|
$uuid = Uuid::uuid4()->toString();
|
||||||
|
$settingsRepository->set('app:telemetry:uuid', $uuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate a fixed time to run the data push at, this will be the same time every day.
|
||||||
|
$time = hexdec(str_replace('-', '', substr($uuid, 27))) % 1440;
|
||||||
|
$hour = floor($time / 60);
|
||||||
|
$minute = $time % 60;
|
||||||
|
|
||||||
|
// Run the telemetry collector.
|
||||||
|
$schedule->call(app()->make(TelemetryCollectionService::class))->description('Collect Telemetry')->dailyAt("$hour:$minute");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,6 @@
|
||||||
|
|
||||||
namespace Pterodactyl\Exceptions;
|
namespace Pterodactyl\Exceptions;
|
||||||
|
|
||||||
use Exception;
|
class AccountNotFoundException extends \Exception
|
||||||
|
|
||||||
class AccountNotFoundException extends Exception
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,6 @@
|
||||||
|
|
||||||
namespace Pterodactyl\Exceptions;
|
namespace Pterodactyl\Exceptions;
|
||||||
|
|
||||||
use Exception;
|
class AutoDeploymentException extends \Exception
|
||||||
|
|
||||||
class AutoDeploymentException extends Exception
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,14 +3,12 @@
|
||||||
namespace Pterodactyl\Exceptions;
|
namespace Pterodactyl\Exceptions;
|
||||||
|
|
||||||
use Exception;
|
use Exception;
|
||||||
use Throwable;
|
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
use Illuminate\Http\Response;
|
use Illuminate\Http\Response;
|
||||||
use Illuminate\Http\JsonResponse;
|
use Illuminate\Http\JsonResponse;
|
||||||
use Illuminate\Container\Container;
|
use Illuminate\Container\Container;
|
||||||
use Illuminate\Http\RedirectResponse;
|
use Illuminate\Http\RedirectResponse;
|
||||||
use Prologue\Alerts\AlertsMessageBag;
|
|
||||||
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
|
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
|
||||||
|
|
||||||
class DisplayException extends PterodactylException implements HttpExceptionInterface
|
class DisplayException extends PterodactylException implements HttpExceptionInterface
|
||||||
|
@ -23,7 +21,7 @@ class DisplayException extends PterodactylException implements HttpExceptionInte
|
||||||
/**
|
/**
|
||||||
* DisplayException constructor.
|
* DisplayException constructor.
|
||||||
*/
|
*/
|
||||||
public function __construct(string $message, ?Throwable $previous = null, protected string $level = self::LEVEL_ERROR, int $code = 0)
|
public function __construct(string $message, ?\Throwable $previous = null, protected string $level = self::LEVEL_ERROR, int $code = 0)
|
||||||
{
|
{
|
||||||
parent::__construct($message, $code, $previous);
|
parent::__construct($message, $code, $previous);
|
||||||
}
|
}
|
||||||
|
@ -54,8 +52,6 @@ class DisplayException extends PterodactylException implements HttpExceptionInte
|
||||||
return response()->json(Handler::toArray($this), $this->getStatusCode(), $this->getHeaders());
|
return response()->json(Handler::toArray($this), $this->getStatusCode(), $this->getHeaders());
|
||||||
}
|
}
|
||||||
|
|
||||||
app(AlertsMessageBag::class)->danger($this->getMessage())->flash();
|
|
||||||
|
|
||||||
return redirect()->back()->withInput();
|
return redirect()->back()->withInput();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,7 +63,7 @@ class DisplayException extends PterodactylException implements HttpExceptionInte
|
||||||
*/
|
*/
|
||||||
public function report()
|
public function report()
|
||||||
{
|
{
|
||||||
if (!$this->getPrevious() instanceof Exception || !Handler::isReportable($this->getPrevious())) {
|
if (!$this->getPrevious() instanceof \Exception || !Handler::isReportable($this->getPrevious())) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,8 +3,6 @@
|
||||||
namespace Pterodactyl\Exceptions;
|
namespace Pterodactyl\Exceptions;
|
||||||
|
|
||||||
use Exception;
|
use Exception;
|
||||||
use Throwable;
|
|
||||||
use PDOException;
|
|
||||||
use Illuminate\Support\Arr;
|
use Illuminate\Support\Arr;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use Illuminate\Http\JsonResponse;
|
use Illuminate\Http\JsonResponse;
|
||||||
|
@ -25,7 +23,7 @@ use Pterodactyl\Exceptions\Repository\RecordNotFoundException;
|
||||||
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
|
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
|
||||||
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
|
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
|
||||||
|
|
||||||
class Handler extends ExceptionHandler
|
final class Handler extends ExceptionHandler
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The validation parser in Laravel formats custom rules using the class name
|
* The validation parser in Laravel formats custom rules using the class name
|
||||||
|
@ -81,7 +79,7 @@ class Handler extends ExceptionHandler
|
||||||
$this->dontReport = [];
|
$this->dontReport = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->reportable(function (PDOException $ex) {
|
$this->reportable(function (\PDOException $ex) {
|
||||||
$ex = $this->generateCleanedExceptionStack($ex);
|
$ex = $this->generateCleanedExceptionStack($ex);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -90,7 +88,7 @@ class Handler extends ExceptionHandler
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private function generateCleanedExceptionStack(Throwable $exception): string
|
private function generateCleanedExceptionStack(\Throwable $exception): string
|
||||||
{
|
{
|
||||||
$cleanedStack = '';
|
$cleanedStack = '';
|
||||||
foreach ($exception->getTrace() as $index => $item) {
|
foreach ($exception->getTrace() as $index => $item) {
|
||||||
|
@ -123,7 +121,7 @@ class Handler extends ExceptionHandler
|
||||||
*
|
*
|
||||||
* @throws \Throwable
|
* @throws \Throwable
|
||||||
*/
|
*/
|
||||||
public function render($request, Throwable $e): Response
|
public function render($request, \Throwable $e): Response
|
||||||
{
|
{
|
||||||
$connections = $this->container->make(Connection::class);
|
$connections = $this->container->make(Connection::class);
|
||||||
|
|
||||||
|
@ -189,7 +187,7 @@ class Handler extends ExceptionHandler
|
||||||
/**
|
/**
|
||||||
* Return the exception as a JSONAPI representation for use on API requests.
|
* Return the exception as a JSONAPI representation for use on API requests.
|
||||||
*/
|
*/
|
||||||
protected function convertExceptionToArray(Throwable $e, array $override = []): array
|
protected function convertExceptionToArray(\Throwable $e, array $override = []): array
|
||||||
{
|
{
|
||||||
$match = self::$exceptionResponseCodes[get_class($e)] ?? null;
|
$match = self::$exceptionResponseCodes[get_class($e)] ?? null;
|
||||||
|
|
||||||
|
@ -235,7 +233,7 @@ class Handler extends ExceptionHandler
|
||||||
/**
|
/**
|
||||||
* Return an array of exceptions that should not be reported.
|
* Return an array of exceptions that should not be reported.
|
||||||
*/
|
*/
|
||||||
public static function isReportable(Exception $exception): bool
|
public static function isReportable(\Exception $exception): bool
|
||||||
{
|
{
|
||||||
return (new static(Container::getInstance()))->shouldReport($exception);
|
return (new static(Container::getInstance()))->shouldReport($exception);
|
||||||
}
|
}
|
||||||
|
@ -260,11 +258,11 @@ class Handler extends ExceptionHandler
|
||||||
*
|
*
|
||||||
* @return \Throwable[]
|
* @return \Throwable[]
|
||||||
*/
|
*/
|
||||||
protected function extractPrevious(Throwable $e): array
|
protected function extractPrevious(\Throwable $e): array
|
||||||
{
|
{
|
||||||
$previous = [];
|
$previous = [];
|
||||||
while ($value = $e->getPrevious()) {
|
while ($value = $e->getPrevious()) {
|
||||||
if (!$value instanceof Throwable) {
|
if (!$value instanceof \Throwable) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
$previous[] = $value;
|
$previous[] = $value;
|
||||||
|
@ -278,7 +276,7 @@ class Handler extends ExceptionHandler
|
||||||
* Helper method to allow reaching into the handler to convert an exception
|
* Helper method to allow reaching into the handler to convert an exception
|
||||||
* into the expected array response type.
|
* into the expected array response type.
|
||||||
*/
|
*/
|
||||||
public static function toArray(Throwable $e): array
|
public static function toArray(\Throwable $e): array
|
||||||
{
|
{
|
||||||
return (new self(app()))->convertExceptionToArray($e);
|
return (new self(app()))->convertExceptionToArray($e);
|
||||||
}
|
}
|
||||||
|
|
21
app/Exceptions/Http/QueryValueOutOfRangeHttpException.php
Normal file
21
app/Exceptions/Http/QueryValueOutOfRangeHttpException.php
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Pterodactyl\Exceptions\Http;
|
||||||
|
|
||||||
|
use Illuminate\Http\Response;
|
||||||
|
use Symfony\Component\HttpKernel\Exception\HttpException;
|
||||||
|
|
||||||
|
class QueryValueOutOfRangeHttpException extends HttpException
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* QueryValueOutOfRangeHttpException constructor.
|
||||||
|
*/
|
||||||
|
public function __construct(string $name, int $min, int $max, \Throwable $previous = null)
|
||||||
|
{
|
||||||
|
parent::__construct(
|
||||||
|
Response::HTTP_BAD_REQUEST,
|
||||||
|
'\"' . $name . '\" query parameter must be between ' . $min . ' and ' . $max,
|
||||||
|
$previous,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
namespace Pterodactyl\Exceptions\Http\Server;
|
namespace Pterodactyl\Exceptions\Http\Server;
|
||||||
|
|
||||||
use Throwable;
|
|
||||||
use Pterodactyl\Models\Server;
|
use Pterodactyl\Models\Server;
|
||||||
use Symfony\Component\HttpKernel\Exception\ConflictHttpException;
|
use Symfony\Component\HttpKernel\Exception\ConflictHttpException;
|
||||||
|
|
||||||
|
@ -12,11 +11,13 @@ class ServerStateConflictException extends ConflictHttpException
|
||||||
* Exception thrown when the server is in an unsupported state for API access or
|
* Exception thrown when the server is in an unsupported state for API access or
|
||||||
* certain operations within the codebase.
|
* certain operations within the codebase.
|
||||||
*/
|
*/
|
||||||
public function __construct(Server $server, Throwable $previous = null)
|
public function __construct(Server $server, \Throwable $previous = null)
|
||||||
{
|
{
|
||||||
$message = 'This server is currently in an unsupported state, please try again later.';
|
$message = 'This server is currently in an unsupported state, please try again later.';
|
||||||
if ($server->isSuspended()) {
|
if ($server->isSuspended()) {
|
||||||
$message = 'This server is currently suspended and the functionality requested is unavailable.';
|
$message = 'This server is currently suspended and the functionality requested is unavailable.';
|
||||||
|
} elseif ($server->node->isUnderMaintenance()) {
|
||||||
|
$message = 'The node of this server is currently under maintenance and the functionality requested is unavailable.';
|
||||||
} elseif (!$server->isInstalled()) {
|
} elseif (!$server->isInstalled()) {
|
||||||
$message = 'This server has not yet completed its installation process, please try again later.';
|
$message = 'This server has not yet completed its installation process, please try again later.';
|
||||||
} elseif ($server->status === Server::STATUS_RESTORING_BACKUP) {
|
} elseif ($server->status === Server::STATUS_RESTORING_BACKUP) {
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
namespace Pterodactyl\Exceptions\Http;
|
namespace Pterodactyl\Exceptions\Http;
|
||||||
|
|
||||||
use Throwable;
|
|
||||||
use Illuminate\Http\Response;
|
use Illuminate\Http\Response;
|
||||||
use Symfony\Component\HttpKernel\Exception\HttpException;
|
use Symfony\Component\HttpKernel\Exception\HttpException;
|
||||||
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
|
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
|
||||||
|
@ -12,7 +11,7 @@ class TwoFactorAuthRequiredException extends HttpException implements HttpExcept
|
||||||
/**
|
/**
|
||||||
* TwoFactorAuthRequiredException constructor.
|
* TwoFactorAuthRequiredException constructor.
|
||||||
*/
|
*/
|
||||||
public function __construct(Throwable $previous = null)
|
public function __construct(\Throwable $previous = null)
|
||||||
{
|
{
|
||||||
parent::__construct(Response::HTTP_BAD_REQUEST, 'Two-factor authentication is required on this account in order to access this endpoint.', $previous);
|
parent::__construct(Response::HTTP_BAD_REQUEST, 'Two-factor authentication is required on this account in order to access this endpoint.', $previous);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Pterodactyl\Exceptions;
|
|
||||||
|
|
||||||
use Exception;
|
|
||||||
use Spatie\Ignition\Contracts\Solution;
|
|
||||||
use Spatie\Ignition\Contracts\ProvidesSolution;
|
|
||||||
|
|
||||||
class ManifestDoesNotExistException extends Exception implements ProvidesSolution
|
|
||||||
{
|
|
||||||
public function getSolution(): Solution
|
|
||||||
{
|
|
||||||
return new Solutions\ManifestDoesNotExistSolution();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -2,8 +2,6 @@
|
||||||
|
|
||||||
namespace Pterodactyl\Exceptions;
|
namespace Pterodactyl\Exceptions;
|
||||||
|
|
||||||
use Exception;
|
class PterodactylException extends \Exception
|
||||||
|
|
||||||
class PterodactylException extends Exception
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
9
app/Exceptions/Service/Egg/BadYamlFormatException.php
Normal file
9
app/Exceptions/Service/Egg/BadYamlFormatException.php
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Pterodactyl\Exceptions\Service\Egg;
|
||||||
|
|
||||||
|
use Pterodactyl\Exceptions\DisplayException;
|
||||||
|
|
||||||
|
class BadYamlFormatException extends DisplayException
|
||||||
|
{
|
||||||
|
}
|
|
@ -2,8 +2,6 @@
|
||||||
|
|
||||||
namespace Pterodactyl\Exceptions\Service\Helper;
|
namespace Pterodactyl\Exceptions\Service\Helper;
|
||||||
|
|
||||||
use Exception;
|
class CdnVersionFetchingException extends \Exception
|
||||||
|
|
||||||
class CdnVersionFetchingException extends Exception
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
namespace Pterodactyl\Exceptions\Service;
|
namespace Pterodactyl\Exceptions\Service;
|
||||||
|
|
||||||
use Throwable;
|
|
||||||
use Pterodactyl\Exceptions\DisplayException;
|
use Pterodactyl\Exceptions\DisplayException;
|
||||||
|
|
||||||
class ServiceLimitExceededException extends DisplayException
|
class ServiceLimitExceededException extends DisplayException
|
||||||
|
@ -11,7 +10,7 @@ class ServiceLimitExceededException extends DisplayException
|
||||||
* Exception thrown when something goes over a defined limit, such as allocated
|
* Exception thrown when something goes over a defined limit, such as allocated
|
||||||
* ports, tasks, databases, etc.
|
* ports, tasks, databases, etc.
|
||||||
*/
|
*/
|
||||||
public function __construct(string $message, Throwable $previous = null)
|
public function __construct(string $message, \Throwable $previous = null)
|
||||||
{
|
{
|
||||||
parent::__construct($message, $previous, self::LEVEL_WARNING);
|
parent::__construct($message, $previous, self::LEVEL_WARNING);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,25 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Pterodactyl\Exceptions\Solutions;
|
|
||||||
|
|
||||||
use Spatie\Ignition\Contracts\Solution;
|
|
||||||
|
|
||||||
class ManifestDoesNotExistSolution implements Solution
|
|
||||||
{
|
|
||||||
public function getSolutionTitle(): string
|
|
||||||
{
|
|
||||||
return "The manifest.json file hasn't been generated yet";
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getSolutionDescription(): string
|
|
||||||
{
|
|
||||||
return 'Run yarn run build:production to build the frontend first.';
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getDocumentationLinks(): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'Docs' => 'https://github.com/pterodactyl/panel/blob/develop/package.json',
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Pterodactyl\Exceptions\Transformer;
|
|
||||||
|
|
||||||
use Pterodactyl\Exceptions\PterodactylException;
|
|
||||||
|
|
||||||
class InvalidTransformerLevelException extends PterodactylException
|
|
||||||
{
|
|
||||||
}
|
|
|
@ -2,12 +2,10 @@
|
||||||
|
|
||||||
namespace Pterodactyl\Extensions\Backups;
|
namespace Pterodactyl\Extensions\Backups;
|
||||||
|
|
||||||
use Closure;
|
|
||||||
use Aws\S3\S3Client;
|
use Aws\S3\S3Client;
|
||||||
use Illuminate\Support\Arr;
|
use Illuminate\Support\Arr;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use Webmozart\Assert\Assert;
|
use Webmozart\Assert\Assert;
|
||||||
use InvalidArgumentException;
|
|
||||||
use Illuminate\Foundation\Application;
|
use Illuminate\Foundation\Application;
|
||||||
use League\Flysystem\FilesystemAdapter;
|
use League\Flysystem\FilesystemAdapter;
|
||||||
use Pterodactyl\Extensions\Filesystem\S3Filesystem;
|
use Pterodactyl\Extensions\Filesystem\S3Filesystem;
|
||||||
|
@ -70,7 +68,7 @@ class BackupManager
|
||||||
$config = $this->getConfig($name);
|
$config = $this->getConfig($name);
|
||||||
|
|
||||||
if (empty($config['adapter'])) {
|
if (empty($config['adapter'])) {
|
||||||
throw new InvalidArgumentException("Backup disk [$name] does not have a configured adapter.");
|
throw new \InvalidArgumentException("Backup disk [$name] does not have a configured adapter.");
|
||||||
}
|
}
|
||||||
|
|
||||||
$adapter = $config['adapter'];
|
$adapter = $config['adapter'];
|
||||||
|
@ -88,7 +86,7 @@ class BackupManager
|
||||||
return $instance;
|
return $instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new InvalidArgumentException("Adapter [$adapter] is not supported.");
|
throw new \InvalidArgumentException("Adapter [$adapter] is not supported.");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -164,7 +162,7 @@ class BackupManager
|
||||||
/**
|
/**
|
||||||
* Register a custom adapter creator closure.
|
* Register a custom adapter creator closure.
|
||||||
*/
|
*/
|
||||||
public function extend(string $adapter, Closure $callback): self
|
public function extend(string $adapter, \Closure $callback): self
|
||||||
{
|
{
|
||||||
$this->customCreators[$adapter] = $callback;
|
$this->customCreators[$adapter] = $callback;
|
||||||
|
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Pterodactyl\Extensions\Facades;
|
|
||||||
|
|
||||||
use Illuminate\Support\Facades\Facade;
|
|
||||||
|
|
||||||
class Theme extends Facade
|
|
||||||
{
|
|
||||||
protected static function getFacadeAccessor(): string
|
|
||||||
{
|
|
||||||
return 'extensions.themes';
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
namespace Pterodactyl\Extensions\Lcobucci\JWT\Encoding;
|
namespace Pterodactyl\Extensions\Lcobucci\JWT\Encoding;
|
||||||
|
|
||||||
use DateTimeImmutable;
|
|
||||||
use Lcobucci\JWT\ClaimsFormatter;
|
use Lcobucci\JWT\ClaimsFormatter;
|
||||||
use Lcobucci\JWT\Token\RegisteredClaims;
|
use Lcobucci\JWT\Token\RegisteredClaims;
|
||||||
|
|
||||||
|
@ -21,7 +20,7 @@ final class TimestampDates implements ClaimsFormatter
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
assert($claims[$claim] instanceof DateTimeImmutable);
|
assert($claims[$claim] instanceof \DateTimeImmutable);
|
||||||
$claims[$claim] = $claims[$claim]->getTimestamp();
|
$claims[$claim] = $claims[$claim]->getTimestamp();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,17 +6,6 @@ use League\Fractal\Serializer\ArraySerializer;
|
||||||
|
|
||||||
class PterodactylSerializer extends ArraySerializer
|
class PterodactylSerializer extends ArraySerializer
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* Serialize an item.
|
|
||||||
*/
|
|
||||||
public function item(?string $resourceKey, array $data): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'object' => $resourceKey,
|
|
||||||
'attributes' => $data,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Serialize a collection.
|
* Serialize a collection.
|
||||||
*/
|
*/
|
||||||
|
@ -33,6 +22,17 @@ class PterodactylSerializer extends ArraySerializer
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serialize an item.
|
||||||
|
*/
|
||||||
|
public function item(?string $resourceKey, array $data): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'object' => $resourceKey,
|
||||||
|
'attributes' => $data,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Serialize a null resource.
|
* Serialize a null resource.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -3,8 +3,8 @@
|
||||||
namespace Pterodactyl\Extensions\Spatie\Fractalistic;
|
namespace Pterodactyl\Extensions\Spatie\Fractalistic;
|
||||||
|
|
||||||
use League\Fractal\Scope;
|
use League\Fractal\Scope;
|
||||||
use League\Fractal\TransformerAbstract;
|
|
||||||
use Spatie\Fractal\Fractal as SpatieFractal;
|
use Spatie\Fractal\Fractal as SpatieFractal;
|
||||||
|
use Pterodactyl\Transformers\Api\Transformer;
|
||||||
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
||||||
use League\Fractal\Pagination\IlluminatePaginatorAdapter;
|
use League\Fractal\Pagination\IlluminatePaginatorAdapter;
|
||||||
use Pterodactyl\Extensions\League\Fractal\Serializers\PterodactylSerializer;
|
use Pterodactyl\Extensions\League\Fractal\Serializers\PterodactylSerializer;
|
||||||
|
@ -32,12 +32,9 @@ class Fractal extends SpatieFractal
|
||||||
|
|
||||||
// If the resource name is not set attempt to pull it off the transformer
|
// If the resource name is not set attempt to pull it off the transformer
|
||||||
// itself and set it automatically.
|
// itself and set it automatically.
|
||||||
if (
|
$class = is_string($this->transformer) ? new $this->transformer() : $this->transformer;
|
||||||
is_null($this->resourceName)
|
if (is_null($this->resourceName) && $class instanceof Transformer) {
|
||||||
&& $this->transformer instanceof TransformerAbstract
|
$this->resourceName = $class->getResourceName();
|
||||||
&& method_exists($this->transformer, 'getResourceName')
|
|
||||||
) {
|
|
||||||
$this->resourceName = $this->transformer->getResourceName();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return parent::createData();
|
return parent::createData();
|
||||||
|
|
|
@ -1,21 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Pterodactyl\Extensions\Themes;
|
|
||||||
|
|
||||||
class Theme
|
|
||||||
{
|
|
||||||
public function js($path): string
|
|
||||||
{
|
|
||||||
return sprintf('<script src="%s"></script>' . PHP_EOL, $this->getUrl($path));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function css($path): string
|
|
||||||
{
|
|
||||||
return sprintf('<link media="all" type="text/css" rel="stylesheet" href="%s"/>' . PHP_EOL, $this->getUrl($path));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function getUrl($path): string
|
|
||||||
{
|
|
||||||
return '/themes/pterodactyl/' . ltrim($path, '/');
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
namespace Pterodactyl\Helpers;
|
namespace Pterodactyl\Helpers;
|
||||||
|
|
||||||
use Exception;
|
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
use Cron\CronExpression;
|
use Cron\CronExpression;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
|
@ -25,7 +24,7 @@ class Utilities
|
||||||
|
|
||||||
$string = substr_replace($string, $character, random_int(0, $length - 1), 1);
|
$string = substr_replace($string, $character, random_int(0, $length - 1), 1);
|
||||||
}
|
}
|
||||||
} catch (Exception $exception) {
|
} catch (\Exception $exception) {
|
||||||
// Just log the error and hope for the best at this point.
|
// Just log the error and hope for the best at this point.
|
||||||
Log::error($exception);
|
Log::error($exception);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,87 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Pterodactyl\Http\Controllers\Admin;
|
|
||||||
|
|
||||||
use Illuminate\View\View;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Illuminate\Http\Response;
|
|
||||||
use Pterodactyl\Models\ApiKey;
|
|
||||||
use Illuminate\Http\RedirectResponse;
|
|
||||||
use Prologue\Alerts\AlertsMessageBag;
|
|
||||||
use Pterodactyl\Services\Acl\Api\AdminAcl;
|
|
||||||
use Illuminate\View\Factory as ViewFactory;
|
|
||||||
use Pterodactyl\Http\Controllers\Controller;
|
|
||||||
use Pterodactyl\Services\Api\KeyCreationService;
|
|
||||||
use Pterodactyl\Contracts\Repository\ApiKeyRepositoryInterface;
|
|
||||||
use Pterodactyl\Http\Requests\Admin\Api\StoreApplicationApiKeyRequest;
|
|
||||||
|
|
||||||
class ApiController extends Controller
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* ApiController constructor.
|
|
||||||
*/
|
|
||||||
public function __construct(
|
|
||||||
private AlertsMessageBag $alert,
|
|
||||||
private ApiKeyRepositoryInterface $repository,
|
|
||||||
private KeyCreationService $keyCreationService,
|
|
||||||
private ViewFactory $view,
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Render view showing all of a user's application API keys.
|
|
||||||
*/
|
|
||||||
public function index(Request $request): View
|
|
||||||
{
|
|
||||||
return $this->view->make('admin.api.index', [
|
|
||||||
'keys' => $this->repository->getApplicationKeys($request->user()),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Render view allowing an admin to create a new application API key.
|
|
||||||
*
|
|
||||||
* @throws \ReflectionException
|
|
||||||
*/
|
|
||||||
public function create(): View
|
|
||||||
{
|
|
||||||
$resources = AdminAcl::getResourceList();
|
|
||||||
sort($resources);
|
|
||||||
|
|
||||||
return $this->view->make('admin.api.new', [
|
|
||||||
'resources' => $resources,
|
|
||||||
'permissions' => [
|
|
||||||
'r' => AdminAcl::READ,
|
|
||||||
'rw' => AdminAcl::READ | AdminAcl::WRITE,
|
|
||||||
'n' => AdminAcl::NONE,
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Store the new key and redirect the user back to the application key listing.
|
|
||||||
*
|
|
||||||
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
|
|
||||||
*/
|
|
||||||
public function store(StoreApplicationApiKeyRequest $request): RedirectResponse
|
|
||||||
{
|
|
||||||
$this->keyCreationService->setKeyType(ApiKey::TYPE_APPLICATION)->handle([
|
|
||||||
'memo' => $request->input('memo'),
|
|
||||||
'user_id' => $request->user()->id,
|
|
||||||
], $request->getKeyPermissions());
|
|
||||||
|
|
||||||
$this->alert->success('A new application API key has been generated for your account.')->flash();
|
|
||||||
|
|
||||||
return redirect()->route('admin.api.index');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete an application API key from the database.
|
|
||||||
*/
|
|
||||||
public function delete(Request $request, string $identifier): Response
|
|
||||||
{
|
|
||||||
$this->repository->deleteApplicationKey($request->user(), $identifier);
|
|
||||||
|
|
||||||
return response('', 204);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -3,24 +3,12 @@
|
||||||
namespace Pterodactyl\Http\Controllers\Admin;
|
namespace Pterodactyl\Http\Controllers\Admin;
|
||||||
|
|
||||||
use Illuminate\View\View;
|
use Illuminate\View\View;
|
||||||
use Illuminate\View\Factory as ViewFactory;
|
|
||||||
use Pterodactyl\Http\Controllers\Controller;
|
use Pterodactyl\Http\Controllers\Controller;
|
||||||
use Pterodactyl\Services\Helpers\SoftwareVersionService;
|
|
||||||
|
|
||||||
class BaseController extends Controller
|
class BaseController extends Controller
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* BaseController constructor.
|
|
||||||
*/
|
|
||||||
public function __construct(private SoftwareVersionService $version, private ViewFactory $view)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the admin index view.
|
|
||||||
*/
|
|
||||||
public function index(): View
|
public function index(): View
|
||||||
{
|
{
|
||||||
return $this->view->make('admin.index', ['version' => $this->version]);
|
return view('templates/base.core');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,130 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Pterodactyl\Http\Controllers\Admin;
|
|
||||||
|
|
||||||
use Exception;
|
|
||||||
use PDOException;
|
|
||||||
use Illuminate\View\View;
|
|
||||||
use Pterodactyl\Models\DatabaseHost;
|
|
||||||
use Illuminate\Http\RedirectResponse;
|
|
||||||
use Prologue\Alerts\AlertsMessageBag;
|
|
||||||
use Illuminate\View\Factory as ViewFactory;
|
|
||||||
use Pterodactyl\Http\Controllers\Controller;
|
|
||||||
use Pterodactyl\Services\Databases\Hosts\HostUpdateService;
|
|
||||||
use Pterodactyl\Http\Requests\Admin\DatabaseHostFormRequest;
|
|
||||||
use Pterodactyl\Services\Databases\Hosts\HostCreationService;
|
|
||||||
use Pterodactyl\Services\Databases\Hosts\HostDeletionService;
|
|
||||||
use Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface;
|
|
||||||
use Pterodactyl\Contracts\Repository\LocationRepositoryInterface;
|
|
||||||
use Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface;
|
|
||||||
|
|
||||||
class DatabaseController extends Controller
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* DatabaseController constructor.
|
|
||||||
*/
|
|
||||||
public function __construct(
|
|
||||||
private AlertsMessageBag $alert,
|
|
||||||
private DatabaseHostRepositoryInterface $repository,
|
|
||||||
private DatabaseRepositoryInterface $databaseRepository,
|
|
||||||
private HostCreationService $creationService,
|
|
||||||
private HostDeletionService $deletionService,
|
|
||||||
private HostUpdateService $updateService,
|
|
||||||
private LocationRepositoryInterface $locationRepository,
|
|
||||||
private ViewFactory $view
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Display database host index.
|
|
||||||
*/
|
|
||||||
public function index(): View
|
|
||||||
{
|
|
||||||
return $this->view->make('admin.databases.index', [
|
|
||||||
'locations' => $this->locationRepository->getAllWithNodes(),
|
|
||||||
'hosts' => $this->repository->getWithViewDetails(),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Display database host to user.
|
|
||||||
*
|
|
||||||
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
|
||||||
*/
|
|
||||||
public function view(int $host): View
|
|
||||||
{
|
|
||||||
return $this->view->make('admin.databases.view', [
|
|
||||||
'locations' => $this->locationRepository->getAllWithNodes(),
|
|
||||||
'host' => $this->repository->find($host),
|
|
||||||
'databases' => $this->databaseRepository->getDatabasesForHost($host),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle request to create a new database host.
|
|
||||||
*
|
|
||||||
* @throws \Throwable
|
|
||||||
*/
|
|
||||||
public function create(DatabaseHostFormRequest $request): RedirectResponse
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$host = $this->creationService->handle($request->normalize());
|
|
||||||
} catch (Exception $exception) {
|
|
||||||
if ($exception instanceof PDOException || $exception->getPrevious() instanceof PDOException) {
|
|
||||||
$this->alert->danger(
|
|
||||||
sprintf('There was an error while trying to connect to the host or while executing a query: "%s"', $exception->getMessage())
|
|
||||||
)->flash();
|
|
||||||
|
|
||||||
return redirect()->route('admin.databases')->withInput($request->validated());
|
|
||||||
} else {
|
|
||||||
throw $exception;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->alert->success('Successfully created a new database host on the system.')->flash();
|
|
||||||
|
|
||||||
return redirect()->route('admin.databases.view', $host->id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle updating database host.
|
|
||||||
*
|
|
||||||
* @throws \Throwable
|
|
||||||
*/
|
|
||||||
public function update(DatabaseHostFormRequest $request, DatabaseHost $host): RedirectResponse
|
|
||||||
{
|
|
||||||
$redirect = redirect()->route('admin.databases.view', $host->id);
|
|
||||||
|
|
||||||
try {
|
|
||||||
$this->updateService->handle($host->id, $request->normalize());
|
|
||||||
$this->alert->success('Database host was updated successfully.')->flash();
|
|
||||||
} catch (Exception $exception) {
|
|
||||||
// Catch any SQL related exceptions and display them back to the user, otherwise just
|
|
||||||
// throw the exception like normal and move on with it.
|
|
||||||
if ($exception instanceof PDOException || $exception->getPrevious() instanceof PDOException) {
|
|
||||||
$this->alert->danger(
|
|
||||||
sprintf('There was an error while trying to connect to the host or while executing a query: "%s"', $exception->getMessage())
|
|
||||||
)->flash();
|
|
||||||
|
|
||||||
return $redirect->withInput($request->normalize());
|
|
||||||
} else {
|
|
||||||
throw $exception;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $redirect;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle request to delete a database host.
|
|
||||||
*
|
|
||||||
* @throws \Pterodactyl\Exceptions\Service\HasActiveServersException
|
|
||||||
*/
|
|
||||||
public function delete(int $host): RedirectResponse
|
|
||||||
{
|
|
||||||
$this->deletionService->handle($host);
|
|
||||||
$this->alert->success('The requested database host has been deleted from the system.')->flash();
|
|
||||||
|
|
||||||
return redirect()->route('admin.databases');
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,103 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Pterodactyl\Http\Controllers\Admin;
|
|
||||||
|
|
||||||
use Illuminate\View\View;
|
|
||||||
use Pterodactyl\Models\Location;
|
|
||||||
use Illuminate\Http\RedirectResponse;
|
|
||||||
use Prologue\Alerts\AlertsMessageBag;
|
|
||||||
use Illuminate\View\Factory as ViewFactory;
|
|
||||||
use Pterodactyl\Exceptions\DisplayException;
|
|
||||||
use Pterodactyl\Http\Controllers\Controller;
|
|
||||||
use Pterodactyl\Http\Requests\Admin\LocationFormRequest;
|
|
||||||
use Pterodactyl\Services\Locations\LocationUpdateService;
|
|
||||||
use Pterodactyl\Services\Locations\LocationCreationService;
|
|
||||||
use Pterodactyl\Services\Locations\LocationDeletionService;
|
|
||||||
use Pterodactyl\Contracts\Repository\LocationRepositoryInterface;
|
|
||||||
|
|
||||||
class LocationController extends Controller
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* LocationController constructor.
|
|
||||||
*/
|
|
||||||
public function __construct(
|
|
||||||
protected AlertsMessageBag $alert,
|
|
||||||
protected LocationCreationService $creationService,
|
|
||||||
protected LocationDeletionService $deletionService,
|
|
||||||
protected LocationRepositoryInterface $repository,
|
|
||||||
protected LocationUpdateService $updateService,
|
|
||||||
protected ViewFactory $view
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the location overview page.
|
|
||||||
*/
|
|
||||||
public function index(): View
|
|
||||||
{
|
|
||||||
return $this->view->make('admin.locations.index', [
|
|
||||||
'locations' => $this->repository->getAllWithDetails(),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the location view page.
|
|
||||||
*
|
|
||||||
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
|
||||||
*/
|
|
||||||
public function view(int $id): View
|
|
||||||
{
|
|
||||||
return $this->view->make('admin.locations.view', [
|
|
||||||
'location' => $this->repository->getWithNodes($id),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle request to create new location.
|
|
||||||
*
|
|
||||||
* @throws \Throwable
|
|
||||||
*/
|
|
||||||
public function create(LocationFormRequest $request): RedirectResponse
|
|
||||||
{
|
|
||||||
$location = $this->creationService->handle($request->normalize());
|
|
||||||
$this->alert->success('Location was created successfully.')->flash();
|
|
||||||
|
|
||||||
return redirect()->route('admin.locations.view', $location->id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle request to update or delete location.
|
|
||||||
*
|
|
||||||
* @throws \Throwable
|
|
||||||
*/
|
|
||||||
public function update(LocationFormRequest $request, Location $location): RedirectResponse
|
|
||||||
{
|
|
||||||
if ($request->input('action') === 'delete') {
|
|
||||||
return $this->delete($location);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->updateService->handle($location->id, $request->normalize());
|
|
||||||
$this->alert->success('Location was updated successfully.')->flash();
|
|
||||||
|
|
||||||
return redirect()->route('admin.locations.view', $location->id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete a location from the system.
|
|
||||||
*
|
|
||||||
* @throws \Exception
|
|
||||||
* @throws \Pterodactyl\Exceptions\DisplayException
|
|
||||||
*/
|
|
||||||
public function delete(Location $location): RedirectResponse
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$this->deletionService->handle($location->id);
|
|
||||||
|
|
||||||
return redirect()->route('admin.locations');
|
|
||||||
} catch (DisplayException $ex) {
|
|
||||||
$this->alert->danger($ex->getMessage())->flash();
|
|
||||||
}
|
|
||||||
|
|
||||||
return redirect()->route('admin.locations.view', $location->id);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,165 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Pterodactyl\Http\Controllers\Admin;
|
|
||||||
|
|
||||||
use Ramsey\Uuid\Uuid;
|
|
||||||
use Illuminate\View\View;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Pterodactyl\Models\Nest;
|
|
||||||
use Illuminate\Http\Response;
|
|
||||||
use Pterodactyl\Models\Mount;
|
|
||||||
use Pterodactyl\Models\Location;
|
|
||||||
use Illuminate\Http\RedirectResponse;
|
|
||||||
use Prologue\Alerts\AlertsMessageBag;
|
|
||||||
use Illuminate\View\Factory as ViewFactory;
|
|
||||||
use Pterodactyl\Http\Controllers\Controller;
|
|
||||||
use Pterodactyl\Http\Requests\Admin\MountFormRequest;
|
|
||||||
use Pterodactyl\Repositories\Eloquent\MountRepository;
|
|
||||||
use Pterodactyl\Contracts\Repository\NestRepositoryInterface;
|
|
||||||
use Pterodactyl\Contracts\Repository\LocationRepositoryInterface;
|
|
||||||
|
|
||||||
class MountController extends Controller
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* MountController constructor.
|
|
||||||
*/
|
|
||||||
public function __construct(
|
|
||||||
protected AlertsMessageBag $alert,
|
|
||||||
protected NestRepositoryInterface $nestRepository,
|
|
||||||
protected LocationRepositoryInterface $locationRepository,
|
|
||||||
protected MountRepository $repository,
|
|
||||||
protected ViewFactory $view
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the mount overview page.
|
|
||||||
*/
|
|
||||||
public function index(): View
|
|
||||||
{
|
|
||||||
return $this->view->make('admin.mounts.index', [
|
|
||||||
'mounts' => $this->repository->getAllWithDetails(),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the mount view page.
|
|
||||||
*
|
|
||||||
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
|
||||||
*/
|
|
||||||
public function view(string $id): View
|
|
||||||
{
|
|
||||||
$nests = Nest::query()->with('eggs')->get();
|
|
||||||
$locations = Location::query()->with('nodes')->get();
|
|
||||||
|
|
||||||
return $this->view->make('admin.mounts.view', [
|
|
||||||
'mount' => $this->repository->getWithRelations($id),
|
|
||||||
'nests' => $nests,
|
|
||||||
'locations' => $locations,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle request to create new mount.
|
|
||||||
*
|
|
||||||
* @throws \Throwable
|
|
||||||
*/
|
|
||||||
public function create(MountFormRequest $request): RedirectResponse
|
|
||||||
{
|
|
||||||
$model = (new Mount())->fill($request->validated());
|
|
||||||
$model->forceFill(['uuid' => Uuid::uuid4()->toString()]);
|
|
||||||
|
|
||||||
$model->saveOrFail();
|
|
||||||
$mount = $model->fresh();
|
|
||||||
|
|
||||||
$this->alert->success('Mount was created successfully.')->flash();
|
|
||||||
|
|
||||||
return redirect()->route('admin.mounts.view', $mount->id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle request to update or delete location.
|
|
||||||
*
|
|
||||||
* @throws \Throwable
|
|
||||||
*/
|
|
||||||
public function update(MountFormRequest $request, Mount $mount): RedirectResponse
|
|
||||||
{
|
|
||||||
if ($request->input('action') === 'delete') {
|
|
||||||
return $this->delete($mount);
|
|
||||||
}
|
|
||||||
|
|
||||||
$mount->forceFill($request->validated())->save();
|
|
||||||
|
|
||||||
$this->alert->success('Mount was updated successfully.')->flash();
|
|
||||||
|
|
||||||
return redirect()->route('admin.mounts.view', $mount->id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete a location from the system.
|
|
||||||
*
|
|
||||||
* @throws \Exception
|
|
||||||
*/
|
|
||||||
public function delete(Mount $mount): RedirectResponse
|
|
||||||
{
|
|
||||||
$mount->delete();
|
|
||||||
|
|
||||||
return redirect()->route('admin.mounts');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds eggs to the mount's many-to-many relation.
|
|
||||||
*/
|
|
||||||
public function addEggs(Request $request, Mount $mount): RedirectResponse
|
|
||||||
{
|
|
||||||
$validatedData = $request->validate([
|
|
||||||
'eggs' => 'required|exists:eggs,id',
|
|
||||||
]);
|
|
||||||
|
|
||||||
$eggs = $validatedData['eggs'] ?? [];
|
|
||||||
if (count($eggs) > 0) {
|
|
||||||
$mount->eggs()->attach($eggs);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->alert->success('Mount was updated successfully.')->flash();
|
|
||||||
|
|
||||||
return redirect()->route('admin.mounts.view', $mount->id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds nodes to the mount's many-to-many relation.
|
|
||||||
*/
|
|
||||||
public function addNodes(Request $request, Mount $mount): RedirectResponse
|
|
||||||
{
|
|
||||||
$data = $request->validate(['nodes' => 'required|exists:nodes,id']);
|
|
||||||
|
|
||||||
$nodes = $data['nodes'] ?? [];
|
|
||||||
if (count($nodes) > 0) {
|
|
||||||
$mount->nodes()->attach($nodes);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->alert->success('Mount was updated successfully.')->flash();
|
|
||||||
|
|
||||||
return redirect()->route('admin.mounts.view', $mount->id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes an egg from the mount's many-to-many relation.
|
|
||||||
*/
|
|
||||||
public function deleteEgg(Mount $mount, int $egg_id): Response
|
|
||||||
{
|
|
||||||
$mount->eggs()->detach($egg_id);
|
|
||||||
|
|
||||||
return response('', 204);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes a node from the mount's many-to-many relation.
|
|
||||||
*/
|
|
||||||
public function deleteNode(Mount $mount, int $node_id): Response
|
|
||||||
{
|
|
||||||
$mount->nodes()->detach($node_id);
|
|
||||||
|
|
||||||
return response('', 204);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,129 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Pterodactyl\Http\Controllers\Admin\Nests;
|
|
||||||
|
|
||||||
use JavaScript;
|
|
||||||
use Illuminate\View\View;
|
|
||||||
use Pterodactyl\Models\Egg;
|
|
||||||
use Illuminate\Http\RedirectResponse;
|
|
||||||
use Prologue\Alerts\AlertsMessageBag;
|
|
||||||
use Illuminate\View\Factory as ViewFactory;
|
|
||||||
use Pterodactyl\Http\Controllers\Controller;
|
|
||||||
use Pterodactyl\Services\Eggs\EggUpdateService;
|
|
||||||
use Pterodactyl\Services\Eggs\EggCreationService;
|
|
||||||
use Pterodactyl\Services\Eggs\EggDeletionService;
|
|
||||||
use Pterodactyl\Http\Requests\Admin\Egg\EggFormRequest;
|
|
||||||
use Pterodactyl\Contracts\Repository\EggRepositoryInterface;
|
|
||||||
use Pterodactyl\Contracts\Repository\NestRepositoryInterface;
|
|
||||||
|
|
||||||
class EggController extends Controller
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* EggController constructor.
|
|
||||||
*/
|
|
||||||
public function __construct(
|
|
||||||
protected AlertsMessageBag $alert,
|
|
||||||
protected EggCreationService $creationService,
|
|
||||||
protected EggDeletionService $deletionService,
|
|
||||||
protected EggRepositoryInterface $repository,
|
|
||||||
protected EggUpdateService $updateService,
|
|
||||||
protected NestRepositoryInterface $nestRepository,
|
|
||||||
protected ViewFactory $view
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle a request to display the Egg creation page.
|
|
||||||
*
|
|
||||||
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
|
||||||
*/
|
|
||||||
public function create(): View
|
|
||||||
{
|
|
||||||
$nests = $this->nestRepository->getWithEggs();
|
|
||||||
JavaScript::put(['nests' => $nests->keyBy('id')]);
|
|
||||||
|
|
||||||
return $this->view->make('admin.eggs.new', ['nests' => $nests]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle request to store a new Egg.
|
|
||||||
*
|
|
||||||
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
|
|
||||||
* @throws \Pterodactyl\Exceptions\Service\Egg\NoParentConfigurationFoundException
|
|
||||||
*/
|
|
||||||
public function store(EggFormRequest $request): RedirectResponse
|
|
||||||
{
|
|
||||||
$data = $request->validated();
|
|
||||||
$data['docker_images'] = $this->normalizeDockerImages($data['docker_images'] ?? null);
|
|
||||||
|
|
||||||
$egg = $this->creationService->handle($data);
|
|
||||||
$this->alert->success(trans('admin/nests.eggs.notices.egg_created'))->flash();
|
|
||||||
|
|
||||||
return redirect()->route('admin.nests.egg.view', $egg->id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle request to view a single Egg.
|
|
||||||
*/
|
|
||||||
public function view(Egg $egg): View
|
|
||||||
{
|
|
||||||
return $this->view->make('admin.eggs.view', [
|
|
||||||
'egg' => $egg,
|
|
||||||
'images' => array_map(
|
|
||||||
fn ($key, $value) => $key === $value ? $value : "$key|$value",
|
|
||||||
array_keys($egg->docker_images),
|
|
||||||
$egg->docker_images,
|
|
||||||
),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle request to update an Egg.
|
|
||||||
*
|
|
||||||
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
|
|
||||||
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
|
||||||
* @throws \Pterodactyl\Exceptions\Service\Egg\NoParentConfigurationFoundException
|
|
||||||
*/
|
|
||||||
public function update(EggFormRequest $request, Egg $egg): RedirectResponse
|
|
||||||
{
|
|
||||||
$data = $request->validated();
|
|
||||||
$data['docker_images'] = $this->normalizeDockerImages($data['docker_images'] ?? null);
|
|
||||||
|
|
||||||
$this->updateService->handle($egg, $data);
|
|
||||||
$this->alert->success(trans('admin/nests.eggs.notices.updated'))->flash();
|
|
||||||
|
|
||||||
return redirect()->route('admin.nests.egg.view', $egg->id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle request to destroy an egg.
|
|
||||||
*
|
|
||||||
* @throws \Pterodactyl\Exceptions\Service\Egg\HasChildrenException
|
|
||||||
* @throws \Pterodactyl\Exceptions\Service\HasActiveServersException
|
|
||||||
*/
|
|
||||||
public function destroy(Egg $egg): RedirectResponse
|
|
||||||
{
|
|
||||||
$this->deletionService->handle($egg->id);
|
|
||||||
$this->alert->success(trans('admin/nests.eggs.notices.deleted'))->flash();
|
|
||||||
|
|
||||||
return redirect()->route('admin.nests.view', $egg->nest_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Normalizes a string of docker image data into the expected egg format.
|
|
||||||
*/
|
|
||||||
protected function normalizeDockerImages(string $input = null): array
|
|
||||||
{
|
|
||||||
$data = array_map(fn ($value) => trim($value), explode("\n", $input ?? ''));
|
|
||||||
|
|
||||||
$images = [];
|
|
||||||
// Iterate over the image data provided and convert it into a name => image
|
|
||||||
// pairing that is used to improve the display on the front-end.
|
|
||||||
foreach ($data as $value) {
|
|
||||||
$parts = explode('|', $value, 2);
|
|
||||||
$images[$parts[0]] = empty($parts[1]) ? $parts[0] : $parts[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
return $images;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,65 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Pterodactyl\Http\Controllers\Admin\Nests;
|
|
||||||
|
|
||||||
use Illuminate\View\View;
|
|
||||||
use Pterodactyl\Models\Egg;
|
|
||||||
use Illuminate\Http\RedirectResponse;
|
|
||||||
use Prologue\Alerts\AlertsMessageBag;
|
|
||||||
use Illuminate\View\Factory as ViewFactory;
|
|
||||||
use Pterodactyl\Http\Controllers\Controller;
|
|
||||||
use Pterodactyl\Services\Eggs\Scripts\InstallScriptService;
|
|
||||||
use Pterodactyl\Contracts\Repository\EggRepositoryInterface;
|
|
||||||
use Pterodactyl\Http\Requests\Admin\Egg\EggScriptFormRequest;
|
|
||||||
|
|
||||||
class EggScriptController extends Controller
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* EggScriptController constructor.
|
|
||||||
*/
|
|
||||||
public function __construct(
|
|
||||||
protected AlertsMessageBag $alert,
|
|
||||||
protected EggRepositoryInterface $repository,
|
|
||||||
protected InstallScriptService $installScriptService,
|
|
||||||
protected ViewFactory $view
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle requests to render installation script for an Egg.
|
|
||||||
*/
|
|
||||||
public function index(int $egg): View
|
|
||||||
{
|
|
||||||
$egg = $this->repository->getWithCopyAttributes($egg);
|
|
||||||
$copy = $this->repository->findWhere([
|
|
||||||
['copy_script_from', '=', null],
|
|
||||||
['nest_id', '=', $egg->nest_id],
|
|
||||||
['id', '!=', $egg],
|
|
||||||
]);
|
|
||||||
|
|
||||||
$rely = $this->repository->findWhere([
|
|
||||||
['copy_script_from', '=', $egg->id],
|
|
||||||
]);
|
|
||||||
|
|
||||||
return $this->view->make('admin.eggs.scripts', [
|
|
||||||
'copyFromOptions' => $copy,
|
|
||||||
'relyOnScript' => $rely,
|
|
||||||
'egg' => $egg,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle a request to update the installation script for an Egg.
|
|
||||||
*
|
|
||||||
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
|
|
||||||
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
|
||||||
* @throws \Pterodactyl\Exceptions\Service\Egg\InvalidCopyFromException
|
|
||||||
*/
|
|
||||||
public function update(EggScriptFormRequest $request, Egg $egg): RedirectResponse
|
|
||||||
{
|
|
||||||
$this->installScriptService->handle($egg, $request->normalize());
|
|
||||||
$this->alert->success(trans('admin/nests.eggs.notices.script_updated'))->flash();
|
|
||||||
|
|
||||||
return redirect()->route('admin.nests.egg.scripts', $egg);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,74 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Pterodactyl\Http\Controllers\Admin\Nests;
|
|
||||||
|
|
||||||
use Pterodactyl\Models\Egg;
|
|
||||||
use Illuminate\Http\RedirectResponse;
|
|
||||||
use Prologue\Alerts\AlertsMessageBag;
|
|
||||||
use Pterodactyl\Http\Controllers\Controller;
|
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
|
||||||
use Pterodactyl\Services\Eggs\Sharing\EggExporterService;
|
|
||||||
use Pterodactyl\Services\Eggs\Sharing\EggImporterService;
|
|
||||||
use Pterodactyl\Http\Requests\Admin\Egg\EggImportFormRequest;
|
|
||||||
use Pterodactyl\Services\Eggs\Sharing\EggUpdateImporterService;
|
|
||||||
|
|
||||||
class EggShareController extends Controller
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* EggShareController constructor.
|
|
||||||
*/
|
|
||||||
public function __construct(
|
|
||||||
protected AlertsMessageBag $alert,
|
|
||||||
protected EggExporterService $exporterService,
|
|
||||||
protected EggImporterService $importerService,
|
|
||||||
protected EggUpdateImporterService $updateImporterService
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
|
||||||
*/
|
|
||||||
public function export(Egg $egg): Response
|
|
||||||
{
|
|
||||||
$filename = trim(preg_replace('/\W/', '-', kebab_case($egg->name)), '-');
|
|
||||||
|
|
||||||
return response($this->exporterService->handle($egg->id), 200, [
|
|
||||||
'Content-Transfer-Encoding' => 'binary',
|
|
||||||
'Content-Description' => 'File Transfer',
|
|
||||||
'Content-Disposition' => 'attachment; filename=egg-' . $filename . '.json',
|
|
||||||
'Content-Type' => 'application/json',
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Import a new service option using an XML file.
|
|
||||||
*
|
|
||||||
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
|
|
||||||
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
|
||||||
* @throws \Pterodactyl\Exceptions\Service\Egg\BadJsonFormatException
|
|
||||||
* @throws \Pterodactyl\Exceptions\Service\InvalidFileUploadException
|
|
||||||
*/
|
|
||||||
public function import(EggImportFormRequest $request): RedirectResponse
|
|
||||||
{
|
|
||||||
$egg = $this->importerService->handle($request->file('import_file'), $request->input('import_to_nest'));
|
|
||||||
$this->alert->success(trans('admin/nests.eggs.notices.imported'))->flash();
|
|
||||||
|
|
||||||
return redirect()->route('admin.nests.egg.view', ['egg' => $egg->id]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update an existing Egg using a new imported file.
|
|
||||||
*
|
|
||||||
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
|
|
||||||
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
|
||||||
* @throws \Pterodactyl\Exceptions\Service\Egg\BadJsonFormatException
|
|
||||||
* @throws \Pterodactyl\Exceptions\Service\InvalidFileUploadException
|
|
||||||
*/
|
|
||||||
public function update(EggImportFormRequest $request, Egg $egg): RedirectResponse
|
|
||||||
{
|
|
||||||
$this->updateImporterService->handle($egg, $request->file('import_file'));
|
|
||||||
$this->alert->success(trans('admin/nests.eggs.notices.updated_via_import'))->flash();
|
|
||||||
|
|
||||||
return redirect()->route('admin.nests.egg.view', ['egg' => $egg]);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,90 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Pterodactyl\Http\Controllers\Admin\Nests;
|
|
||||||
|
|
||||||
use Illuminate\View\View;
|
|
||||||
use Pterodactyl\Models\Egg;
|
|
||||||
use Pterodactyl\Models\EggVariable;
|
|
||||||
use Illuminate\Http\RedirectResponse;
|
|
||||||
use Prologue\Alerts\AlertsMessageBag;
|
|
||||||
use Illuminate\View\Factory as ViewFactory;
|
|
||||||
use Pterodactyl\Http\Controllers\Controller;
|
|
||||||
use Pterodactyl\Contracts\Repository\EggRepositoryInterface;
|
|
||||||
use Pterodactyl\Services\Eggs\Variables\VariableUpdateService;
|
|
||||||
use Pterodactyl\Http\Requests\Admin\Egg\EggVariableFormRequest;
|
|
||||||
use Pterodactyl\Services\Eggs\Variables\VariableCreationService;
|
|
||||||
use Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface;
|
|
||||||
|
|
||||||
class EggVariableController extends Controller
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* EggVariableController constructor.
|
|
||||||
*/
|
|
||||||
public function __construct(
|
|
||||||
protected AlertsMessageBag $alert,
|
|
||||||
protected VariableCreationService $creationService,
|
|
||||||
protected VariableUpdateService $updateService,
|
|
||||||
protected EggRepositoryInterface $repository,
|
|
||||||
protected EggVariableRepositoryInterface $variableRepository,
|
|
||||||
protected ViewFactory $view
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle request to view the variables attached to an Egg.
|
|
||||||
*
|
|
||||||
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
|
||||||
*/
|
|
||||||
public function view(int $egg): View
|
|
||||||
{
|
|
||||||
$egg = $this->repository->getWithVariables($egg);
|
|
||||||
|
|
||||||
return $this->view->make('admin.eggs.variables', ['egg' => $egg]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle a request to create a new Egg variable.
|
|
||||||
*
|
|
||||||
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
|
|
||||||
* @throws \Pterodactyl\Exceptions\Service\Egg\Variable\BadValidationRuleException
|
|
||||||
* @throws \Pterodactyl\Exceptions\Service\Egg\Variable\ReservedVariableNameException
|
|
||||||
*/
|
|
||||||
public function store(EggVariableFormRequest $request, Egg $egg): RedirectResponse
|
|
||||||
{
|
|
||||||
$this->creationService->handle($egg->id, $request->normalize());
|
|
||||||
$this->alert->success(trans('admin/nests.variables.notices.variable_created'))->flash();
|
|
||||||
|
|
||||||
return redirect()->route('admin.nests.egg.variables', $egg->id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle a request to update an existing Egg variable.
|
|
||||||
*
|
|
||||||
* @throws \Pterodactyl\Exceptions\DisplayException
|
|
||||||
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
|
|
||||||
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
|
||||||
* @throws \Pterodactyl\Exceptions\Service\Egg\Variable\ReservedVariableNameException
|
|
||||||
*/
|
|
||||||
public function update(EggVariableFormRequest $request, Egg $egg, EggVariable $variable): RedirectResponse
|
|
||||||
{
|
|
||||||
$this->updateService->handle($variable, $request->normalize());
|
|
||||||
$this->alert->success(trans('admin/nests.variables.notices.variable_updated', [
|
|
||||||
'variable' => $variable->name,
|
|
||||||
]))->flash();
|
|
||||||
|
|
||||||
return redirect()->route('admin.nests.egg.variables', $egg->id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle a request to delete an existing Egg variable from the Panel.
|
|
||||||
*/
|
|
||||||
public function destroy(int $egg, EggVariable $variable): RedirectResponse
|
|
||||||
{
|
|
||||||
$this->variableRepository->delete($variable->id);
|
|
||||||
$this->alert->success(trans('admin/nests.variables.notices.variable_deleted', [
|
|
||||||
'variable' => $variable->name,
|
|
||||||
]))->flash();
|
|
||||||
|
|
||||||
return redirect()->route('admin.nests.egg.variables', $egg);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,102 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Pterodactyl\Http\Controllers\Admin\Nests;
|
|
||||||
|
|
||||||
use Illuminate\View\View;
|
|
||||||
use Illuminate\Http\RedirectResponse;
|
|
||||||
use Prologue\Alerts\AlertsMessageBag;
|
|
||||||
use Illuminate\View\Factory as ViewFactory;
|
|
||||||
use Pterodactyl\Http\Controllers\Controller;
|
|
||||||
use Pterodactyl\Services\Nests\NestUpdateService;
|
|
||||||
use Pterodactyl\Services\Nests\NestCreationService;
|
|
||||||
use Pterodactyl\Services\Nests\NestDeletionService;
|
|
||||||
use Pterodactyl\Contracts\Repository\NestRepositoryInterface;
|
|
||||||
use Pterodactyl\Http\Requests\Admin\Nest\StoreNestFormRequest;
|
|
||||||
|
|
||||||
class NestController extends Controller
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* NestController constructor.
|
|
||||||
*/
|
|
||||||
public function __construct(
|
|
||||||
protected AlertsMessageBag $alert,
|
|
||||||
protected NestCreationService $nestCreationService,
|
|
||||||
protected NestDeletionService $nestDeletionService,
|
|
||||||
protected NestRepositoryInterface $repository,
|
|
||||||
protected NestUpdateService $nestUpdateService,
|
|
||||||
protected ViewFactory $view
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Render nest listing page.
|
|
||||||
*
|
|
||||||
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
|
||||||
*/
|
|
||||||
public function index(): View
|
|
||||||
{
|
|
||||||
return $this->view->make('admin.nests.index', [
|
|
||||||
'nests' => $this->repository->getWithCounts(),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Render nest creation page.
|
|
||||||
*/
|
|
||||||
public function create(): View
|
|
||||||
{
|
|
||||||
return $this->view->make('admin.nests.new');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle the storage of a new nest.
|
|
||||||
*
|
|
||||||
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
|
|
||||||
*/
|
|
||||||
public function store(StoreNestFormRequest $request): RedirectResponse
|
|
||||||
{
|
|
||||||
$nest = $this->nestCreationService->handle($request->normalize());
|
|
||||||
$this->alert->success(trans('admin/nests.notices.created', ['name' => $nest->name]))->flash();
|
|
||||||
|
|
||||||
return redirect()->route('admin.nests.view', $nest->id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return details about a nest including all the eggs and servers per egg.
|
|
||||||
*
|
|
||||||
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
|
||||||
*/
|
|
||||||
public function view(int $nest): View
|
|
||||||
{
|
|
||||||
return $this->view->make('admin.nests.view', [
|
|
||||||
'nest' => $this->repository->getWithEggServers($nest),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle request to update a nest.
|
|
||||||
*
|
|
||||||
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
|
|
||||||
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
|
||||||
*/
|
|
||||||
public function update(StoreNestFormRequest $request, int $nest): RedirectResponse
|
|
||||||
{
|
|
||||||
$this->nestUpdateService->handle($nest, $request->normalize());
|
|
||||||
$this->alert->success(trans('admin/nests.notices.updated'))->flash();
|
|
||||||
|
|
||||||
return redirect()->route('admin.nests.view', $nest);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle request to delete a nest.
|
|
||||||
*
|
|
||||||
* @throws \Pterodactyl\Exceptions\Service\HasActiveServersException
|
|
||||||
*/
|
|
||||||
public function destroy(int $nest): RedirectResponse
|
|
||||||
{
|
|
||||||
$this->nestDeletionService->handle($nest);
|
|
||||||
$this->alert->success(trans('admin/nests.notices.deleted'))->flash();
|
|
||||||
|
|
||||||
return redirect()->route('admin.nests');
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,62 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Pterodactyl\Http\Controllers\Admin;
|
|
||||||
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Pterodactyl\Models\Node;
|
|
||||||
use Pterodactyl\Models\ApiKey;
|
|
||||||
use Illuminate\Http\JsonResponse;
|
|
||||||
use Pterodactyl\Http\Controllers\Controller;
|
|
||||||
use Illuminate\Contracts\Encryption\Encrypter;
|
|
||||||
use Pterodactyl\Services\Api\KeyCreationService;
|
|
||||||
use Pterodactyl\Repositories\Eloquent\ApiKeyRepository;
|
|
||||||
|
|
||||||
class NodeAutoDeployController extends Controller
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* NodeAutoDeployController constructor.
|
|
||||||
*/
|
|
||||||
public function __construct(
|
|
||||||
private ApiKeyRepository $repository,
|
|
||||||
private Encrypter $encrypter,
|
|
||||||
private KeyCreationService $keyCreationService
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates a new API key for the logged-in user with only permission to read
|
|
||||||
* nodes, and returns that as the deployment key for a node.
|
|
||||||
*
|
|
||||||
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
|
|
||||||
*/
|
|
||||||
public function __invoke(Request $request, Node $node): JsonResponse
|
|
||||||
{
|
|
||||||
/** @var \Pterodactyl\Models\ApiKey|null $key */
|
|
||||||
$key = $this->repository->getApplicationKeys($request->user())
|
|
||||||
->filter(function (ApiKey $key) {
|
|
||||||
foreach ($key->getAttributes() as $permission => $value) {
|
|
||||||
if ($permission === 'r_nodes' && $value === 1) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
})
|
|
||||||
->first();
|
|
||||||
|
|
||||||
// We couldn't find a key that exists for this user with only permission for
|
|
||||||
// reading nodes. Go ahead and create it now.
|
|
||||||
if (!$key) {
|
|
||||||
$key = $this->keyCreationService->setKeyType(ApiKey::TYPE_APPLICATION)->handle([
|
|
||||||
'user_id' => $request->user()->id,
|
|
||||||
'memo' => 'Automatically generated node deployment key.',
|
|
||||||
'allowed_ips' => [],
|
|
||||||
], ['r_nodes' => 1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new JsonResponse([
|
|
||||||
'node' => $node->id,
|
|
||||||
'token' => $key->identifier . $this->encrypter->decrypt($key->token),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,35 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Pterodactyl\Http\Controllers\Admin\Nodes;
|
|
||||||
|
|
||||||
use Illuminate\View\View;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Pterodactyl\Models\Node;
|
|
||||||
use Spatie\QueryBuilder\QueryBuilder;
|
|
||||||
use Pterodactyl\Http\Controllers\Controller;
|
|
||||||
use Illuminate\Contracts\View\Factory as ViewFactory;
|
|
||||||
|
|
||||||
class NodeController extends Controller
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* NodeController constructor.
|
|
||||||
*/
|
|
||||||
public function __construct(private ViewFactory $view)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a listing of nodes on the system.
|
|
||||||
*/
|
|
||||||
public function index(Request $request): View
|
|
||||||
{
|
|
||||||
$nodes = QueryBuilder::for(
|
|
||||||
Node::query()->with('location')->withCount('servers')
|
|
||||||
)
|
|
||||||
->allowedFilters(['uuid', 'name'])
|
|
||||||
->allowedSorts(['id'])
|
|
||||||
->paginate(25);
|
|
||||||
|
|
||||||
return $this->view->make('admin.nodes.index', ['nodes' => $nodes]);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,102 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Pterodactyl\Http\Controllers\Admin\Nodes;
|
|
||||||
|
|
||||||
use Illuminate\View\View;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Pterodactyl\Models\Node;
|
|
||||||
use Illuminate\Support\Collection;
|
|
||||||
use Pterodactyl\Models\Allocation;
|
|
||||||
use Pterodactyl\Http\Controllers\Controller;
|
|
||||||
use Illuminate\Contracts\View\Factory as ViewFactory;
|
|
||||||
use Pterodactyl\Repositories\Eloquent\NodeRepository;
|
|
||||||
use Pterodactyl\Repositories\Eloquent\ServerRepository;
|
|
||||||
use Pterodactyl\Traits\Controllers\JavascriptInjection;
|
|
||||||
use Pterodactyl\Services\Helpers\SoftwareVersionService;
|
|
||||||
use Pterodactyl\Repositories\Eloquent\LocationRepository;
|
|
||||||
use Pterodactyl\Repositories\Eloquent\AllocationRepository;
|
|
||||||
|
|
||||||
class NodeViewController extends Controller
|
|
||||||
{
|
|
||||||
use JavascriptInjection;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* NodeViewController constructor.
|
|
||||||
*/
|
|
||||||
public function __construct(
|
|
||||||
private AllocationRepository $allocationRepository,
|
|
||||||
private LocationRepository $locationRepository,
|
|
||||||
private NodeRepository $repository,
|
|
||||||
private ServerRepository $serverRepository,
|
|
||||||
private SoftwareVersionService $versionService,
|
|
||||||
private ViewFactory $view
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns index view for a specific node on the system.
|
|
||||||
*/
|
|
||||||
public function index(Request $request, Node $node): View
|
|
||||||
{
|
|
||||||
$node = $this->repository->loadLocationAndServerCount($node);
|
|
||||||
|
|
||||||
return $this->view->make('admin.nodes.view.index', [
|
|
||||||
'node' => $node,
|
|
||||||
'stats' => $this->repository->getUsageStats($node),
|
|
||||||
'version' => $this->versionService,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the settings page for a specific node.
|
|
||||||
*/
|
|
||||||
public function settings(Request $request, Node $node): View
|
|
||||||
{
|
|
||||||
return $this->view->make('admin.nodes.view.settings', [
|
|
||||||
'node' => $node,
|
|
||||||
'locations' => $this->locationRepository->all(),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the node configuration page for a specific node.
|
|
||||||
*/
|
|
||||||
public function configuration(Request $request, Node $node): View
|
|
||||||
{
|
|
||||||
return $this->view->make('admin.nodes.view.configuration', compact('node'));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the node allocation management page.
|
|
||||||
*/
|
|
||||||
public function allocations(Request $request, Node $node): View
|
|
||||||
{
|
|
||||||
$node = $this->repository->loadNodeAllocations($node);
|
|
||||||
|
|
||||||
$this->plainInject(['node' => Collection::wrap($node)->only(['id'])]);
|
|
||||||
|
|
||||||
return $this->view->make('admin.nodes.view.allocation', [
|
|
||||||
'node' => $node,
|
|
||||||
'allocations' => Allocation::query()->where('node_id', $node->id)
|
|
||||||
->groupBy('ip')
|
|
||||||
->orderByRaw('INET_ATON(ip) ASC')
|
|
||||||
->get(['ip']),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return a listing of servers that exist for this specific node.
|
|
||||||
*/
|
|
||||||
public function servers(Request $request, Node $node): View
|
|
||||||
{
|
|
||||||
$this->plainInject([
|
|
||||||
'node' => Collection::wrap($node->makeVisible(['daemon_token_id', 'daemon_token']))
|
|
||||||
->only(['scheme', 'fqdn', 'daemonListen', 'daemon_token_id', 'daemon_token']),
|
|
||||||
]);
|
|
||||||
|
|
||||||
return $this->view->make('admin.nodes.view.servers', [
|
|
||||||
'node' => $node,
|
|
||||||
'servers' => $this->serverRepository->loadAllServersForNode($node->id, 25),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,40 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Pterodactyl\Http\Controllers\Admin\Nodes;
|
|
||||||
|
|
||||||
use Illuminate\Support\Str;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Pterodactyl\Models\Node;
|
|
||||||
use Illuminate\Http\JsonResponse;
|
|
||||||
use Pterodactyl\Http\Controllers\Controller;
|
|
||||||
use Pterodactyl\Repositories\Wings\DaemonConfigurationRepository;
|
|
||||||
|
|
||||||
class SystemInformationController extends Controller
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* SystemInformationController constructor.
|
|
||||||
*/
|
|
||||||
public function __construct(private DaemonConfigurationRepository $repository)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns system information from the Daemon.
|
|
||||||
*
|
|
||||||
* @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException
|
|
||||||
*/
|
|
||||||
public function __invoke(Request $request, Node $node): JsonResponse
|
|
||||||
{
|
|
||||||
$data = $this->repository->setNode($node)->getSystemInformation();
|
|
||||||
|
|
||||||
return new JsonResponse([
|
|
||||||
'version' => $data['version'] ?? '',
|
|
||||||
'system' => [
|
|
||||||
'type' => Str::title($data['os'] ?? 'Unknown'),
|
|
||||||
'arch' => $data['architecture'] ?? '--',
|
|
||||||
'release' => $data['kernel_version'] ?? '--',
|
|
||||||
'cpus' => $data['cpu_count'] ?? 0,
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,183 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Pterodactyl\Http\Controllers\Admin;
|
|
||||||
|
|
||||||
use Illuminate\View\View;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Pterodactyl\Models\Node;
|
|
||||||
use Illuminate\Http\Response;
|
|
||||||
use Pterodactyl\Models\Allocation;
|
|
||||||
use Illuminate\Http\RedirectResponse;
|
|
||||||
use Prologue\Alerts\AlertsMessageBag;
|
|
||||||
use Illuminate\View\Factory as ViewFactory;
|
|
||||||
use Pterodactyl\Http\Controllers\Controller;
|
|
||||||
use Pterodactyl\Services\Nodes\NodeUpdateService;
|
|
||||||
use Illuminate\Cache\Repository as CacheRepository;
|
|
||||||
use Pterodactyl\Services\Nodes\NodeCreationService;
|
|
||||||
use Pterodactyl\Services\Nodes\NodeDeletionService;
|
|
||||||
use Pterodactyl\Services\Allocations\AssignmentService;
|
|
||||||
use Pterodactyl\Services\Helpers\SoftwareVersionService;
|
|
||||||
use Pterodactyl\Http\Requests\Admin\Node\NodeFormRequest;
|
|
||||||
use Pterodactyl\Contracts\Repository\NodeRepositoryInterface;
|
|
||||||
use Pterodactyl\Contracts\Repository\ServerRepositoryInterface;
|
|
||||||
use Pterodactyl\Http\Requests\Admin\Node\AllocationFormRequest;
|
|
||||||
use Pterodactyl\Services\Allocations\AllocationDeletionService;
|
|
||||||
use Pterodactyl\Contracts\Repository\LocationRepositoryInterface;
|
|
||||||
use Pterodactyl\Contracts\Repository\AllocationRepositoryInterface;
|
|
||||||
use Pterodactyl\Http\Requests\Admin\Node\AllocationAliasFormRequest;
|
|
||||||
|
|
||||||
class NodesController extends Controller
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* NodesController constructor.
|
|
||||||
*/
|
|
||||||
public function __construct(
|
|
||||||
protected AlertsMessageBag $alert,
|
|
||||||
protected AllocationDeletionService $allocationDeletionService,
|
|
||||||
protected AllocationRepositoryInterface $allocationRepository,
|
|
||||||
protected AssignmentService $assignmentService,
|
|
||||||
protected CacheRepository $cache,
|
|
||||||
protected NodeCreationService $creationService,
|
|
||||||
protected NodeDeletionService $deletionService,
|
|
||||||
protected LocationRepositoryInterface $locationRepository,
|
|
||||||
protected NodeRepositoryInterface $repository,
|
|
||||||
protected ServerRepositoryInterface $serverRepository,
|
|
||||||
protected NodeUpdateService $updateService,
|
|
||||||
protected SoftwareVersionService $versionService,
|
|
||||||
protected ViewFactory $view
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Displays create new node page.
|
|
||||||
*/
|
|
||||||
public function create(): View|RedirectResponse
|
|
||||||
{
|
|
||||||
$locations = $this->locationRepository->all();
|
|
||||||
if (count($locations) < 1) {
|
|
||||||
$this->alert->warning(trans('admin/node.notices.location_required'))->flash();
|
|
||||||
|
|
||||||
return redirect()->route('admin.locations');
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->view->make('admin.nodes.new', ['locations' => $locations]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Post controller to create a new node on the system.
|
|
||||||
*
|
|
||||||
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
|
|
||||||
*/
|
|
||||||
public function store(NodeFormRequest $request): RedirectResponse
|
|
||||||
{
|
|
||||||
$node = $this->creationService->handle($request->normalize());
|
|
||||||
$this->alert->info(trans('admin/node.notices.node_created'))->flash();
|
|
||||||
|
|
||||||
return redirect()->route('admin.nodes.view.allocation', $node->id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates settings for a node.
|
|
||||||
*
|
|
||||||
* @throws \Pterodactyl\Exceptions\DisplayException
|
|
||||||
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
|
|
||||||
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
|
||||||
*/
|
|
||||||
public function updateSettings(NodeFormRequest $request, Node $node): RedirectResponse
|
|
||||||
{
|
|
||||||
$this->updateService->handle($node, $request->normalize(), $request->input('reset_secret') === 'on');
|
|
||||||
$this->alert->success(trans('admin/node.notices.node_updated'))->flash();
|
|
||||||
|
|
||||||
return redirect()->route('admin.nodes.view.settings', $node->id)->withInput();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes a single allocation from a node.
|
|
||||||
*
|
|
||||||
* @throws \Pterodactyl\Exceptions\Service\Allocation\ServerUsingAllocationException
|
|
||||||
*/
|
|
||||||
public function allocationRemoveSingle(int $node, Allocation $allocation): Response
|
|
||||||
{
|
|
||||||
$this->allocationDeletionService->handle($allocation);
|
|
||||||
|
|
||||||
return response('', 204);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes multiple individual allocations from a node.
|
|
||||||
*
|
|
||||||
* @throws \Pterodactyl\Exceptions\Service\Allocation\ServerUsingAllocationException
|
|
||||||
*/
|
|
||||||
public function allocationRemoveMultiple(Request $request, int $node): Response
|
|
||||||
{
|
|
||||||
$allocations = $request->input('allocations');
|
|
||||||
foreach ($allocations as $rawAllocation) {
|
|
||||||
$allocation = new Allocation();
|
|
||||||
$allocation->id = $rawAllocation['id'];
|
|
||||||
$this->allocationRemoveSingle($node, $allocation);
|
|
||||||
}
|
|
||||||
|
|
||||||
return response('', 204);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove all allocations for a specific IP at once on a node.
|
|
||||||
*/
|
|
||||||
public function allocationRemoveBlock(Request $request, int $node): RedirectResponse
|
|
||||||
{
|
|
||||||
$this->allocationRepository->deleteWhere([
|
|
||||||
['node_id', '=', $node],
|
|
||||||
['server_id', '=', null],
|
|
||||||
['ip', '=', $request->input('ip')],
|
|
||||||
]);
|
|
||||||
|
|
||||||
$this->alert->success(trans('admin/node.notices.unallocated_deleted', ['ip' => $request->input('ip')]))
|
|
||||||
->flash();
|
|
||||||
|
|
||||||
return redirect()->route('admin.nodes.view.allocation', $node);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets an alias for a specific allocation on a node.
|
|
||||||
*
|
|
||||||
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
|
|
||||||
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
|
||||||
*/
|
|
||||||
public function allocationSetAlias(AllocationAliasFormRequest $request): \Symfony\Component\HttpFoundation\Response
|
|
||||||
{
|
|
||||||
$this->allocationRepository->update($request->input('allocation_id'), [
|
|
||||||
'ip_alias' => (empty($request->input('alias'))) ? null : $request->input('alias'),
|
|
||||||
]);
|
|
||||||
|
|
||||||
return response('', 204);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates new allocations on a node.
|
|
||||||
*
|
|
||||||
* @throws \Pterodactyl\Exceptions\Service\Allocation\CidrOutOfRangeException
|
|
||||||
* @throws \Pterodactyl\Exceptions\Service\Allocation\InvalidPortMappingException
|
|
||||||
* @throws \Pterodactyl\Exceptions\Service\Allocation\PortOutOfRangeException
|
|
||||||
* @throws \Pterodactyl\Exceptions\Service\Allocation\TooManyPortsInRangeException
|
|
||||||
*/
|
|
||||||
public function createAllocation(AllocationFormRequest $request, Node $node): RedirectResponse
|
|
||||||
{
|
|
||||||
$this->assignmentService->handle($node, $request->normalize());
|
|
||||||
$this->alert->success(trans('admin/node.notices.allocations_added'))->flash();
|
|
||||||
|
|
||||||
return redirect()->route('admin.nodes.view.allocation', $node->id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes a node from the system.
|
|
||||||
*
|
|
||||||
* @throws \Pterodactyl\Exceptions\DisplayException
|
|
||||||
*/
|
|
||||||
public function delete(int|Node $node): RedirectResponse
|
|
||||||
{
|
|
||||||
$this->deletionService->handle($node);
|
|
||||||
$this->alert->success(trans('admin/node.notices.node_deleted'))->flash();
|
|
||||||
|
|
||||||
return redirect()->route('admin.nodes');
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,86 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Pterodactyl\Http\Controllers\Admin\Servers;
|
|
||||||
|
|
||||||
use JavaScript;
|
|
||||||
use Illuminate\View\View;
|
|
||||||
use Pterodactyl\Models\Node;
|
|
||||||
use Pterodactyl\Models\Location;
|
|
||||||
use Illuminate\Http\RedirectResponse;
|
|
||||||
use Prologue\Alerts\AlertsMessageBag;
|
|
||||||
use Illuminate\View\Factory as ViewFactory;
|
|
||||||
use Pterodactyl\Http\Controllers\Controller;
|
|
||||||
use Pterodactyl\Repositories\Eloquent\NestRepository;
|
|
||||||
use Pterodactyl\Repositories\Eloquent\NodeRepository;
|
|
||||||
use Pterodactyl\Http\Requests\Admin\ServerFormRequest;
|
|
||||||
use Pterodactyl\Services\Servers\ServerCreationService;
|
|
||||||
|
|
||||||
class CreateServerController extends Controller
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* CreateServerController constructor.
|
|
||||||
*/
|
|
||||||
public function __construct(
|
|
||||||
private AlertsMessageBag $alert,
|
|
||||||
private NestRepository $nestRepository,
|
|
||||||
private NodeRepository $nodeRepository,
|
|
||||||
private ServerCreationService $creationService,
|
|
||||||
private ViewFactory $view
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Displays the create server page.
|
|
||||||
*
|
|
||||||
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
|
||||||
*/
|
|
||||||
public function index(): View|RedirectResponse
|
|
||||||
{
|
|
||||||
$nodes = Node::all();
|
|
||||||
if (count($nodes) < 1) {
|
|
||||||
$this->alert->warning(trans('admin/server.alerts.node_required'))->flash();
|
|
||||||
|
|
||||||
return redirect()->route('admin.nodes');
|
|
||||||
}
|
|
||||||
|
|
||||||
$nests = $this->nestRepository->getWithEggs();
|
|
||||||
|
|
||||||
JavaScript::put([
|
|
||||||
'nodeData' => $this->nodeRepository->getNodesForServerCreation(),
|
|
||||||
'nests' => $nests->map(function ($item) {
|
|
||||||
return array_merge($item->toArray(), [
|
|
||||||
'eggs' => $item->eggs->keyBy('id')->toArray(),
|
|
||||||
]);
|
|
||||||
})->keyBy('id'),
|
|
||||||
]);
|
|
||||||
|
|
||||||
return $this->view->make('admin.servers.new', [
|
|
||||||
'locations' => Location::all(),
|
|
||||||
'nests' => $nests,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new server on the remote system.
|
|
||||||
*
|
|
||||||
* @throws \Illuminate\Validation\ValidationException
|
|
||||||
* @throws \Pterodactyl\Exceptions\DisplayException
|
|
||||||
* @throws \Pterodactyl\Exceptions\Service\Deployment\NoViableAllocationException
|
|
||||||
* @throws \Pterodactyl\Exceptions\Service\Deployment\NoViableNodeException
|
|
||||||
* @throws \Throwable
|
|
||||||
*/
|
|
||||||
public function store(ServerFormRequest $request): RedirectResponse
|
|
||||||
{
|
|
||||||
$data = $request->except(['_token']);
|
|
||||||
if (!empty($data['custom_image'])) {
|
|
||||||
$data['image'] = $data['custom_image'];
|
|
||||||
unset($data['custom_image']);
|
|
||||||
}
|
|
||||||
|
|
||||||
$server = $this->creationService->handle($data);
|
|
||||||
|
|
||||||
$this->alert->success(trans('admin/server.alerts.server_created'))->flash();
|
|
||||||
|
|
||||||
return new RedirectResponse('/admin/servers/view/' . $server->id);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,38 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Pterodactyl\Http\Controllers\Admin\Servers;
|
|
||||||
|
|
||||||
use Illuminate\View\View;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Pterodactyl\Models\Server;
|
|
||||||
use Spatie\QueryBuilder\QueryBuilder;
|
|
||||||
use Spatie\QueryBuilder\AllowedFilter;
|
|
||||||
use Pterodactyl\Http\Controllers\Controller;
|
|
||||||
use Pterodactyl\Models\Filters\AdminServerFilter;
|
|
||||||
use Illuminate\Contracts\View\Factory as ViewFactory;
|
|
||||||
|
|
||||||
class ServerController extends Controller
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* ServerController constructor.
|
|
||||||
*/
|
|
||||||
public function __construct(private ViewFactory $view)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns all the servers that exist on the system using a paginated result set. If
|
|
||||||
* a query is passed along in the request it is also passed to the repository function.
|
|
||||||
*/
|
|
||||||
public function index(Request $request): View
|
|
||||||
{
|
|
||||||
$servers = QueryBuilder::for(Server::query()->with('node', 'user', 'allocation'))
|
|
||||||
->allowedFilters([
|
|
||||||
AllowedFilter::exact('owner_id'),
|
|
||||||
AllowedFilter::custom('*', new AdminServerFilter()),
|
|
||||||
])
|
|
||||||
->paginate(config()->get('pterodactyl.paginate.admin.servers'));
|
|
||||||
|
|
||||||
return $this->view->make('admin.servers.index', ['servers' => $servers]);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,105 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Pterodactyl\Http\Controllers\Admin\Servers;
|
|
||||||
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Pterodactyl\Models\Server;
|
|
||||||
use Illuminate\Http\RedirectResponse;
|
|
||||||
use Prologue\Alerts\AlertsMessageBag;
|
|
||||||
use Pterodactyl\Models\ServerTransfer;
|
|
||||||
use Pterodactyl\Http\Controllers\Controller;
|
|
||||||
use Pterodactyl\Services\Servers\TransferService;
|
|
||||||
use Pterodactyl\Repositories\Eloquent\NodeRepository;
|
|
||||||
use Pterodactyl\Repositories\Wings\DaemonConfigurationRepository;
|
|
||||||
use Pterodactyl\Contracts\Repository\AllocationRepositoryInterface;
|
|
||||||
|
|
||||||
class ServerTransferController extends Controller
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* ServerTransferController constructor.
|
|
||||||
*/
|
|
||||||
public function __construct(
|
|
||||||
private AlertsMessageBag $alert,
|
|
||||||
private AllocationRepositoryInterface $allocationRepository,
|
|
||||||
private NodeRepository $nodeRepository,
|
|
||||||
private TransferService $transferService,
|
|
||||||
private DaemonConfigurationRepository $daemonConfigurationRepository
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Starts a transfer of a server to a new node.
|
|
||||||
*
|
|
||||||
* @throws \Throwable
|
|
||||||
*/
|
|
||||||
public function transfer(Request $request, Server $server): RedirectResponse
|
|
||||||
{
|
|
||||||
$validatedData = $request->validate([
|
|
||||||
'node_id' => 'required|exists:nodes,id',
|
|
||||||
'allocation_id' => 'required|bail|unique:servers|exists:allocations,id',
|
|
||||||
'allocation_additional' => 'nullable',
|
|
||||||
]);
|
|
||||||
|
|
||||||
$node_id = $validatedData['node_id'];
|
|
||||||
$allocation_id = intval($validatedData['allocation_id']);
|
|
||||||
$additional_allocations = array_map('intval', $validatedData['allocation_additional'] ?? []);
|
|
||||||
|
|
||||||
// Check if the node is viable for the transfer.
|
|
||||||
$node = $this->nodeRepository->getNodeWithResourceUsage($node_id);
|
|
||||||
if ($node->isViable($server->memory, $server->disk)) {
|
|
||||||
// Check if the selected daemon is online.
|
|
||||||
$this->daemonConfigurationRepository->setNode($node)->getSystemInformation();
|
|
||||||
|
|
||||||
$server->validateTransferState();
|
|
||||||
|
|
||||||
// Create a new ServerTransfer entry.
|
|
||||||
$transfer = new ServerTransfer();
|
|
||||||
|
|
||||||
$transfer->server_id = $server->id;
|
|
||||||
$transfer->old_node = $server->node_id;
|
|
||||||
$transfer->new_node = $node_id;
|
|
||||||
$transfer->old_allocation = $server->allocation_id;
|
|
||||||
$transfer->new_allocation = $allocation_id;
|
|
||||||
$transfer->old_additional_allocations = $server->allocations->where('id', '!=', $server->allocation_id)->pluck('id');
|
|
||||||
$transfer->new_additional_allocations = $additional_allocations;
|
|
||||||
|
|
||||||
$transfer->save();
|
|
||||||
|
|
||||||
// Add the allocations to the server, so they cannot be automatically assigned while the transfer is in progress.
|
|
||||||
$this->assignAllocationsToServer($server, $node_id, $allocation_id, $additional_allocations);
|
|
||||||
|
|
||||||
// Request an archive from the server's current daemon. (this also checks if the daemon is online)
|
|
||||||
$this->transferService->requestArchive($server);
|
|
||||||
|
|
||||||
$this->alert->success(trans('admin/server.alerts.transfer_started'))->flash();
|
|
||||||
} else {
|
|
||||||
$this->alert->danger(trans('admin/server.alerts.transfer_not_viable'))->flash();
|
|
||||||
}
|
|
||||||
|
|
||||||
return redirect()->route('admin.servers.view.manage', $server->id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Assigns the specified allocations to the specified server.
|
|
||||||
*/
|
|
||||||
private function assignAllocationsToServer(Server $server, int $node_id, int $allocation_id, array $additional_allocations)
|
|
||||||
{
|
|
||||||
$allocations = $additional_allocations;
|
|
||||||
$allocations[] = $allocation_id;
|
|
||||||
|
|
||||||
$unassigned = $this->allocationRepository->getUnassignedAllocationIds($node_id);
|
|
||||||
|
|
||||||
$updateIds = [];
|
|
||||||
foreach ($allocations as $allocation) {
|
|
||||||
if (!in_array($allocation, $unassigned)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$updateIds[] = $allocation;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!empty($updateIds)) {
|
|
||||||
$this->allocationRepository->updateWhereIn('id', $updateIds, ['server_id' => $server->id]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,155 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Pterodactyl\Http\Controllers\Admin\Servers;
|
|
||||||
|
|
||||||
use JavaScript;
|
|
||||||
use Illuminate\View\View;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Pterodactyl\Models\Nest;
|
|
||||||
use Pterodactyl\Models\Server;
|
|
||||||
use Pterodactyl\Exceptions\DisplayException;
|
|
||||||
use Pterodactyl\Http\Controllers\Controller;
|
|
||||||
use Pterodactyl\Services\Servers\EnvironmentService;
|
|
||||||
use Illuminate\Contracts\View\Factory as ViewFactory;
|
|
||||||
use Pterodactyl\Repositories\Eloquent\NestRepository;
|
|
||||||
use Pterodactyl\Repositories\Eloquent\NodeRepository;
|
|
||||||
use Pterodactyl\Repositories\Eloquent\MountRepository;
|
|
||||||
use Pterodactyl\Repositories\Eloquent\ServerRepository;
|
|
||||||
use Pterodactyl\Traits\Controllers\JavascriptInjection;
|
|
||||||
use Pterodactyl\Repositories\Eloquent\LocationRepository;
|
|
||||||
use Pterodactyl\Repositories\Eloquent\DatabaseHostRepository;
|
|
||||||
|
|
||||||
class ServerViewController extends Controller
|
|
||||||
{
|
|
||||||
use JavascriptInjection;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ServerViewController constructor.
|
|
||||||
*/
|
|
||||||
public function __construct(
|
|
||||||
private DatabaseHostRepository $databaseHostRepository,
|
|
||||||
private LocationRepository $locationRepository,
|
|
||||||
private MountRepository $mountRepository,
|
|
||||||
private NestRepository $nestRepository,
|
|
||||||
private NodeRepository $nodeRepository,
|
|
||||||
private ServerRepository $repository,
|
|
||||||
private EnvironmentService $environmentService,
|
|
||||||
private ViewFactory $view
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the index view for a server.
|
|
||||||
*/
|
|
||||||
public function index(Request $request, Server $server): View
|
|
||||||
{
|
|
||||||
return $this->view->make('admin.servers.view.index', compact('server'));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the server details page.
|
|
||||||
*/
|
|
||||||
public function details(Request $request, Server $server): View
|
|
||||||
{
|
|
||||||
return $this->view->make('admin.servers.view.details', compact('server'));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a view of server build settings.
|
|
||||||
*/
|
|
||||||
public function build(Request $request, Server $server): View
|
|
||||||
{
|
|
||||||
$allocations = $server->node->allocations->toBase();
|
|
||||||
|
|
||||||
return $this->view->make('admin.servers.view.build', [
|
|
||||||
'server' => $server,
|
|
||||||
'assigned' => $allocations->where('server_id', $server->id)->sortBy('port')->sortBy('ip'),
|
|
||||||
'unassigned' => $allocations->where('server_id', null)->sortBy('port')->sortBy('ip'),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the server startup management page.
|
|
||||||
*
|
|
||||||
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
|
||||||
*/
|
|
||||||
public function startup(Request $request, Server $server): View
|
|
||||||
{
|
|
||||||
$nests = $this->nestRepository->getWithEggs();
|
|
||||||
$variables = $this->environmentService->handle($server);
|
|
||||||
|
|
||||||
$this->plainInject([
|
|
||||||
'server' => $server,
|
|
||||||
'server_variables' => $variables,
|
|
||||||
'nests' => $nests->map(function (Nest $item) {
|
|
||||||
return array_merge($item->toArray(), [
|
|
||||||
'eggs' => $item->eggs->keyBy('id')->toArray(),
|
|
||||||
]);
|
|
||||||
})->keyBy('id'),
|
|
||||||
]);
|
|
||||||
|
|
||||||
return $this->view->make('admin.servers.view.startup', compact('server', 'nests'));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns all the databases that exist for the server.
|
|
||||||
*/
|
|
||||||
public function database(Request $request, Server $server): View
|
|
||||||
{
|
|
||||||
return $this->view->make('admin.servers.view.database', [
|
|
||||||
'hosts' => $this->databaseHostRepository->all(),
|
|
||||||
'server' => $server,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns all the mounts that exist for the server.
|
|
||||||
*/
|
|
||||||
public function mounts(Request $request, Server $server): View
|
|
||||||
{
|
|
||||||
$server->load('mounts');
|
|
||||||
|
|
||||||
return $this->view->make('admin.servers.view.mounts', [
|
|
||||||
'mounts' => $this->mountRepository->getMountListForServer($server),
|
|
||||||
'server' => $server,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the base server management page, or an exception if the server
|
|
||||||
* is in a state that cannot be recovered from.
|
|
||||||
*
|
|
||||||
* @throws \Pterodactyl\Exceptions\DisplayException
|
|
||||||
*/
|
|
||||||
public function manage(Request $request, Server $server): View
|
|
||||||
{
|
|
||||||
if ($server->status === Server::STATUS_INSTALL_FAILED) {
|
|
||||||
throw new DisplayException('This server is in a failed install state and cannot be recovered. Please delete and re-create the server.');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the panel doesn't have at least 2 nodes configured.
|
|
||||||
$nodes = $this->nodeRepository->all();
|
|
||||||
$canTransfer = false;
|
|
||||||
if (count($nodes) >= 2) {
|
|
||||||
$canTransfer = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
JavaScript::put([
|
|
||||||
'nodeData' => $this->nodeRepository->getNodesForServerCreation(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
return $this->view->make('admin.servers.view.manage', [
|
|
||||||
'server' => $server,
|
|
||||||
'locations' => $this->locationRepository->all(),
|
|
||||||
'canTransfer' => $canTransfer,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the server deletion page.
|
|
||||||
*/
|
|
||||||
public function delete(Request $request, Server $server): View
|
|
||||||
{
|
|
||||||
return $this->view->make('admin.servers.view.delete', compact('server'));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,273 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Pterodactyl\Http\Controllers\Admin;
|
|
||||||
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Pterodactyl\Models\User;
|
|
||||||
use Illuminate\Http\Response;
|
|
||||||
use Pterodactyl\Models\Mount;
|
|
||||||
use Pterodactyl\Models\Server;
|
|
||||||
use Pterodactyl\Models\Database;
|
|
||||||
use Pterodactyl\Models\MountServer;
|
|
||||||
use Illuminate\Http\RedirectResponse;
|
|
||||||
use Prologue\Alerts\AlertsMessageBag;
|
|
||||||
use Pterodactyl\Exceptions\DisplayException;
|
|
||||||
use Pterodactyl\Http\Controllers\Controller;
|
|
||||||
use Illuminate\Validation\ValidationException;
|
|
||||||
use Pterodactyl\Services\Servers\SuspensionService;
|
|
||||||
use Pterodactyl\Repositories\Eloquent\MountRepository;
|
|
||||||
use Pterodactyl\Services\Servers\ServerDeletionService;
|
|
||||||
use Pterodactyl\Services\Servers\ReinstallServerService;
|
|
||||||
use Pterodactyl\Exceptions\Model\DataValidationException;
|
|
||||||
use Pterodactyl\Repositories\Wings\DaemonServerRepository;
|
|
||||||
use Pterodactyl\Services\Servers\BuildModificationService;
|
|
||||||
use Pterodactyl\Services\Databases\DatabasePasswordService;
|
|
||||||
use Pterodactyl\Services\Servers\DetailsModificationService;
|
|
||||||
use Pterodactyl\Services\Servers\StartupModificationService;
|
|
||||||
use Pterodactyl\Contracts\Repository\NestRepositoryInterface;
|
|
||||||
use Pterodactyl\Repositories\Eloquent\DatabaseHostRepository;
|
|
||||||
use Pterodactyl\Services\Databases\DatabaseManagementService;
|
|
||||||
use Illuminate\Contracts\Config\Repository as ConfigRepository;
|
|
||||||
use Pterodactyl\Contracts\Repository\ServerRepositoryInterface;
|
|
||||||
use Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface;
|
|
||||||
use Pterodactyl\Contracts\Repository\AllocationRepositoryInterface;
|
|
||||||
use Pterodactyl\Services\Servers\ServerConfigurationStructureService;
|
|
||||||
use Pterodactyl\Http\Requests\Admin\Servers\Databases\StoreServerDatabaseRequest;
|
|
||||||
|
|
||||||
class ServersController extends Controller
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* ServersController constructor.
|
|
||||||
*/
|
|
||||||
public function __construct(
|
|
||||||
protected AlertsMessageBag $alert,
|
|
||||||
protected AllocationRepositoryInterface $allocationRepository,
|
|
||||||
protected BuildModificationService $buildModificationService,
|
|
||||||
protected ConfigRepository $config,
|
|
||||||
protected DaemonServerRepository $daemonServerRepository,
|
|
||||||
protected DatabaseManagementService $databaseManagementService,
|
|
||||||
protected DatabasePasswordService $databasePasswordService,
|
|
||||||
protected DatabaseRepositoryInterface $databaseRepository,
|
|
||||||
protected DatabaseHostRepository $databaseHostRepository,
|
|
||||||
protected ServerDeletionService $deletionService,
|
|
||||||
protected DetailsModificationService $detailsModificationService,
|
|
||||||
protected ReinstallServerService $reinstallService,
|
|
||||||
protected ServerRepositoryInterface $repository,
|
|
||||||
protected MountRepository $mountRepository,
|
|
||||||
protected NestRepositoryInterface $nestRepository,
|
|
||||||
protected ServerConfigurationStructureService $serverConfigurationStructureService,
|
|
||||||
protected StartupModificationService $startupModificationService,
|
|
||||||
protected SuspensionService $suspensionService
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the details for a server.
|
|
||||||
*
|
|
||||||
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
|
|
||||||
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
|
||||||
*/
|
|
||||||
public function setDetails(Request $request, Server $server): RedirectResponse
|
|
||||||
{
|
|
||||||
$this->detailsModificationService->handle($server, $request->only([
|
|
||||||
'owner_id', 'external_id', 'name', 'description',
|
|
||||||
]));
|
|
||||||
|
|
||||||
$this->alert->success(trans('admin/server.alerts.details_updated'))->flash();
|
|
||||||
|
|
||||||
return redirect()->route('admin.servers.view.details', $server->id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Toggles the installation status for a server.
|
|
||||||
*
|
|
||||||
* @throws \Pterodactyl\Exceptions\DisplayException
|
|
||||||
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
|
|
||||||
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
|
||||||
*/
|
|
||||||
public function toggleInstall(Server $server): RedirectResponse
|
|
||||||
{
|
|
||||||
if ($server->status === Server::STATUS_INSTALL_FAILED) {
|
|
||||||
throw new DisplayException(trans('admin/server.exceptions.marked_as_failed'));
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->repository->update($server->id, [
|
|
||||||
'status' => $server->isInstalled() ? Server::STATUS_INSTALLING : null,
|
|
||||||
], true, true);
|
|
||||||
|
|
||||||
$this->alert->success(trans('admin/server.alerts.install_toggled'))->flash();
|
|
||||||
|
|
||||||
return redirect()->route('admin.servers.view.manage', $server->id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reinstalls the server with the currently assigned service.
|
|
||||||
*
|
|
||||||
* @throws \Pterodactyl\Exceptions\DisplayException
|
|
||||||
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
|
|
||||||
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
|
||||||
*/
|
|
||||||
public function reinstallServer(Server $server): RedirectResponse
|
|
||||||
{
|
|
||||||
$this->reinstallService->handle($server);
|
|
||||||
$this->alert->success(trans('admin/server.alerts.server_reinstalled'))->flash();
|
|
||||||
|
|
||||||
return redirect()->route('admin.servers.view.manage', $server->id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Manage the suspension status for a server.
|
|
||||||
*
|
|
||||||
* @throws \Pterodactyl\Exceptions\DisplayException
|
|
||||||
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
|
|
||||||
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
|
||||||
*/
|
|
||||||
public function manageSuspension(Request $request, Server $server): RedirectResponse
|
|
||||||
{
|
|
||||||
$this->suspensionService->toggle($server, $request->input('action'));
|
|
||||||
$this->alert->success(trans('admin/server.alerts.suspension_toggled', [
|
|
||||||
'status' => $request->input('action') . 'ed',
|
|
||||||
]))->flash();
|
|
||||||
|
|
||||||
return redirect()->route('admin.servers.view.manage', $server->id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the build configuration for a server.
|
|
||||||
*
|
|
||||||
* @throws \Pterodactyl\Exceptions\DisplayException
|
|
||||||
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
|
||||||
* @throws \Illuminate\Validation\ValidationException
|
|
||||||
*/
|
|
||||||
public function updateBuild(Request $request, Server $server): RedirectResponse
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$this->buildModificationService->handle($server, $request->only([
|
|
||||||
'allocation_id', 'add_allocations', 'remove_allocations',
|
|
||||||
'memory', 'swap', 'io', 'cpu', 'threads', 'disk',
|
|
||||||
'database_limit', 'allocation_limit', 'backup_limit', 'oom_disabled',
|
|
||||||
]));
|
|
||||||
} catch (DataValidationException $exception) {
|
|
||||||
throw new ValidationException($exception->getValidator());
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->alert->success(trans('admin/server.alerts.build_updated'))->flash();
|
|
||||||
|
|
||||||
return redirect()->route('admin.servers.view.build', $server->id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Start the server deletion process.
|
|
||||||
*
|
|
||||||
* @throws \Pterodactyl\Exceptions\DisplayException
|
|
||||||
* @throws \Throwable
|
|
||||||
*/
|
|
||||||
public function delete(Request $request, Server $server): RedirectResponse
|
|
||||||
{
|
|
||||||
$this->deletionService->withForce($request->filled('force_delete'))->handle($server);
|
|
||||||
$this->alert->success(trans('admin/server.alerts.server_deleted'))->flash();
|
|
||||||
|
|
||||||
return redirect()->route('admin.servers');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the startup command as well as variables.
|
|
||||||
*
|
|
||||||
* @throws \Illuminate\Validation\ValidationException
|
|
||||||
*/
|
|
||||||
public function saveStartup(Request $request, Server $server): RedirectResponse
|
|
||||||
{
|
|
||||||
$data = $request->except('_token');
|
|
||||||
if (!empty($data['custom_docker_image'])) {
|
|
||||||
$data['docker_image'] = $data['custom_docker_image'];
|
|
||||||
unset($data['custom_docker_image']);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
$this->startupModificationService
|
|
||||||
->setUserLevel(User::USER_LEVEL_ADMIN)
|
|
||||||
->handle($server, $data);
|
|
||||||
} catch (DataValidationException $exception) {
|
|
||||||
throw new ValidationException($exception->getValidator());
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->alert->success(trans('admin/server.alerts.startup_changed'))->flash();
|
|
||||||
|
|
||||||
return redirect()->route('admin.servers.view.startup', $server->id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new database assigned to a specific server.
|
|
||||||
*
|
|
||||||
* @throws \Throwable
|
|
||||||
*/
|
|
||||||
public function newDatabase(StoreServerDatabaseRequest $request, Server $server): RedirectResponse
|
|
||||||
{
|
|
||||||
$this->databaseManagementService->create($server, [
|
|
||||||
'database' => DatabaseManagementService::generateUniqueDatabaseName($request->input('database'), $server->id),
|
|
||||||
'remote' => $request->input('remote'),
|
|
||||||
'database_host_id' => $request->input('database_host_id'),
|
|
||||||
'max_connections' => $request->input('max_connections'),
|
|
||||||
]);
|
|
||||||
|
|
||||||
return redirect()->route('admin.servers.view.database', $server->id)->withInput();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resets the database password for a specific database on this server.
|
|
||||||
*
|
|
||||||
* @throws \Throwable
|
|
||||||
*/
|
|
||||||
public function resetDatabasePassword(Request $request, Server $server): Response
|
|
||||||
{
|
|
||||||
/** @var \Pterodactyl\Models\Database $database */
|
|
||||||
$database = $server->databases()->findOrFail($request->input('database'));
|
|
||||||
|
|
||||||
$this->databasePasswordService->handle($database);
|
|
||||||
|
|
||||||
return response('', 204);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes a database from a server.
|
|
||||||
*
|
|
||||||
* @throws \Exception
|
|
||||||
*/
|
|
||||||
public function deleteDatabase(Server $server, Database $database): Response
|
|
||||||
{
|
|
||||||
$this->databaseManagementService->delete($database);
|
|
||||||
|
|
||||||
return response('', 204);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a mount to a server.
|
|
||||||
*
|
|
||||||
* @throws \Throwable
|
|
||||||
*/
|
|
||||||
public function addMount(Request $request, Server $server): RedirectResponse
|
|
||||||
{
|
|
||||||
$mountServer = (new MountServer())->forceFill([
|
|
||||||
'mount_id' => $request->input('mount_id'),
|
|
||||||
'server_id' => $server->id,
|
|
||||||
]);
|
|
||||||
|
|
||||||
$mountServer->saveOrFail();
|
|
||||||
|
|
||||||
$this->alert->success('Mount was added successfully.')->flash();
|
|
||||||
|
|
||||||
return redirect()->route('admin.servers.view.mounts', $server->id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove a mount from a server.
|
|
||||||
*/
|
|
||||||
public function deleteMount(Server $server, Mount $mount): RedirectResponse
|
|
||||||
{
|
|
||||||
MountServer::where('mount_id', $mount->id)->where('server_id', $server->id)->delete();
|
|
||||||
|
|
||||||
$this->alert->success('Mount was removed successfully.')->flash();
|
|
||||||
|
|
||||||
return redirect()->route('admin.servers.view.mounts', $server->id);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,62 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Pterodactyl\Http\Controllers\Admin\Settings;
|
|
||||||
|
|
||||||
use Illuminate\View\View;
|
|
||||||
use Illuminate\Http\RedirectResponse;
|
|
||||||
use Prologue\Alerts\AlertsMessageBag;
|
|
||||||
use Illuminate\Contracts\Console\Kernel;
|
|
||||||
use Illuminate\View\Factory as ViewFactory;
|
|
||||||
use Pterodactyl\Http\Controllers\Controller;
|
|
||||||
use Illuminate\Contracts\Config\Repository as ConfigRepository;
|
|
||||||
use Pterodactyl\Contracts\Repository\SettingsRepositoryInterface;
|
|
||||||
use Pterodactyl\Http\Requests\Admin\Settings\AdvancedSettingsFormRequest;
|
|
||||||
|
|
||||||
class AdvancedController extends Controller
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* AdvancedController constructor.
|
|
||||||
*/
|
|
||||||
public function __construct(
|
|
||||||
private AlertsMessageBag $alert,
|
|
||||||
private ConfigRepository $config,
|
|
||||||
private Kernel $kernel,
|
|
||||||
private SettingsRepositoryInterface $settings,
|
|
||||||
private ViewFactory $view
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Render advanced Panel settings UI.
|
|
||||||
*/
|
|
||||||
public function index(): View
|
|
||||||
{
|
|
||||||
$showRecaptchaWarning = false;
|
|
||||||
if (
|
|
||||||
$this->config->get('recaptcha._shipped_secret_key') === $this->config->get('recaptcha.secret_key')
|
|
||||||
|| $this->config->get('recaptcha._shipped_website_key') === $this->config->get('recaptcha.website_key')
|
|
||||||
) {
|
|
||||||
$showRecaptchaWarning = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->view->make('admin.settings.advanced', [
|
|
||||||
'showRecaptchaWarning' => $showRecaptchaWarning,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
|
|
||||||
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
|
||||||
*/
|
|
||||||
public function update(AdvancedSettingsFormRequest $request): RedirectResponse
|
|
||||||
{
|
|
||||||
foreach ($request->normalize() as $key => $value) {
|
|
||||||
$this->settings->set('settings::' . $key, $value);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->kernel->call('queue:restart');
|
|
||||||
$this->alert->success('Advanced settings have been updated successfully and the queue worker was restarted to apply these changes.')->flash();
|
|
||||||
|
|
||||||
return redirect()->route('admin.settings.advanced');
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,60 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Pterodactyl\Http\Controllers\Admin\Settings;
|
|
||||||
|
|
||||||
use Illuminate\View\View;
|
|
||||||
use Illuminate\Http\RedirectResponse;
|
|
||||||
use Prologue\Alerts\AlertsMessageBag;
|
|
||||||
use Illuminate\Contracts\Console\Kernel;
|
|
||||||
use Illuminate\View\Factory as ViewFactory;
|
|
||||||
use Pterodactyl\Http\Controllers\Controller;
|
|
||||||
use Pterodactyl\Traits\Helpers\AvailableLanguages;
|
|
||||||
use Pterodactyl\Services\Helpers\SoftwareVersionService;
|
|
||||||
use Pterodactyl\Contracts\Repository\SettingsRepositoryInterface;
|
|
||||||
use Pterodactyl\Http\Requests\Admin\Settings\BaseSettingsFormRequest;
|
|
||||||
|
|
||||||
class IndexController extends Controller
|
|
||||||
{
|
|
||||||
use AvailableLanguages;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* IndexController constructor.
|
|
||||||
*/
|
|
||||||
public function __construct(
|
|
||||||
private AlertsMessageBag $alert,
|
|
||||||
private Kernel $kernel,
|
|
||||||
private SettingsRepositoryInterface $settings,
|
|
||||||
private SoftwareVersionService $versionService,
|
|
||||||
private ViewFactory $view
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Render the UI for basic Panel settings.
|
|
||||||
*/
|
|
||||||
public function index(): View
|
|
||||||
{
|
|
||||||
return $this->view->make('admin.settings.index', [
|
|
||||||
'version' => $this->versionService,
|
|
||||||
'languages' => $this->getAvailableLanguages(true),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle settings update.
|
|
||||||
*
|
|
||||||
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
|
|
||||||
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
|
||||||
*/
|
|
||||||
public function update(BaseSettingsFormRequest $request): RedirectResponse
|
|
||||||
{
|
|
||||||
foreach ($request->normalize() as $key => $value) {
|
|
||||||
$this->settings->set('settings::' . $key, $value);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->kernel->call('queue:restart');
|
|
||||||
$this->alert->success('Panel settings have been updated successfully and the queue worker was restarted to apply these changes.')->flash();
|
|
||||||
|
|
||||||
return redirect()->route('admin.settings');
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,91 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Pterodactyl\Http\Controllers\Admin\Settings;
|
|
||||||
|
|
||||||
use Exception;
|
|
||||||
use Illuminate\View\View;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Illuminate\Http\Response;
|
|
||||||
use Illuminate\Contracts\Console\Kernel;
|
|
||||||
use Pterodactyl\Notifications\MailTested;
|
|
||||||
use Illuminate\View\Factory as ViewFactory;
|
|
||||||
use Illuminate\Support\Facades\Notification;
|
|
||||||
use Pterodactyl\Exceptions\DisplayException;
|
|
||||||
use Pterodactyl\Http\Controllers\Controller;
|
|
||||||
use Illuminate\Contracts\Encryption\Encrypter;
|
|
||||||
use Pterodactyl\Providers\SettingsServiceProvider;
|
|
||||||
use Illuminate\Contracts\Config\Repository as ConfigRepository;
|
|
||||||
use Pterodactyl\Contracts\Repository\SettingsRepositoryInterface;
|
|
||||||
use Pterodactyl\Http\Requests\Admin\Settings\MailSettingsFormRequest;
|
|
||||||
|
|
||||||
class MailController extends Controller
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* MailController constructor.
|
|
||||||
*/
|
|
||||||
public function __construct(
|
|
||||||
private ConfigRepository $config,
|
|
||||||
private Encrypter $encrypter,
|
|
||||||
private Kernel $kernel,
|
|
||||||
private SettingsRepositoryInterface $settings,
|
|
||||||
private ViewFactory $view
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Render UI for editing mail settings. This UI should only display if
|
|
||||||
* the server is configured to send mail using SMTP.
|
|
||||||
*/
|
|
||||||
public function index(): View
|
|
||||||
{
|
|
||||||
return $this->view->make('admin.settings.mail', [
|
|
||||||
'disabled' => $this->config->get('mail.default') !== 'smtp',
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle request to update SMTP mail settings.
|
|
||||||
*
|
|
||||||
* @throws DisplayException
|
|
||||||
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
|
|
||||||
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
|
||||||
*/
|
|
||||||
public function update(MailSettingsFormRequest $request): Response
|
|
||||||
{
|
|
||||||
if ($this->config->get('mail.default') !== 'smtp') {
|
|
||||||
throw new DisplayException('This feature is only available if SMTP is the selected email driver for the Panel.');
|
|
||||||
}
|
|
||||||
|
|
||||||
$values = $request->normalize();
|
|
||||||
if (array_get($values, 'mail:password') === '!e') {
|
|
||||||
$values['mail:password'] = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($values as $key => $value) {
|
|
||||||
if (in_array($key, SettingsServiceProvider::getEncryptedKeys()) && !empty($value)) {
|
|
||||||
$value = $this->encrypter->encrypt($value);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->settings->set('settings::' . $key, $value);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->kernel->call('queue:restart');
|
|
||||||
|
|
||||||
return response('', 204);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Submit a request to send a test mail message.
|
|
||||||
*/
|
|
||||||
public function test(Request $request): Response
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
Notification::route('mail', $request->user()->email)
|
|
||||||
->notify(new MailTested($request->user()));
|
|
||||||
} catch (Exception $exception) {
|
|
||||||
return response($exception->getMessage(), 500);
|
|
||||||
}
|
|
||||||
|
|
||||||
return response('', 204);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,153 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Pterodactyl\Http\Controllers\Admin;
|
|
||||||
|
|
||||||
use Illuminate\View\View;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Pterodactyl\Models\User;
|
|
||||||
use Pterodactyl\Models\Model;
|
|
||||||
use Illuminate\Support\Collection;
|
|
||||||
use Illuminate\Http\RedirectResponse;
|
|
||||||
use Prologue\Alerts\AlertsMessageBag;
|
|
||||||
use Spatie\QueryBuilder\QueryBuilder;
|
|
||||||
use Illuminate\View\Factory as ViewFactory;
|
|
||||||
use Pterodactyl\Exceptions\DisplayException;
|
|
||||||
use Pterodactyl\Http\Controllers\Controller;
|
|
||||||
use Illuminate\Contracts\Translation\Translator;
|
|
||||||
use Pterodactyl\Services\Users\UserUpdateService;
|
|
||||||
use Pterodactyl\Traits\Helpers\AvailableLanguages;
|
|
||||||
use Pterodactyl\Services\Users\UserCreationService;
|
|
||||||
use Pterodactyl\Services\Users\UserDeletionService;
|
|
||||||
use Pterodactyl\Http\Requests\Admin\UserFormRequest;
|
|
||||||
use Pterodactyl\Http\Requests\Admin\NewUserFormRequest;
|
|
||||||
use Pterodactyl\Contracts\Repository\UserRepositoryInterface;
|
|
||||||
|
|
||||||
class UserController extends Controller
|
|
||||||
{
|
|
||||||
use AvailableLanguages;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* UserController constructor.
|
|
||||||
*/
|
|
||||||
public function __construct(
|
|
||||||
protected AlertsMessageBag $alert,
|
|
||||||
protected UserCreationService $creationService,
|
|
||||||
protected UserDeletionService $deletionService,
|
|
||||||
protected Translator $translator,
|
|
||||||
protected UserUpdateService $updateService,
|
|
||||||
protected UserRepositoryInterface $repository,
|
|
||||||
protected ViewFactory $view
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Display user index page.
|
|
||||||
*/
|
|
||||||
public function index(Request $request): View
|
|
||||||
{
|
|
||||||
$users = QueryBuilder::for(
|
|
||||||
User::query()->select('users.*')
|
|
||||||
->selectRaw('COUNT(DISTINCT(subusers.id)) as subuser_of_count')
|
|
||||||
->selectRaw('COUNT(DISTINCT(servers.id)) as servers_count')
|
|
||||||
->leftJoin('subusers', 'subusers.user_id', '=', 'users.id')
|
|
||||||
->leftJoin('servers', 'servers.owner_id', '=', 'users.id')
|
|
||||||
->groupBy('users.id')
|
|
||||||
)
|
|
||||||
->allowedFilters(['username', 'email', 'uuid'])
|
|
||||||
->allowedSorts(['id', 'uuid'])
|
|
||||||
->paginate(50);
|
|
||||||
|
|
||||||
return $this->view->make('admin.users.index', ['users' => $users]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Display new user page.
|
|
||||||
*/
|
|
||||||
public function create(): View
|
|
||||||
{
|
|
||||||
return $this->view->make('admin.users.new', [
|
|
||||||
'languages' => $this->getAvailableLanguages(true),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Display user view page.
|
|
||||||
*/
|
|
||||||
public function view(User $user): View
|
|
||||||
{
|
|
||||||
return $this->view->make('admin.users.view', [
|
|
||||||
'user' => $user,
|
|
||||||
'languages' => $this->getAvailableLanguages(true),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete a user from the system.
|
|
||||||
*
|
|
||||||
* @throws \Exception
|
|
||||||
* @throws \Pterodactyl\Exceptions\DisplayException
|
|
||||||
*/
|
|
||||||
public function delete(Request $request, User $user): RedirectResponse
|
|
||||||
{
|
|
||||||
if ($request->user()->id === $user->id) {
|
|
||||||
throw new DisplayException($this->translator->get('admin/user.exceptions.user_has_servers'));
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->deletionService->handle($user);
|
|
||||||
|
|
||||||
return redirect()->route('admin.users');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a user.
|
|
||||||
*
|
|
||||||
* @throws \Exception
|
|
||||||
* @throws \Throwable
|
|
||||||
*/
|
|
||||||
public function store(NewUserFormRequest $request): RedirectResponse
|
|
||||||
{
|
|
||||||
$user = $this->creationService->handle($request->normalize());
|
|
||||||
$this->alert->success($this->translator->get('admin/user.notices.account_created'))->flash();
|
|
||||||
|
|
||||||
return redirect()->route('admin.users.view', $user->id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update a user on the system.
|
|
||||||
*
|
|
||||||
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
|
|
||||||
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
|
||||||
*/
|
|
||||||
public function update(UserFormRequest $request, User $user): RedirectResponse
|
|
||||||
{
|
|
||||||
$this->updateService
|
|
||||||
->setUserLevel(User::USER_LEVEL_ADMIN)
|
|
||||||
->handle($user, $request->normalize());
|
|
||||||
|
|
||||||
$this->alert->success(trans('admin/user.notices.account_updated'))->flash();
|
|
||||||
|
|
||||||
return redirect()->route('admin.users.view', $user->id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a JSON response of users on the system.
|
|
||||||
*/
|
|
||||||
public function json(Request $request): Model|Collection
|
|
||||||
{
|
|
||||||
$users = QueryBuilder::for(User::query())->allowedFilters(['email'])->paginate(25);
|
|
||||||
|
|
||||||
// Handle single user requests.
|
|
||||||
if ($request->query('user_id')) {
|
|
||||||
$user = User::query()->findOrFail($request->input('user_id'));
|
|
||||||
$user->md5 = md5(strtolower($user->email));
|
|
||||||
|
|
||||||
return $user;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $users->map(function ($item) {
|
|
||||||
$item->md5 = md5(strtolower($item->email));
|
|
||||||
|
|
||||||
return $item;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -3,19 +3,16 @@
|
||||||
namespace Pterodactyl\Http\Controllers\Api\Application;
|
namespace Pterodactyl\Http\Controllers\Api\Application;
|
||||||
|
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Webmozart\Assert\Assert;
|
|
||||||
use Illuminate\Http\Response;
|
use Illuminate\Http\Response;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
use Illuminate\Container\Container;
|
use Illuminate\Container\Container;
|
||||||
use Pterodactyl\Http\Controllers\Controller;
|
use Pterodactyl\Http\Controllers\Controller;
|
||||||
use Pterodactyl\Extensions\Spatie\Fractalistic\Fractal;
|
use Pterodactyl\Extensions\Spatie\Fractalistic\Fractal;
|
||||||
use Pterodactyl\Transformers\Api\Application\BaseTransformer;
|
|
||||||
|
|
||||||
abstract class ApplicationApiController extends Controller
|
abstract class ApplicationApiController extends Controller
|
||||||
{
|
{
|
||||||
protected Request $request;
|
|
||||||
|
|
||||||
protected Fractal $fractal;
|
protected Fractal $fractal;
|
||||||
|
protected Request $request;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ApplicationApiController constructor.
|
* ApplicationApiController constructor.
|
||||||
|
@ -47,21 +44,11 @@ abstract class ApplicationApiController extends Controller
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return an instance of an application transformer.
|
* Return an HTTP/201 response for the API.
|
||||||
*
|
|
||||||
* @template T of \Pterodactyl\Transformers\Api\Application\BaseTransformer
|
|
||||||
*
|
|
||||||
* @param class-string<T> $abstract
|
|
||||||
*
|
|
||||||
* @return T
|
|
||||||
*
|
|
||||||
* @noinspection PhpDocSignatureInspection
|
|
||||||
*/
|
*/
|
||||||
public function getTransformer(string $abstract)
|
protected function returnAccepted(): Response
|
||||||
{
|
{
|
||||||
Assert::subclassOf($abstract, BaseTransformer::class);
|
return new Response('', Response::HTTP_ACCEPTED);
|
||||||
|
|
||||||
return $abstract::fromRequest($this->request);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -0,0 +1,99 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Pterodactyl\Http\Controllers\Api\Application\Databases;
|
||||||
|
|
||||||
|
use Illuminate\Http\Response;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Pterodactyl\Models\DatabaseHost;
|
||||||
|
use Spatie\QueryBuilder\QueryBuilder;
|
||||||
|
use Pterodactyl\Services\Databases\Hosts\HostUpdateService;
|
||||||
|
use Pterodactyl\Services\Databases\Hosts\HostCreationService;
|
||||||
|
use Pterodactyl\Exceptions\Http\QueryValueOutOfRangeHttpException;
|
||||||
|
use Pterodactyl\Transformers\Api\Application\DatabaseHostTransformer;
|
||||||
|
use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController;
|
||||||
|
use Pterodactyl\Http\Requests\Api\Application\Databases\GetDatabaseRequest;
|
||||||
|
use Pterodactyl\Http\Requests\Api\Application\Databases\GetDatabasesRequest;
|
||||||
|
use Pterodactyl\Http\Requests\Api\Application\Databases\StoreDatabaseRequest;
|
||||||
|
use Pterodactyl\Http\Requests\Api\Application\Databases\DeleteDatabaseRequest;
|
||||||
|
use Pterodactyl\Http\Requests\Api\Application\Databases\UpdateDatabaseRequest;
|
||||||
|
|
||||||
|
class DatabaseController extends ApplicationApiController
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* DatabaseController constructor.
|
||||||
|
*/
|
||||||
|
public function __construct(private HostCreationService $creationService, private HostUpdateService $updateService)
|
||||||
|
{
|
||||||
|
parent::__construct();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an array of all database hosts.
|
||||||
|
*/
|
||||||
|
public function index(GetDatabasesRequest $request): array
|
||||||
|
{
|
||||||
|
$perPage = (int) $request->query('per_page', '10');
|
||||||
|
if ($perPage < 1 || $perPage > 100) {
|
||||||
|
throw new QueryValueOutOfRangeHttpException('per_page', 1, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
$databases = QueryBuilder::for(DatabaseHost::query())
|
||||||
|
->allowedFilters(['name', 'host'])
|
||||||
|
->allowedSorts(['id', 'name', 'host'])
|
||||||
|
->paginate($perPage);
|
||||||
|
|
||||||
|
return $this->fractal->collection($databases)
|
||||||
|
->transformWith(DatabaseHostTransformer::class)
|
||||||
|
->toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a single database host.
|
||||||
|
*/
|
||||||
|
public function view(GetDatabaseRequest $request, DatabaseHost $databaseHost): array
|
||||||
|
{
|
||||||
|
return $this->fractal->item($databaseHost)
|
||||||
|
->transformWith(DatabaseHostTransformer::class)
|
||||||
|
->toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new database host.
|
||||||
|
*
|
||||||
|
* @throws \Throwable
|
||||||
|
*/
|
||||||
|
public function store(StoreDatabaseRequest $request): JsonResponse
|
||||||
|
{
|
||||||
|
$databaseHost = $this->creationService->handle($request->validated());
|
||||||
|
|
||||||
|
return $this->fractal->item($databaseHost)
|
||||||
|
->transformWith(DatabaseHostTransformer::class)
|
||||||
|
->respond(JsonResponse::HTTP_CREATED);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates a database host.
|
||||||
|
*
|
||||||
|
* @throws \Throwable
|
||||||
|
*/
|
||||||
|
public function update(UpdateDatabaseRequest $request, DatabaseHost $databaseHost): array
|
||||||
|
{
|
||||||
|
$databaseHost = $this->updateService->handle($databaseHost->id, $request->validated());
|
||||||
|
|
||||||
|
return $this->fractal->item($databaseHost)
|
||||||
|
->transformWith(DatabaseHostTransformer::class)
|
||||||
|
->toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes a database host.
|
||||||
|
*
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
public function delete(DeleteDatabaseRequest $request, DatabaseHost $databaseHost): Response
|
||||||
|
{
|
||||||
|
$databaseHost->delete();
|
||||||
|
|
||||||
|
return $this->returnNoContent();
|
||||||
|
}
|
||||||
|
}
|
120
app/Http/Controllers/Api/Application/Eggs/EggController.php
Normal file
120
app/Http/Controllers/Api/Application/Eggs/EggController.php
Normal file
|
@ -0,0 +1,120 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Pterodactyl\Http\Controllers\Api\Application\Eggs;
|
||||||
|
|
||||||
|
use Ramsey\Uuid\Uuid;
|
||||||
|
use Pterodactyl\Models\Egg;
|
||||||
|
use Pterodactyl\Models\Nest;
|
||||||
|
use Illuminate\Http\Response;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Spatie\QueryBuilder\QueryBuilder;
|
||||||
|
use Pterodactyl\Services\Eggs\Sharing\EggExporterService;
|
||||||
|
use Pterodactyl\Transformers\Api\Application\EggTransformer;
|
||||||
|
use Pterodactyl\Http\Requests\Api\Application\Eggs\GetEggRequest;
|
||||||
|
use Pterodactyl\Exceptions\Http\QueryValueOutOfRangeHttpException;
|
||||||
|
use Pterodactyl\Http\Requests\Api\Application\Eggs\GetEggsRequest;
|
||||||
|
use Pterodactyl\Http\Requests\Api\Application\Eggs\StoreEggRequest;
|
||||||
|
use Pterodactyl\Http\Requests\Api\Application\Eggs\DeleteEggRequest;
|
||||||
|
use Pterodactyl\Http\Requests\Api\Application\Eggs\ExportEggRequest;
|
||||||
|
use Pterodactyl\Http\Requests\Api\Application\Eggs\UpdateEggRequest;
|
||||||
|
use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController;
|
||||||
|
|
||||||
|
class EggController extends ApplicationApiController
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* EggController constructor.
|
||||||
|
*/
|
||||||
|
public function __construct(private EggExporterService $eggExporterService)
|
||||||
|
{
|
||||||
|
parent::__construct();
|
||||||
|
|
||||||
|
$this->eggExporterService = $eggExporterService;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return an array of all eggs on a given nest.
|
||||||
|
*/
|
||||||
|
public function index(GetEggsRequest $request, Nest $nest): array
|
||||||
|
{
|
||||||
|
$perPage = (int) $request->query('per_page', '10');
|
||||||
|
if ($perPage > 100) {
|
||||||
|
throw new QueryValueOutOfRangeHttpException('per_page', 1, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
// @phpstan-ignore-next-line
|
||||||
|
$eggs = QueryBuilder::for(Egg::query())
|
||||||
|
->where('nest_id', '=', $nest->id)
|
||||||
|
->allowedFilters(['id', 'name', 'author'])
|
||||||
|
->allowedSorts(['id', 'name', 'author']);
|
||||||
|
if ($perPage > 0) {
|
||||||
|
$eggs = $eggs->paginate($perPage);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->fractal->collection($eggs)
|
||||||
|
->transformWith(EggTransformer::class)
|
||||||
|
->toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a single egg.
|
||||||
|
*/
|
||||||
|
public function view(GetEggRequest $request, Egg $egg): array
|
||||||
|
{
|
||||||
|
return $this->fractal->item($egg)
|
||||||
|
->transformWith(EggTransformer::class)
|
||||||
|
->toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new egg.
|
||||||
|
*/
|
||||||
|
public function store(StoreEggRequest $request): JsonResponse
|
||||||
|
{
|
||||||
|
$validated = $request->validated();
|
||||||
|
$merged = array_merge($validated, [
|
||||||
|
'uuid' => Uuid::uuid4()->toString(),
|
||||||
|
// TODO: allow this to be set in the request, and default to config value if null or not present.
|
||||||
|
'author' => config('pterodactyl.service.author'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$egg = Egg::query()->create($merged);
|
||||||
|
|
||||||
|
return $this->fractal->item($egg)
|
||||||
|
->transformWith(EggTransformer::class)
|
||||||
|
->respond(Response::HTTP_CREATED);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates an egg.
|
||||||
|
*/
|
||||||
|
public function update(UpdateEggRequest $request, Egg $egg): array
|
||||||
|
{
|
||||||
|
$egg->update($request->validated());
|
||||||
|
|
||||||
|
return $this->fractal->item($egg)
|
||||||
|
->transformWith(EggTransformer::class)
|
||||||
|
->toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes an egg.
|
||||||
|
*
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
public function delete(DeleteEggRequest $request, Egg $egg): Response
|
||||||
|
{
|
||||||
|
$egg->delete();
|
||||||
|
|
||||||
|
return $this->returnNoContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exports an egg.
|
||||||
|
*
|
||||||
|
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
||||||
|
*/
|
||||||
|
public function export(ExportEggRequest $request, int $eggId): JsonResponse
|
||||||
|
{
|
||||||
|
return new JsonResponse($this->eggExporterService->handle($eggId));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,75 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Pterodactyl\Http\Controllers\Api\Application\Eggs;
|
||||||
|
|
||||||
|
use Pterodactyl\Models\Egg;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Http\Response;
|
||||||
|
use Pterodactyl\Models\EggVariable;
|
||||||
|
use Illuminate\Database\ConnectionInterface;
|
||||||
|
use Pterodactyl\Services\Eggs\Variables\VariableUpdateService;
|
||||||
|
use Pterodactyl\Services\Eggs\Variables\VariableCreationService;
|
||||||
|
use Pterodactyl\Transformers\Api\Application\EggVariableTransformer;
|
||||||
|
use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController;
|
||||||
|
use Pterodactyl\Http\Requests\Api\Application\Eggs\Variables\StoreEggVariableRequest;
|
||||||
|
use Pterodactyl\Http\Requests\Api\Application\Eggs\Variables\UpdateEggVariablesRequest;
|
||||||
|
|
||||||
|
class EggVariableController extends ApplicationApiController
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private ConnectionInterface $connection,
|
||||||
|
private VariableCreationService $variableCreationService,
|
||||||
|
private VariableUpdateService $variableUpdateService
|
||||||
|
) {
|
||||||
|
parent::__construct();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new egg variable.
|
||||||
|
*
|
||||||
|
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
|
||||||
|
* @throws \Pterodactyl\Exceptions\Service\Egg\Variable\BadValidationRuleException
|
||||||
|
* @throws \Pterodactyl\Exceptions\Service\Egg\Variable\ReservedVariableNameException
|
||||||
|
*/
|
||||||
|
public function store(StoreEggVariableRequest $request, Egg $egg): array
|
||||||
|
{
|
||||||
|
$variable = $this->variableCreationService->handle($egg->id, $request->validated());
|
||||||
|
|
||||||
|
return $this->fractal->item($variable)
|
||||||
|
->transformWith(EggVariableTransformer::class)
|
||||||
|
->toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates multiple egg variables.
|
||||||
|
*
|
||||||
|
* @throws \Throwable
|
||||||
|
*/
|
||||||
|
public function update(UpdateEggVariablesRequest $request, Egg $egg): array
|
||||||
|
{
|
||||||
|
$validated = $request->validated();
|
||||||
|
|
||||||
|
$this->connection->transaction(function () use ($egg, $validated) {
|
||||||
|
foreach ($validated as $data) {
|
||||||
|
$this->variableUpdateService->handle($egg, $data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return $this->fractal->collection($egg->refresh()->variables)
|
||||||
|
->transformWith(EggVariableTransformer::class)
|
||||||
|
->toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes a single egg variable.
|
||||||
|
*/
|
||||||
|
public function delete(Request $request, Egg $egg, EggVariable $eggVariable): Response
|
||||||
|
{
|
||||||
|
EggVariable::query()
|
||||||
|
->where('id', $eggVariable->id)
|
||||||
|
->where('egg_id', $egg->id)
|
||||||
|
->delete();
|
||||||
|
|
||||||
|
return $this->returnNoContent();
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,6 +10,7 @@ use Pterodactyl\Services\Locations\LocationUpdateService;
|
||||||
use Pterodactyl\Services\Locations\LocationCreationService;
|
use Pterodactyl\Services\Locations\LocationCreationService;
|
||||||
use Pterodactyl\Services\Locations\LocationDeletionService;
|
use Pterodactyl\Services\Locations\LocationDeletionService;
|
||||||
use Pterodactyl\Transformers\Api\Application\LocationTransformer;
|
use Pterodactyl\Transformers\Api\Application\LocationTransformer;
|
||||||
|
use Pterodactyl\Exceptions\Http\QueryValueOutOfRangeHttpException;
|
||||||
use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController;
|
use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController;
|
||||||
use Pterodactyl\Http\Requests\Api\Application\Locations\GetLocationRequest;
|
use Pterodactyl\Http\Requests\Api\Application\Locations\GetLocationRequest;
|
||||||
use Pterodactyl\Http\Requests\Api\Application\Locations\GetLocationsRequest;
|
use Pterodactyl\Http\Requests\Api\Application\Locations\GetLocationsRequest;
|
||||||
|
@ -35,13 +36,18 @@ class LocationController extends ApplicationApiController
|
||||||
*/
|
*/
|
||||||
public function index(GetLocationsRequest $request): array
|
public function index(GetLocationsRequest $request): array
|
||||||
{
|
{
|
||||||
|
$perPage = (int) $request->query('per_page', '10');
|
||||||
|
if ($perPage < 1 || $perPage > 100) {
|
||||||
|
throw new QueryValueOutOfRangeHttpException('per_page', 1, 100);
|
||||||
|
}
|
||||||
|
|
||||||
$locations = QueryBuilder::for(Location::query())
|
$locations = QueryBuilder::for(Location::query())
|
||||||
->allowedFilters(['short', 'long'])
|
->allowedFilters(['short', 'long'])
|
||||||
->allowedSorts(['id'])
|
->allowedSorts(['id', 'short', 'long'])
|
||||||
->paginate($request->query('per_page') ?? 50);
|
->paginate($perPage);
|
||||||
|
|
||||||
return $this->fractal->collection($locations)
|
return $this->fractal->collection($locations)
|
||||||
->transformWith($this->getTransformer(LocationTransformer::class))
|
->transformWith(LocationTransformer::class)
|
||||||
->toArray();
|
->toArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,7 +57,7 @@ class LocationController extends ApplicationApiController
|
||||||
public function view(GetLocationRequest $request, Location $location): array
|
public function view(GetLocationRequest $request, Location $location): array
|
||||||
{
|
{
|
||||||
return $this->fractal->item($location)
|
return $this->fractal->item($location)
|
||||||
->transformWith($this->getTransformer(LocationTransformer::class))
|
->transformWith(LocationTransformer::class)
|
||||||
->toArray();
|
->toArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,12 +72,7 @@ class LocationController extends ApplicationApiController
|
||||||
$location = $this->creationService->handle($request->validated());
|
$location = $this->creationService->handle($request->validated());
|
||||||
|
|
||||||
return $this->fractal->item($location)
|
return $this->fractal->item($location)
|
||||||
->transformWith($this->getTransformer(LocationTransformer::class))
|
->transformWith(LocationTransformer::class)
|
||||||
->addMeta([
|
|
||||||
'resource' => route('api.application.locations.view', [
|
|
||||||
'location' => $location->id,
|
|
||||||
]),
|
|
||||||
])
|
|
||||||
->respond(201);
|
->respond(201);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,7 +87,7 @@ class LocationController extends ApplicationApiController
|
||||||
$location = $this->updateService->handle($location, $request->validated());
|
$location = $this->updateService->handle($location, $request->validated());
|
||||||
|
|
||||||
return $this->fractal->item($location)
|
return $this->fractal->item($location)
|
||||||
->transformWith($this->getTransformer(LocationTransformer::class))
|
->transformWith(LocationTransformer::class)
|
||||||
->toArray();
|
->toArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,6 +100,6 @@ class LocationController extends ApplicationApiController
|
||||||
{
|
{
|
||||||
$this->deletionService->handle($location);
|
$this->deletionService->handle($location);
|
||||||
|
|
||||||
return response('', 204);
|
return $this->returnNoContent();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
163
app/Http/Controllers/Api/Application/Mounts/MountController.php
Normal file
163
app/Http/Controllers/Api/Application/Mounts/MountController.php
Normal file
|
@ -0,0 +1,163 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Pterodactyl\Http\Controllers\Api\Application\Mounts;
|
||||||
|
|
||||||
|
use Illuminate\Http\Response;
|
||||||
|
use Pterodactyl\Models\Mount;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Spatie\QueryBuilder\QueryBuilder;
|
||||||
|
use Pterodactyl\Transformers\Api\Application\MountTransformer;
|
||||||
|
use Pterodactyl\Exceptions\Http\QueryValueOutOfRangeHttpException;
|
||||||
|
use Pterodactyl\Http\Requests\Api\Application\Mounts\GetMountRequest;
|
||||||
|
use Pterodactyl\Http\Requests\Api\Application\Mounts\GetMountsRequest;
|
||||||
|
use Pterodactyl\Http\Requests\Api\Application\Mounts\MountEggsRequest;
|
||||||
|
use Pterodactyl\Http\Requests\Api\Application\Mounts\MountNodesRequest;
|
||||||
|
use Pterodactyl\Http\Requests\Api\Application\Mounts\StoreMountRequest;
|
||||||
|
use Pterodactyl\Http\Requests\Api\Application\Mounts\DeleteMountRequest;
|
||||||
|
use Pterodactyl\Http\Requests\Api\Application\Mounts\UpdateMountRequest;
|
||||||
|
use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController;
|
||||||
|
|
||||||
|
class MountController extends ApplicationApiController
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* MountController constructor.
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
parent::__construct();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an array of all mount.
|
||||||
|
*/
|
||||||
|
public function index(GetMountsRequest $request): array
|
||||||
|
{
|
||||||
|
$perPage = (int) $request->query('per_page', '10');
|
||||||
|
if ($perPage < 1 || $perPage > 100) {
|
||||||
|
throw new QueryValueOutOfRangeHttpException('per_page', 1, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
$mounts = QueryBuilder::for(Mount::query())
|
||||||
|
->allowedFilters(['id', 'name', 'source', 'target'])
|
||||||
|
->allowedSorts(['id', 'name', 'source', 'target'])
|
||||||
|
->paginate($perPage);
|
||||||
|
|
||||||
|
return $this->fractal->collection($mounts)
|
||||||
|
->transformWith(MountTransformer::class)
|
||||||
|
->toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a single mount.
|
||||||
|
*/
|
||||||
|
public function view(GetMountRequest $request, Mount $mount): array
|
||||||
|
{
|
||||||
|
return $this->fractal->item($mount)
|
||||||
|
->transformWith(MountTransformer::class)
|
||||||
|
->toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new mount.
|
||||||
|
*/
|
||||||
|
public function store(StoreMountRequest $request): JsonResponse
|
||||||
|
{
|
||||||
|
$mount = Mount::query()->create($request->validated());
|
||||||
|
|
||||||
|
return $this->fractal->item($mount)
|
||||||
|
->transformWith(MountTransformer::class)
|
||||||
|
->respond(JsonResponse::HTTP_CREATED);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates a mount.
|
||||||
|
*/
|
||||||
|
public function update(UpdateMountRequest $request, Mount $mount): array
|
||||||
|
{
|
||||||
|
$mount->update($request->validated());
|
||||||
|
|
||||||
|
return $this->fractal->item($mount)
|
||||||
|
->transformWith(MountTransformer::class)
|
||||||
|
->toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes a mount.
|
||||||
|
*
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
public function delete(DeleteMountRequest $request, Mount $mount): Response
|
||||||
|
{
|
||||||
|
$mount->delete();
|
||||||
|
|
||||||
|
return $this->returnNoContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attaches eggs to a mount.
|
||||||
|
*/
|
||||||
|
public function addEggs(MountEggsRequest $request, Mount $mount): array
|
||||||
|
{
|
||||||
|
$data = $request->validated();
|
||||||
|
|
||||||
|
$eggs = $data['eggs'] ?? [];
|
||||||
|
if (count($eggs) > 0) {
|
||||||
|
$mount->eggs()->syncWithoutDetaching($eggs);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->fractal->item($mount)
|
||||||
|
->transformWith(MountTransformer::class)
|
||||||
|
->toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attaches nodes to a mount.
|
||||||
|
*/
|
||||||
|
public function addNodes(MountNodesRequest $request, Mount $mount): array
|
||||||
|
{
|
||||||
|
$data = $request->validated();
|
||||||
|
|
||||||
|
$nodes = $data['nodes'] ?? [];
|
||||||
|
if (count($nodes) > 0) {
|
||||||
|
$mount->nodes()->syncWithoutDetaching($nodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->fractal->item($mount)
|
||||||
|
->transformWith(MountTransformer::class)
|
||||||
|
->toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detaches eggs from a mount.
|
||||||
|
*/
|
||||||
|
public function deleteEggs(MountEggsRequest $request, Mount $mount): array
|
||||||
|
{
|
||||||
|
$data = $request->validated();
|
||||||
|
|
||||||
|
$eggs = $data['eggs'] ?? [];
|
||||||
|
if (count($eggs) > 0) {
|
||||||
|
$mount->eggs()->detach($eggs);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->fractal->item($mount)
|
||||||
|
->transformWith(MountTransformer::class)
|
||||||
|
->toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detaches nodes from a mount.
|
||||||
|
*/
|
||||||
|
public function deleteNodes(MountNodesRequest $request, Mount $mount): array
|
||||||
|
{
|
||||||
|
$data = $request->validated();
|
||||||
|
|
||||||
|
$nodes = $data['nodes'] ?? [];
|
||||||
|
if (count($nodes) > 0) {
|
||||||
|
$mount->nodes()->detach($nodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->fractal->item($mount)
|
||||||
|
->transformWith(MountTransformer::class)
|
||||||
|
->toArray();
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,33 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Pterodactyl\Http\Controllers\Api\Application\Nests;
|
|
||||||
|
|
||||||
use Pterodactyl\Models\Egg;
|
|
||||||
use Pterodactyl\Models\Nest;
|
|
||||||
use Pterodactyl\Transformers\Api\Application\EggTransformer;
|
|
||||||
use Pterodactyl\Http\Requests\Api\Application\Nests\Eggs\GetEggRequest;
|
|
||||||
use Pterodactyl\Http\Requests\Api\Application\Nests\Eggs\GetEggsRequest;
|
|
||||||
use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController;
|
|
||||||
|
|
||||||
class EggController extends ApplicationApiController
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Return all eggs that exist for a given nest.
|
|
||||||
*/
|
|
||||||
public function index(GetEggsRequest $request, Nest $nest): array
|
|
||||||
{
|
|
||||||
return $this->fractal->collection($nest->eggs)
|
|
||||||
->transformWith($this->getTransformer(EggTransformer::class))
|
|
||||||
->toArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return a single egg that exists on the specified nest.
|
|
||||||
*/
|
|
||||||
public function view(GetEggRequest $request, Nest $nest, Egg $egg): array
|
|
||||||
{
|
|
||||||
return $this->fractal->item($egg)
|
|
||||||
->transformWith($this->getTransformer(EggTransformer::class))
|
|
||||||
->toArray();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -3,9 +3,21 @@
|
||||||
namespace Pterodactyl\Http\Controllers\Api\Application\Nests;
|
namespace Pterodactyl\Http\Controllers\Api\Application\Nests;
|
||||||
|
|
||||||
use Pterodactyl\Models\Nest;
|
use Pterodactyl\Models\Nest;
|
||||||
use Pterodactyl\Contracts\Repository\NestRepositoryInterface;
|
use Illuminate\Http\Response;
|
||||||
|
use Spatie\QueryBuilder\QueryBuilder;
|
||||||
|
use Pterodactyl\Services\Nests\NestUpdateService;
|
||||||
|
use Pterodactyl\Services\Nests\NestCreationService;
|
||||||
|
use Pterodactyl\Services\Nests\NestDeletionService;
|
||||||
|
use Pterodactyl\Services\Eggs\Sharing\EggImporterService;
|
||||||
|
use Pterodactyl\Transformers\Api\Application\EggTransformer;
|
||||||
use Pterodactyl\Transformers\Api\Application\NestTransformer;
|
use Pterodactyl\Transformers\Api\Application\NestTransformer;
|
||||||
|
use Pterodactyl\Exceptions\Http\QueryValueOutOfRangeHttpException;
|
||||||
|
use Pterodactyl\Http\Requests\Api\Application\Nests\GetNestRequest;
|
||||||
|
use Pterodactyl\Http\Requests\Api\Application\Eggs\ImportEggRequest;
|
||||||
use Pterodactyl\Http\Requests\Api\Application\Nests\GetNestsRequest;
|
use Pterodactyl\Http\Requests\Api\Application\Nests\GetNestsRequest;
|
||||||
|
use Pterodactyl\Http\Requests\Api\Application\Nests\StoreNestRequest;
|
||||||
|
use Pterodactyl\Http\Requests\Api\Application\Nests\DeleteNestRequest;
|
||||||
|
use Pterodactyl\Http\Requests\Api\Application\Nests\UpdateNestRequest;
|
||||||
use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController;
|
use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController;
|
||||||
|
|
||||||
class NestController extends ApplicationApiController
|
class NestController extends ApplicationApiController
|
||||||
|
@ -13,8 +25,12 @@ class NestController extends ApplicationApiController
|
||||||
/**
|
/**
|
||||||
* NestController constructor.
|
* NestController constructor.
|
||||||
*/
|
*/
|
||||||
public function __construct(private NestRepositoryInterface $repository)
|
public function __construct(
|
||||||
{
|
private NestCreationService $nestCreationService,
|
||||||
|
private NestDeletionService $nestDeletionService,
|
||||||
|
private NestUpdateService $nestUpdateService,
|
||||||
|
private EggImporterService $eggImporterService
|
||||||
|
) {
|
||||||
parent::__construct();
|
parent::__construct();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,20 +39,87 @@ class NestController extends ApplicationApiController
|
||||||
*/
|
*/
|
||||||
public function index(GetNestsRequest $request): array
|
public function index(GetNestsRequest $request): array
|
||||||
{
|
{
|
||||||
$nests = $this->repository->paginated($request->query('per_page') ?? 50);
|
$perPage = (int) $request->query('per_page', '10');
|
||||||
|
if ($perPage > 100) {
|
||||||
|
throw new QueryValueOutOfRangeHttpException('per_page', 1, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
$nests = QueryBuilder::for(Nest::query())
|
||||||
|
->allowedFilters(['id', 'name', 'author'])
|
||||||
|
->allowedSorts(['id', 'name', 'author']);
|
||||||
|
if ($perPage > 0) {
|
||||||
|
$nests = $nests->paginate($perPage);
|
||||||
|
}
|
||||||
|
|
||||||
return $this->fractal->collection($nests)
|
return $this->fractal->collection($nests)
|
||||||
->transformWith($this->getTransformer(NestTransformer::class))
|
->transformWith(NestTransformer::class)
|
||||||
->toArray();
|
->toArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return information about a single Nest model.
|
* Return information about a single Nest model.
|
||||||
*/
|
*/
|
||||||
public function view(GetNestsRequest $request, Nest $nest): array
|
public function view(GetNestRequest $request, Nest $nest): array
|
||||||
{
|
{
|
||||||
return $this->fractal->item($nest)
|
return $this->fractal->item($nest)
|
||||||
->transformWith($this->getTransformer(NestTransformer::class))
|
->transformWith(NestTransformer::class)
|
||||||
->toArray();
|
->toArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new nest.
|
||||||
|
*
|
||||||
|
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
|
||||||
|
*/
|
||||||
|
public function store(StoreNestRequest $request): array
|
||||||
|
{
|
||||||
|
$nest = $this->nestCreationService->handle($request->validated());
|
||||||
|
|
||||||
|
return $this->fractal->item($nest)
|
||||||
|
->transformWith(NestTransformer::class)
|
||||||
|
->toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Imports an egg.
|
||||||
|
*/
|
||||||
|
public function import(ImportEggRequest $request, Nest $nest): array
|
||||||
|
{
|
||||||
|
$egg = $this->eggImporterService->handleContent(
|
||||||
|
$nest->id,
|
||||||
|
$request->getContent(),
|
||||||
|
$request->headers->get('Content-Type'),
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this->fractal->item($egg)
|
||||||
|
->transformWith(EggTransformer::class)
|
||||||
|
->toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates an existing nest.
|
||||||
|
*
|
||||||
|
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
|
||||||
|
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
||||||
|
*/
|
||||||
|
public function update(UpdateNestRequest $request, Nest $nest): array
|
||||||
|
{
|
||||||
|
$this->nestUpdateService->handle($nest->id, $request->validated());
|
||||||
|
|
||||||
|
return $this->fractal->item($nest)
|
||||||
|
->transformWith(NestTransformer::class)
|
||||||
|
->toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes an existing nest.
|
||||||
|
*
|
||||||
|
* @throws \Pterodactyl\Exceptions\Service\HasActiveServersException
|
||||||
|
*/
|
||||||
|
public function delete(DeleteNestRequest $request, Nest $nest): Response
|
||||||
|
{
|
||||||
|
$this->nestDeletionService->handle($nest->id);
|
||||||
|
|
||||||
|
return $this->returnNoContent();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,13 +3,14 @@
|
||||||
namespace Pterodactyl\Http\Controllers\Api\Application\Nodes;
|
namespace Pterodactyl\Http\Controllers\Api\Application\Nodes;
|
||||||
|
|
||||||
use Pterodactyl\Models\Node;
|
use Pterodactyl\Models\Node;
|
||||||
use Illuminate\Http\JsonResponse;
|
use Illuminate\Http\Response;
|
||||||
use Pterodactyl\Models\Allocation;
|
use Pterodactyl\Models\Allocation;
|
||||||
use Spatie\QueryBuilder\QueryBuilder;
|
use Spatie\QueryBuilder\QueryBuilder;
|
||||||
use Spatie\QueryBuilder\AllowedFilter;
|
use Spatie\QueryBuilder\AllowedFilter;
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
use Pterodactyl\Services\Allocations\AssignmentService;
|
use Pterodactyl\Services\Allocations\AssignmentService;
|
||||||
use Pterodactyl\Services\Allocations\AllocationDeletionService;
|
use Pterodactyl\Services\Allocations\AllocationDeletionService;
|
||||||
|
use Pterodactyl\Exceptions\Http\QueryValueOutOfRangeHttpException;
|
||||||
use Pterodactyl\Transformers\Api\Application\AllocationTransformer;
|
use Pterodactyl\Transformers\Api\Application\AllocationTransformer;
|
||||||
use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController;
|
use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController;
|
||||||
use Pterodactyl\Http\Requests\Api\Application\Allocations\GetAllocationsRequest;
|
use Pterodactyl\Http\Requests\Api\Application\Allocations\GetAllocationsRequest;
|
||||||
|
@ -33,23 +34,27 @@ class AllocationController extends ApplicationApiController
|
||||||
*/
|
*/
|
||||||
public function index(GetAllocationsRequest $request, Node $node): array
|
public function index(GetAllocationsRequest $request, Node $node): array
|
||||||
{
|
{
|
||||||
$allocations = QueryBuilder::for($node->allocations())
|
$perPage = (int) $request->query('per_page', '10');
|
||||||
->allowedFilters([
|
if ($perPage < 1 || $perPage > 100) {
|
||||||
AllowedFilter::exact('ip'),
|
throw new QueryValueOutOfRangeHttpException('per_page', 1, 100);
|
||||||
AllowedFilter::exact('port'),
|
}
|
||||||
'ip_alias',
|
|
||||||
AllowedFilter::callback('server_id', function (Builder $builder, $value) {
|
|
||||||
if (empty($value) || is_bool($value) || !ctype_digit((string) $value)) {
|
|
||||||
return $builder->whereNull('server_id');
|
|
||||||
}
|
|
||||||
|
|
||||||
return $builder->where('server_id', $value);
|
$allocations = QueryBuilder::for(Allocation::query()->where('node_id', '=', $node->id))
|
||||||
|
->allowedFilters([
|
||||||
|
'id', 'ip', 'port', 'alias',
|
||||||
|
AllowedFilter::callback('server_id', function (Builder $query, $value) {
|
||||||
|
if ($value === '0') {
|
||||||
|
$query->whereNull('server_id');
|
||||||
|
} else {
|
||||||
|
$query->where('server_id', '=', $value);
|
||||||
|
}
|
||||||
}),
|
}),
|
||||||
])
|
])
|
||||||
->paginate($request->query('per_page') ?? 50);
|
->allowedSorts(['id', 'ip', 'port', 'server_id'])
|
||||||
|
->paginate($perPage);
|
||||||
|
|
||||||
return $this->fractal->collection($allocations)
|
return $this->fractal->collection($allocations)
|
||||||
->transformWith($this->getTransformer(AllocationTransformer::class))
|
->transformWith(AllocationTransformer::class)
|
||||||
->toArray();
|
->toArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,11 +67,11 @@ class AllocationController extends ApplicationApiController
|
||||||
* @throws \Pterodactyl\Exceptions\Service\Allocation\PortOutOfRangeException
|
* @throws \Pterodactyl\Exceptions\Service\Allocation\PortOutOfRangeException
|
||||||
* @throws \Pterodactyl\Exceptions\Service\Allocation\TooManyPortsInRangeException
|
* @throws \Pterodactyl\Exceptions\Service\Allocation\TooManyPortsInRangeException
|
||||||
*/
|
*/
|
||||||
public function store(StoreAllocationRequest $request, Node $node): JsonResponse
|
public function store(StoreAllocationRequest $request, Node $node): Response
|
||||||
{
|
{
|
||||||
$this->assignmentService->handle($node, $request->validated());
|
$this->assignmentService->handle($node, $request->validated());
|
||||||
|
|
||||||
return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT);
|
return $this->returnNoContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -74,10 +79,10 @@ class AllocationController extends ApplicationApiController
|
||||||
*
|
*
|
||||||
* @throws \Pterodactyl\Exceptions\Service\Allocation\ServerUsingAllocationException
|
* @throws \Pterodactyl\Exceptions\Service\Allocation\ServerUsingAllocationException
|
||||||
*/
|
*/
|
||||||
public function delete(DeleteAllocationRequest $request, Node $node, Allocation $allocation): JsonResponse
|
public function delete(DeleteAllocationRequest $request, Node $node, Allocation $allocation): Response
|
||||||
{
|
{
|
||||||
$this->deletionService->handle($allocation);
|
$this->deletionService->handle($allocation);
|
||||||
|
|
||||||
return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT);
|
return $this->returnNoContent();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,8 +14,12 @@ class NodeConfigurationController extends ApplicationApiController
|
||||||
* to remote machines so long as an API key is provided to the machine to make the request
|
* to remote machines so long as an API key is provided to the machine to make the request
|
||||||
* with, and the node is known.
|
* with, and the node is known.
|
||||||
*/
|
*/
|
||||||
public function __invoke(GetNodeRequest $request, Node $node): JsonResponse
|
public function __invoke(GetNodeRequest $request, Node $node): JsonResponse|string
|
||||||
{
|
{
|
||||||
|
if ($request->query('format') === 'yaml') {
|
||||||
|
return $node->getYamlConfiguration();
|
||||||
|
}
|
||||||
|
|
||||||
return new JsonResponse($node->getConfiguration());
|
return new JsonResponse($node->getConfiguration());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,12 +3,14 @@
|
||||||
namespace Pterodactyl\Http\Controllers\Api\Application\Nodes;
|
namespace Pterodactyl\Http\Controllers\Api\Application\Nodes;
|
||||||
|
|
||||||
use Pterodactyl\Models\Node;
|
use Pterodactyl\Models\Node;
|
||||||
|
use Illuminate\Http\Response;
|
||||||
use Illuminate\Http\JsonResponse;
|
use Illuminate\Http\JsonResponse;
|
||||||
use Spatie\QueryBuilder\QueryBuilder;
|
use Spatie\QueryBuilder\QueryBuilder;
|
||||||
use Pterodactyl\Services\Nodes\NodeUpdateService;
|
use Pterodactyl\Services\Nodes\NodeUpdateService;
|
||||||
use Pterodactyl\Services\Nodes\NodeCreationService;
|
use Pterodactyl\Services\Nodes\NodeCreationService;
|
||||||
use Pterodactyl\Services\Nodes\NodeDeletionService;
|
use Pterodactyl\Services\Nodes\NodeDeletionService;
|
||||||
use Pterodactyl\Transformers\Api\Application\NodeTransformer;
|
use Pterodactyl\Transformers\Api\Application\NodeTransformer;
|
||||||
|
use Pterodactyl\Exceptions\Http\QueryValueOutOfRangeHttpException;
|
||||||
use Pterodactyl\Http\Requests\Api\Application\Nodes\GetNodeRequest;
|
use Pterodactyl\Http\Requests\Api\Application\Nodes\GetNodeRequest;
|
||||||
use Pterodactyl\Http\Requests\Api\Application\Nodes\GetNodesRequest;
|
use Pterodactyl\Http\Requests\Api\Application\Nodes\GetNodesRequest;
|
||||||
use Pterodactyl\Http\Requests\Api\Application\Nodes\StoreNodeRequest;
|
use Pterodactyl\Http\Requests\Api\Application\Nodes\StoreNodeRequest;
|
||||||
|
@ -34,13 +36,18 @@ class NodeController extends ApplicationApiController
|
||||||
*/
|
*/
|
||||||
public function index(GetNodesRequest $request): array
|
public function index(GetNodesRequest $request): array
|
||||||
{
|
{
|
||||||
|
$perPage = (int) $request->query('per_page', '10');
|
||||||
|
if ($perPage < 1 || $perPage > 100) {
|
||||||
|
throw new QueryValueOutOfRangeHttpException('per_page', 1, 100);
|
||||||
|
}
|
||||||
|
|
||||||
$nodes = QueryBuilder::for(Node::query())
|
$nodes = QueryBuilder::for(Node::query())
|
||||||
->allowedFilters(['uuid', 'name', 'fqdn', 'daemon_token_id'])
|
->allowedFilters(['id', 'uuid', 'name', 'fqdn', 'daemon_token_id'])
|
||||||
->allowedSorts(['id', 'uuid', 'memory', 'disk'])
|
->allowedSorts(['id', 'uuid', 'name', 'location_id', 'fqdn', 'memory', 'disk'])
|
||||||
->paginate($request->query('per_page') ?? 50);
|
->paginate($perPage);
|
||||||
|
|
||||||
return $this->fractal->collection($nodes)
|
return $this->fractal->collection($nodes)
|
||||||
->transformWith($this->getTransformer(NodeTransformer::class))
|
->transformWith(NodeTransformer::class)
|
||||||
->toArray();
|
->toArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,7 +57,7 @@ class NodeController extends ApplicationApiController
|
||||||
public function view(GetNodeRequest $request, Node $node): array
|
public function view(GetNodeRequest $request, Node $node): array
|
||||||
{
|
{
|
||||||
return $this->fractal->item($node)
|
return $this->fractal->item($node)
|
||||||
->transformWith($this->getTransformer(NodeTransformer::class))
|
->transformWith(NodeTransformer::class)
|
||||||
->toArray();
|
->toArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,12 +72,7 @@ class NodeController extends ApplicationApiController
|
||||||
$node = $this->creationService->handle($request->validated());
|
$node = $this->creationService->handle($request->validated());
|
||||||
|
|
||||||
return $this->fractal->item($node)
|
return $this->fractal->item($node)
|
||||||
->transformWith($this->getTransformer(NodeTransformer::class))
|
->transformWith(NodeTransformer::class)
|
||||||
->addMeta([
|
|
||||||
'resource' => route('api.application.nodes.view', [
|
|
||||||
'node' => $node->id,
|
|
||||||
]),
|
|
||||||
])
|
|
||||||
->respond(201);
|
->respond(201);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,11 +86,10 @@ class NodeController extends ApplicationApiController
|
||||||
$node = $this->updateService->handle(
|
$node = $this->updateService->handle(
|
||||||
$node,
|
$node,
|
||||||
$request->validated(),
|
$request->validated(),
|
||||||
$request->input('reset_secret') === true
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return $this->fractal->item($node)
|
return $this->fractal->item($node)
|
||||||
->transformWith($this->getTransformer(NodeTransformer::class))
|
->transformWith(NodeTransformer::class)
|
||||||
->toArray();
|
->toArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,10 +99,10 @@ class NodeController extends ApplicationApiController
|
||||||
*
|
*
|
||||||
* @throws \Pterodactyl\Exceptions\Service\HasActiveServersException
|
* @throws \Pterodactyl\Exceptions\Service\HasActiveServersException
|
||||||
*/
|
*/
|
||||||
public function delete(DeleteNodeRequest $request, Node $node): JsonResponse
|
public function delete(DeleteNodeRequest $request, Node $node): Response
|
||||||
{
|
{
|
||||||
$this->deletionService->handle($node);
|
$this->deletionService->handle($node);
|
||||||
|
|
||||||
return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT);
|
return $this->returnNoContent();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,7 +33,7 @@ class NodeDeploymentController extends ApplicationApiController
|
||||||
->handle($request->query('per_page'), $request->query('page'));
|
->handle($request->query('per_page'), $request->query('page'));
|
||||||
|
|
||||||
return $this->fractal->collection($nodes)
|
return $this->fractal->collection($nodes)
|
||||||
->transformWith($this->getTransformer(NodeTransformer::class))
|
->transformWith(NodeTransformer::class)
|
||||||
->toArray();
|
->toArray();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Pterodactyl\Http\Controllers\Api\Application\Nodes;
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Pterodactyl\Models\Node;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Cache\Repository as CacheRepository;
|
||||||
|
use Pterodactyl\Repositories\Wings\DaemonConfigurationRepository;
|
||||||
|
use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController;
|
||||||
|
|
||||||
|
class NodeInformationController extends ApplicationApiController
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* NodeInformationController constructor.
|
||||||
|
*/
|
||||||
|
public function __construct(private CacheRepository $cache, private DaemonConfigurationRepository $repository)
|
||||||
|
{
|
||||||
|
parent::__construct();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns system information from the node.
|
||||||
|
*
|
||||||
|
* @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException
|
||||||
|
*/
|
||||||
|
public function __invoke(Request $request, Node $node): JsonResponse
|
||||||
|
{
|
||||||
|
$data = $this->cache
|
||||||
|
->tags(['nodes'])
|
||||||
|
->remember($node->uuid, Carbon::now()->addSeconds(30), function () use ($node) {
|
||||||
|
return $this->repository->setNode($node)->getSystemInformation();
|
||||||
|
});
|
||||||
|
|
||||||
|
return new JsonResponse([
|
||||||
|
'version' => $data['version'] ?? null,
|
||||||
|
'system' => [
|
||||||
|
'type' => Str::title($data['os'] ?? 'Unknown'),
|
||||||
|
'arch' => $data['architecture'] ?? null,
|
||||||
|
'release' => $data['kernel_version'] ?? null,
|
||||||
|
'cpus' => $data['cpu_count'] ?? null,
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,96 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Pterodactyl\Http\Controllers\Api\Application\Roles;
|
||||||
|
|
||||||
|
use Illuminate\Http\Response;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Pterodactyl\Models\AdminRole;
|
||||||
|
use Spatie\QueryBuilder\QueryBuilder;
|
||||||
|
use Pterodactyl\Exceptions\Http\QueryValueOutOfRangeHttpException;
|
||||||
|
use Pterodactyl\Transformers\Api\Application\AdminRoleTransformer;
|
||||||
|
use Pterodactyl\Http\Requests\Api\Application\Roles\GetRoleRequest;
|
||||||
|
use Pterodactyl\Http\Requests\Api\Application\Roles\GetRolesRequest;
|
||||||
|
use Pterodactyl\Http\Requests\Api\Application\Roles\StoreRoleRequest;
|
||||||
|
use Pterodactyl\Http\Requests\Api\Application\Roles\DeleteRoleRequest;
|
||||||
|
use Pterodactyl\Http\Requests\Api\Application\Roles\UpdateRoleRequest;
|
||||||
|
use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController;
|
||||||
|
|
||||||
|
class RoleController extends ApplicationApiController
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* RoleController constructor.
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
parent::__construct();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an array of all roles.
|
||||||
|
*/
|
||||||
|
public function index(GetRolesRequest $request): array
|
||||||
|
{
|
||||||
|
$perPage = (int) $request->query('per_page', '10');
|
||||||
|
if ($perPage < 1 || $perPage > 100) {
|
||||||
|
throw new QueryValueOutOfRangeHttpException('per_page', 1, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
$roles = QueryBuilder::for(AdminRole::query())
|
||||||
|
->allowedFilters(['id', 'name'])
|
||||||
|
->allowedSorts(['id', 'name'])
|
||||||
|
->paginate($perPage);
|
||||||
|
|
||||||
|
return $this->fractal->collection($roles)
|
||||||
|
->transformWith(AdminRoleTransformer::class)
|
||||||
|
->toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a single role.
|
||||||
|
*/
|
||||||
|
public function view(GetRoleRequest $request, AdminRole $role): array
|
||||||
|
{
|
||||||
|
return $this->fractal->item($role)
|
||||||
|
->transformWith(AdminRoleTransformer::class)
|
||||||
|
->toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new role.
|
||||||
|
*/
|
||||||
|
public function store(StoreRoleRequest $request): JsonResponse
|
||||||
|
{
|
||||||
|
$data = array_merge($request->validated(), [
|
||||||
|
'sort_id' => 99,
|
||||||
|
]);
|
||||||
|
$role = AdminRole::query()->create($data);
|
||||||
|
|
||||||
|
return $this->fractal->item($role)
|
||||||
|
->transformWith(AdminRoleTransformer::class)
|
||||||
|
->respond(JsonResponse::HTTP_CREATED);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates a role.
|
||||||
|
*/
|
||||||
|
public function update(UpdateRoleRequest $request, AdminRole $role): array
|
||||||
|
{
|
||||||
|
$role->update($request->validated());
|
||||||
|
|
||||||
|
return $this->fractal->item($role)
|
||||||
|
->transformWith(AdminRoleTransformer::class)
|
||||||
|
->toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes a role.
|
||||||
|
*
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
public function delete(DeleteRoleRequest $request, AdminRole $role): Response
|
||||||
|
{
|
||||||
|
$role->delete();
|
||||||
|
|
||||||
|
return $this->returnNoContent();
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue