Compare commits
5 commits
develop
...
1.0-develo
Author | SHA1 | Date | |
---|---|---|---|
|
8abf2d8106 | ||
|
341eda7855 | ||
|
742e352c67 | ||
|
a62e8b1a67 | ||
|
35159b3715 |
1071 changed files with 38618 additions and 26462 deletions
20
.env.ci
Normal file
20
.env.ci
Normal file
|
@ -0,0 +1,20 @@
|
|||
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,6 +1,7 @@
|
|||
APP_ENV=production
|
||||
APP_DEBUG=false
|
||||
APP_KEY=
|
||||
APP_THEME=pterodactyl
|
||||
APP_TIMEZONE=UTC
|
||||
APP_URL=http://panel.example.com
|
||||
APP_LOCALE=en
|
||||
|
|
|
@ -28,8 +28,8 @@ module.exports = {
|
|||
// 'standard',
|
||||
'eslint:recommended',
|
||||
'plugin:react/recommended',
|
||||
'plugin:react/jsx-runtime',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:jest-dom/recommended',
|
||||
],
|
||||
rules: {
|
||||
eqeqeq: 'error',
|
||||
|
@ -37,7 +37,6 @@ module.exports = {
|
|||
// TypeScript can infer this significantly better than eslint ever can.
|
||||
'react/prop-types': 0,
|
||||
'react/display-name': 0,
|
||||
'react/no-unknown-property': ['error', {ignore: ['css']}],
|
||||
'@typescript-eslint/no-explicit-any': 0,
|
||||
'@typescript-eslint/no-non-null-assertion': 0,
|
||||
// This setup is required to avoid a spam of errors when running eslint about React being
|
||||
|
|
21
.github/docker/Caddyfile
vendored
21
.github/docker/Caddyfile
vendored
|
@ -1,21 +0,0 @@
|
|||
: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
Normal file
76
.github/docker/README.md
vendored
Normal file
|
@ -0,0 +1,76 @@
|
|||
# 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
Normal file
51
.github/docker/default.conf
vendored
Normal file
|
@ -0,0 +1,51 @@
|
|||
# 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
Normal file
70
.github/docker/default_ssl.conf
vendored
Normal file
|
@ -0,0 +1,70 @@
|
|||
# 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
Normal file
81
.github/docker/entrypoint.sh
vendored
Normal file
|
@ -0,0 +1,81 @@
|
|||
#!/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 /app/storage/logs/ /var/log/panel/
|
||||
|
||||
## 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
21
.github/docker/php-fpm.conf
vendored
|
@ -1,21 +0,0 @@
|
|||
[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
|
80
.github/docker/supervisord.conf
vendored
80
.github/docker/supervisord.conf
vendored
|
@ -1,57 +1,39 @@
|
|||
[supervisord]
|
||||
logfile=/dev/stdout
|
||||
logfile_maxbytes=0
|
||||
loglevel=info
|
||||
minfds=1024
|
||||
minprocs=200
|
||||
nodaemon=true
|
||||
pidfile=/dev/null
|
||||
|
||||
[unix_http_server]
|
||||
file=/tmp/supervisor.sock
|
||||
file=/tmp/supervisor.sock ; path to your socket file
|
||||
|
||||
[supervisorctl]
|
||||
serverurl=unix:///tmp/supervisor.sock
|
||||
[supervisord]
|
||||
logfile=/var/log/supervisord/supervisord.log ; supervisord log file
|
||||
logfile_maxbytes=50MB ; maximum size of logfile before rotation
|
||||
logfile_backups=2 ; number of backed up logfiles
|
||||
loglevel=error ; info, debug, warn, trace
|
||||
pidfile=/var/run/supervisord.pid ; pidfile location
|
||||
nodaemon=false ; run supervisord as a daemon
|
||||
minfds=1024 ; number of startup file descriptors
|
||||
minprocs=200 ; number of process descriptors
|
||||
user=root ; default user
|
||||
childlogdir=/var/log/supervisord/ ; where child log files will live
|
||||
|
||||
[rpcinterface:supervisor]
|
||||
supervisor.rpcinterface_factory=supervisor.rpcinterface:make_main_rpcinterface
|
||||
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
|
||||
|
||||
[program:caddy]
|
||||
command=/usr/local/bin/caddy run --config /etc/caddy/Caddyfile
|
||||
[supervisorctl]
|
||||
serverurl=unix:///tmp/supervisor.sock ; use a unix:// URL for a unix socket
|
||||
|
||||
[program:php-fpm]
|
||||
command=/usr/local/sbin/php-fpm -F
|
||||
autostart=true
|
||||
autorestart=true
|
||||
|
||||
[program:queue-worker]
|
||||
command=/usr/local/bin/php /app/artisan queue:work --queue=high,standard,low --sleep=3 --tries=3
|
||||
user=nginx
|
||||
autostart=true
|
||||
autorestart=true
|
||||
|
||||
[program:nginx]
|
||||
command=/usr/sbin/nginx -g 'daemon off;'
|
||||
autostart=true
|
||||
autorestart=true
|
||||
priority=10
|
||||
stdout_logfile=/dev/stdout
|
||||
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"
|
||||
stdout_events_enabled=true
|
||||
stderr_events_enabled=true
|
16
.github/docker/www.conf
vendored
Normal file
16
.github/docker/www.conf
vendored
Normal file
|
@ -0,0 +1,16 @@
|
|||
[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
8
.github/docker/yacron.yaml
vendored
|
@ -1,8 +0,0 @@
|
|||
jobs:
|
||||
- name: scheduler
|
||||
command:
|
||||
- /usr/bin/php
|
||||
- /var/www/pterodactyl/artisan
|
||||
- schedule:run
|
||||
schedule: "* * * * *"
|
||||
utc: true
|
35
.github/workflows/build.yaml
vendored
Normal file
35
.github/workflows/build.yaml
vendored
Normal file
|
@ -0,0 +1,35 @@
|
|||
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
Normal file
72
.github/workflows/ci.yaml
vendored
Normal file
|
@ -0,0 +1,72 @@
|
|||
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, 8.2]
|
||||
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
|
13
.github/workflows/docker.yaml
vendored
13
.github/workflows/docker.yaml
vendored
|
@ -16,13 +16,14 @@ on:
|
|||
jobs:
|
||||
push:
|
||||
name: Push
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-20.04
|
||||
if: "!contains(github.ref, 'develop') || (!contains(github.event.head_commit.message, 'skip docker') && !contains(github.event.head_commit.message, 'docker skip'))"
|
||||
steps:
|
||||
- name: Code checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Fetch metadata
|
||||
id: metadata
|
||||
- name: Docker metadata
|
||||
id: docker_meta
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: ghcr.io/pterodactyl/panel
|
||||
|
@ -58,10 +59,10 @@ jobs:
|
|||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
file: ./Containerfile
|
||||
file: ./Dockerfile
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
platforms: linux/amd64,linux/arm64
|
||||
labels: ${{ steps.metadata.outputs.labels }}
|
||||
tags: ${{ steps.metadata.outputs.tags }}
|
||||
labels: ${{ steps.docker_meta.outputs.labels }}
|
||||
tags: ${{ steps.docker_meta.outputs.tags }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
|
210
.github/workflows/laravel.yaml
vendored
210
.github/workflows/laravel.yaml
vendored
|
@ -1,210 +0,0 @@
|
|||
name: Laravel
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "develop"
|
||||
- "1.0-develop"
|
||||
pull_request:
|
||||
branches:
|
||||
- "develop"
|
||||
- "1.0-develop"
|
||||
|
||||
jobs:
|
||||
analysis:
|
||||
name: Static Analysis
|
||||
runs-on: ubuntu-22.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-22.04
|
||||
steps:
|
||||
- name: Code checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
# TODO: Update to 8.2 once php-cs-fixer supports it
|
||||
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-22.04
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
php: [8.1, 8.2]
|
||||
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-22.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, 8.2]
|
||||
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
Normal file
36
.github/workflows/lint.yaml
vendored
Normal file
|
@ -0,0 +1,36 @@
|
|||
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
|
15
.github/workflows/release.yaml
vendored
15
.github/workflows/release.yaml
vendored
|
@ -8,25 +8,22 @@ on:
|
|||
jobs:
|
||||
release:
|
||||
name: Release
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: Code checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
cache: pnpm
|
||||
node-version: 16
|
||||
cache: "yarn"
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
run: yarn install --frozen-lockfile
|
||||
|
||||
- name: Build
|
||||
run: pnpm run build
|
||||
run: yarn build:production
|
||||
|
||||
- name: Create release branch and bump version
|
||||
env:
|
||||
|
@ -44,7 +41,7 @@ jobs:
|
|||
|
||||
- name: Create release archive
|
||||
run: |
|
||||
rm -rf node_modules tests CODE_OF_CONDUCT.md CONTRIBUTING.md flake.lock flake.nix phpstan.neon phpunit.xml shell.nix
|
||||
rm -rf node_modules tests CODE_OF_CONDUCT.md CONTRIBUTING.md flake.lock flake.nix phpunit.xml shell.nix
|
||||
tar -czf panel.tar.gz * .editorconfig .env.example .eslintignore .eslintrc.js .gitignore .prettierrc.json
|
||||
|
||||
- name: Extract changelog
|
||||
|
|
63
.github/workflows/ui.yaml
vendored
63
.github/workflows/ui.yaml
vendored
|
@ -1,63 +0,0 @@
|
|||
name: UI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "develop"
|
||||
- "1.0-develop"
|
||||
pull_request:
|
||||
branches:
|
||||
- "develop"
|
||||
- "1.0-develop"
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
name: Lint
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Code checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
cache: pnpm
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Lint
|
||||
run: pnpm run lint
|
||||
|
||||
tests:
|
||||
name: Tests
|
||||
runs-on: ubuntu-22.04
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
node: [16, 18]
|
||||
steps:
|
||||
- name: Code checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
cache: pnpm
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Build
|
||||
run: pnpm run build
|
||||
|
||||
- name: Tests
|
||||
run: pnpm run test
|
1
.npmrc
1
.npmrc
|
@ -1 +0,0 @@
|
|||
shamefully-hoist=true
|
|
@ -1,4 +0,0 @@
|
|||
.github
|
||||
public
|
||||
node_modules
|
||||
resources/views
|
|
@ -4,19 +4,6 @@
|
|||
"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
|
||||
}
|
||||
}
|
||||
]
|
||||
"jsxSingleQuote": true,
|
||||
"endOfLine": "lf"
|
||||
}
|
||||
|
|
63
BUILDING.md
Normal file
63
BUILDING.md
Normal file
|
@ -0,0 +1,63 @@
|
|||
# Local Development
|
||||
Pterodactyl is now powered by React, Typescript, and Tailwindcss using webpack at its core to generate compiled assets.
|
||||
Release versions of Pterodactyl will include pre-compiled, minified, and hashed assets ready-to-go.
|
||||
|
||||
However, if you are interested in running custom themes or making modifications to the React files you'll need a build
|
||||
system in place to generate these compiled assets. To get your environment setup you'll need at minimum:
|
||||
|
||||
* [Node.js](https://nodejs.org/en/) v14.x.x
|
||||
* [Yarn](https://classic.yarnpkg.com/lang/en/) v1.x.x
|
||||
* [Go](https://golang.org/) 1.17.x
|
||||
|
||||
### Install Dependencies
|
||||
```bash
|
||||
yarn install
|
||||
```
|
||||
|
||||
The command above will download all of the dependencies necessary to get Pterodactyl assets building. After that, its as
|
||||
simple as running the command below to generate assets while you're developing. Until you've run this command at least
|
||||
once you'll likely see a 500 error on your Panel about a missing `manifest.json` file. This is generated by the commands
|
||||
below.
|
||||
|
||||
```bash
|
||||
# Build the compiled set of assets for development.
|
||||
yarn run build
|
||||
|
||||
# Build the assets automatically as they are changed. This allows you to refresh
|
||||
# the page and see the changes immediately.
|
||||
yarn run watch
|
||||
```
|
||||
|
||||
### Hot Module Reloading
|
||||
For more advanced users, we also support 'Hot Module Reloading', allowing you to quickly see changes you're making
|
||||
to the Vue template files without having to reload the page you're on. To Get started with this, you just need
|
||||
to run the command below.
|
||||
|
||||
```bash
|
||||
PUBLIC_PATH=http://192.168.1.1:8080 yarn run serve --host 192.168.1.1
|
||||
```
|
||||
|
||||
There are two _very important_ parts of this command to take note of and change for your specific environment. The first
|
||||
is the `--host` flag, which is required and should point to the machine where the `webpack-serve` server will be running.
|
||||
The second is the `PUBLIC_PATH` environment variable which is the URL pointing to the HMR server and is appended to all of
|
||||
the asset URLs used in Pterodactyl.
|
||||
|
||||
#### Development Environment
|
||||
If you're using the [`pterodactyl/development`](https://github.com/pterodactyl/development) environments, which are
|
||||
highly recommended, you can just run `yarn run serve` to run the HMR server, no additional configuration is necessary.
|
||||
|
||||
### Building for Production
|
||||
Once you have your files squared away and ready for the live server, you'll be needing to generate compiled, minified,
|
||||
and hashed assets to push live. To do so, run the command below:
|
||||
|
||||
```bash
|
||||
yarn run build:production
|
||||
```
|
||||
|
||||
This will generate a production JS bundle and associated assets, all located in `public/assets/` which will need to
|
||||
be uploaded to your server or CDN for clients to use.
|
||||
|
||||
### Running Wings
|
||||
To run `wings` in development all you need to do is set up the configuration file as normal when adding a new node, and
|
||||
then you can build and run a local version of Wings by executing `make debug` in the Wings code directory. This must
|
||||
be run on a Linux VM of some sort, you cannot run this locally on macOS or Windows.
|
22
CHANGELOG.md
22
CHANGELOG.md
|
@ -3,6 +3,28 @@ 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.
|
||||
|
||||
## v1.11.5
|
||||
### Fixed
|
||||
* Rust egg using the wrong Docker image, breaking Rust modding frameworks.
|
||||
|
||||
## v1.11.4
|
||||
### Added
|
||||
* Added support for the `server.queryport` option on the Rust egg.
|
||||
* Added support for the Carbon modding framework to the Rust egg.
|
||||
|
||||
### Changed
|
||||
* Upgraded to Laravel 10.
|
||||
* Sensitive data is no longer shown in the CopyOnClick toast notification.
|
||||
|
||||
### Fixed
|
||||
* Allow SVGs to be edited in the server's file manager.
|
||||
* Properly validate the request body when creating a backup.
|
||||
* Fixed issue with schedules running at the wrong time when the panel utilized a timezone with non-hour offsets (such as `Australia/Darwin`).
|
||||
* Fixes the log directory when running the Panel in a container.
|
||||
* Fixes the permission name used to check if a user has permission to read files/folders.
|
||||
* Fixes the ability to unset a server's description through the client API.
|
||||
* Fixed the MassActionBar on the server's file manager blocking elements below it, preventing them from being interacted with.
|
||||
|
||||
## v1.11.3
|
||||
### Changed
|
||||
* When updating a server's description through the client API, if no value is specified, the description will now remain unchanged.
|
||||
|
|
|
@ -1,77 +0,0 @@
|
|||
# Stage 1 - Builder
|
||||
FROM --platform=$TARGETOS/$TARGETARCH registry.access.redhat.com/ubi9/nodejs-18-minimal AS builder
|
||||
|
||||
USER 0
|
||||
RUN npm install -g pnpm
|
||||
|
||||
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 .npmrc .prettierrc.json package.json pnpm-lock.yaml tailwind.config.js tsconfig.json vite.config.ts .
|
||||
|
||||
RUN /opt/app-root/src/.npm-global/bin/pnpm install \
|
||||
&& /opt/app-root/src/.npm-global/bin/pnpm build \
|
||||
&& rm -rf resources/scripts .eslintignore .eslintrc.yml .npmrc package.json pnpm-lock.yaml tailwind.config.js tsconfig.json vite.config.ts node_modules
|
||||
|
||||
USER 1001
|
||||
|
||||
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 \
|
||||
# ref; https://bugzilla.redhat.com/show_bug.cgi?id=1870814
|
||||
&& microdnf reinstall -y tzdata \
|
||||
&& microdnf module -y reset php \
|
||||
&& microdnf module -y enable php:remi-8.2 \
|
||||
&& microdnf install -y composer 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 \
|
||||
&& 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=docker.io/library/caddy:latest /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"]
|
41
Dockerfile
Normal file
41
Dockerfile
Normal file
|
@ -0,0 +1,41 @@
|
|||
# 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 \
|
||||
&& mkdir -p /app/storage/logs/ \
|
||||
&& 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" ]
|
9
LICENSE
9
LICENSE
|
@ -1,9 +0,0 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2024 Skynet
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@ -1,6 +1,6 @@
|
|||
[![Logo Image](https://cdn.pterodactyl.io/logos/new/pterodactyl_logo.png)](https://pterodactyl.io)
|
||||
|
||||
![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/pterodactyl/panel/laravel.yaml?label=Tests&style=for-the-badge&branch=develop)
|
||||
![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/pterodactyl/panel/ci.yaml?label=Tests&style=for-the-badge&branch=1.0-develop)
|
||||
![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 contributors](https://img.shields.io/github/contributors/pterodactyl/panel?style=for-the-badge)
|
||||
|
|
|
@ -15,7 +15,7 @@ class InfoCommand extends Command
|
|||
/**
|
||||
* VersionCommand constructor.
|
||||
*/
|
||||
public function __construct(private ConfigRepository $config, private SoftwareVersionService $softwareVersionService)
|
||||
public function __construct(private ConfigRepository $config, private SoftwareVersionService $versionService)
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
@ -27,9 +27,9 @@ class InfoCommand extends Command
|
|||
{
|
||||
$this->output->title('Version Information');
|
||||
$this->table([], [
|
||||
['Panel Version', $this->softwareVersionService->getCurrentVersion()],
|
||||
['Latest Version', $this->softwareVersionService->getLatestPanel()],
|
||||
['Up-to-Date', $this->softwareVersionService->isLatestPanel() ? 'Yes' : $this->formatText('No', 'bg=red')],
|
||||
['Panel Version', $this->config->get('app.version')],
|
||||
['Latest Version', $this->versionService->getPanel()],
|
||||
['Up-to-Date', $this->versionService->isLatestPanel() ? 'Yes' : $this->formatText('No', 'bg=red')],
|
||||
['Unique Identifier', $this->config->get('pterodactyl.service.author')],
|
||||
], 'compact');
|
||||
|
||||
|
|
|
@ -21,6 +21,6 @@ class UpCommand extends BaseUpCommand
|
|||
return 1;
|
||||
}
|
||||
|
||||
return parent::handle();
|
||||
return parent::handle() ?? 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,10 +44,10 @@ class DeleteUserCommand extends Command
|
|||
if ($this->input->isInteractive()) {
|
||||
$tableValues = [];
|
||||
foreach ($results as $user) {
|
||||
$tableValues[] = [$user->id, $user->email, $user->username];
|
||||
$tableValues[] = [$user->id, $user->email, $user->name];
|
||||
}
|
||||
|
||||
$this->table(['User ID', 'Email', 'Username'], $tableValues);
|
||||
$this->table(['User ID', 'Email', 'Name'], $tableValues);
|
||||
if (!$deleteUser = $this->ask(trans('command/messages.user.select_search_user'))) {
|
||||
return $this->handle();
|
||||
}
|
||||
|
|
|
@ -30,6 +30,8 @@ class MakeUserCommand extends Command
|
|||
$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'));
|
||||
$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')) {
|
||||
$this->warn(trans('command/messages.user.ask_password_help'));
|
||||
|
@ -37,11 +39,12 @@ class MakeUserCommand extends Command
|
|||
$password = $this->secret(trans('command/messages.user.ask_password'));
|
||||
}
|
||||
|
||||
$user = $this->creationService->handle(compact('email', 'username', 'password', 'root_admin'));
|
||||
$user = $this->creationService->handle(compact('email', 'username', 'name_first', 'name_last', 'password', 'root_admin'));
|
||||
$this->table(['Field', 'Value'], [
|
||||
['UUID', $user->uuid],
|
||||
['Email', $user->email],
|
||||
['Username', $user->username],
|
||||
['Name', $user->name],
|
||||
['Admin', $user->root_admin ? 'Yes' : 'No'],
|
||||
]);
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ use Illuminate\Http\Response;
|
|||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Container\Container;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Prologue\Alerts\AlertsMessageBag;
|
||||
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
|
||||
|
||||
class DisplayException extends PterodactylException implements HttpExceptionInterface
|
||||
|
@ -52,6 +53,8 @@ class DisplayException extends PterodactylException implements HttpExceptionInte
|
|||
return response()->json(Handler::toArray($this), $this->getStatusCode(), $this->getHeaders());
|
||||
}
|
||||
|
||||
app(AlertsMessageBag::class)->danger($this->getMessage())->flash();
|
||||
|
||||
return redirect()->back()->withInput();
|
||||
}
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ use Pterodactyl\Exceptions\Repository\RecordNotFoundException;
|
|||
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
|
||||
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
|
||||
|
||||
final class Handler extends ExceptionHandler
|
||||
class Handler extends ExceptionHandler
|
||||
{
|
||||
/**
|
||||
* The validation parser in Laravel formats custom rules using the class name
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
<?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,
|
||||
);
|
||||
}
|
||||
}
|
14
app/Exceptions/ManifestDoesNotExistException.php
Normal file
14
app/Exceptions/ManifestDoesNotExistException.php
Normal file
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
|
||||
namespace Pterodactyl\Exceptions;
|
||||
|
||||
use Spatie\Ignition\Contracts\Solution;
|
||||
use Spatie\Ignition\Contracts\ProvidesSolution;
|
||||
|
||||
class ManifestDoesNotExistException extends \Exception implements ProvidesSolution
|
||||
{
|
||||
public function getSolution(): Solution
|
||||
{
|
||||
return new Solutions\ManifestDoesNotExistSolution();
|
||||
}
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Pterodactyl\Exceptions\Service\Egg;
|
||||
|
||||
use Pterodactyl\Exceptions\DisplayException;
|
||||
|
||||
class BadYamlFormatException extends DisplayException
|
||||
{
|
||||
}
|
25
app/Exceptions/Solutions/ManifestDoesNotExistSolution.php
Normal file
25
app/Exceptions/Solutions/ManifestDoesNotExistSolution.php
Normal file
|
@ -0,0 +1,25 @@
|
|||
<?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',
|
||||
];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
<?php
|
||||
|
||||
namespace Pterodactyl\Exceptions\Transformer;
|
||||
|
||||
use Pterodactyl\Exceptions\PterodactylException;
|
||||
|
||||
class InvalidTransformerLevelException extends PterodactylException
|
||||
{
|
||||
}
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace Pterodactyl\Extensions\Backups;
|
||||
|
||||
use Closure;
|
||||
use Aws\S3\S3Client;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Str;
|
||||
|
|
13
app/Extensions/Facades/Theme.php
Normal file
13
app/Extensions/Facades/Theme.php
Normal file
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
namespace Pterodactyl\Extensions\Facades;
|
||||
|
||||
use Illuminate\Support\Facades\Facade;
|
||||
|
||||
class Theme extends Facade
|
||||
{
|
||||
protected static function getFacadeAccessor(): string
|
||||
{
|
||||
return 'extensions.themes';
|
||||
}
|
||||
}
|
|
@ -6,6 +6,17 @@ use League\Fractal\Serializer\ArraySerializer;
|
|||
|
||||
class PterodactylSerializer extends ArraySerializer
|
||||
{
|
||||
/**
|
||||
* Serialize an item.
|
||||
*/
|
||||
public function item(?string $resourceKey, array $data): array
|
||||
{
|
||||
return [
|
||||
'object' => $resourceKey,
|
||||
'attributes' => $data,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize a collection.
|
||||
*/
|
||||
|
@ -22,17 +33,6 @@ class PterodactylSerializer extends ArraySerializer
|
|||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize an item.
|
||||
*/
|
||||
public function item(?string $resourceKey, array $data): array
|
||||
{
|
||||
return [
|
||||
'object' => $resourceKey,
|
||||
'attributes' => $data,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize a null resource.
|
||||
*/
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
namespace Pterodactyl\Extensions\Spatie\Fractalistic;
|
||||
|
||||
use League\Fractal\Scope;
|
||||
use League\Fractal\TransformerAbstract;
|
||||
use Spatie\Fractal\Fractal as SpatieFractal;
|
||||
use Pterodactyl\Transformers\Api\Transformer;
|
||||
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
||||
use League\Fractal\Pagination\IlluminatePaginatorAdapter;
|
||||
use Pterodactyl\Extensions\League\Fractal\Serializers\PterodactylSerializer;
|
||||
|
@ -32,9 +32,12 @@ class Fractal extends SpatieFractal
|
|||
|
||||
// If the resource name is not set attempt to pull it off the transformer
|
||||
// itself and set it automatically.
|
||||
$class = is_string($this->transformer) ? new $this->transformer() : $this->transformer;
|
||||
if (is_null($this->resourceName) && $class instanceof Transformer) {
|
||||
$this->resourceName = $class->getResourceName();
|
||||
if (
|
||||
is_null($this->resourceName)
|
||||
&& $this->transformer instanceof TransformerAbstract
|
||||
&& method_exists($this->transformer, 'getResourceName')
|
||||
) {
|
||||
$this->resourceName = $this->transformer->getResourceName();
|
||||
}
|
||||
|
||||
return parent::createData();
|
||||
|
|
21
app/Extensions/Themes/Theme.php
Normal file
21
app/Extensions/Themes/Theme.php
Normal file
|
@ -0,0 +1,21 @@
|
|||
<?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, '/');
|
||||
}
|
||||
}
|
87
app/Http/Controllers/Admin/ApiController.php
Normal file
87
app/Http/Controllers/Admin/ApiController.php
Normal file
|
@ -0,0 +1,87 @@
|
|||
<?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,12 +3,24 @@
|
|||
namespace Pterodactyl\Http\Controllers\Admin;
|
||||
|
||||
use Illuminate\View\View;
|
||||
use Illuminate\View\Factory as ViewFactory;
|
||||
use Pterodactyl\Http\Controllers\Controller;
|
||||
use Pterodactyl\Services\Helpers\SoftwareVersionService;
|
||||
|
||||
class BaseController extends Controller
|
||||
{
|
||||
/**
|
||||
* BaseController constructor.
|
||||
*/
|
||||
public function __construct(private SoftwareVersionService $version, private ViewFactory $view)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the admin index view.
|
||||
*/
|
||||
public function index(): View
|
||||
{
|
||||
return view('templates/base.core');
|
||||
return $this->view->make('admin.index', ['version' => $this->version]);
|
||||
}
|
||||
}
|
||||
|
|
129
app/Http/Controllers/Admin/DatabaseController.php
Normal file
129
app/Http/Controllers/Admin/DatabaseController.php
Normal file
|
@ -0,0 +1,129 @@
|
|||
<?php
|
||||
|
||||
namespace Pterodactyl\Http\Controllers\Admin;
|
||||
|
||||
use Exception;
|
||||
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');
|
||||
}
|
||||
}
|
103
app/Http/Controllers/Admin/LocationController.php
Normal file
103
app/Http/Controllers/Admin/LocationController.php
Normal file
|
@ -0,0 +1,103 @@
|
|||
<?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);
|
||||
}
|
||||
}
|
165
app/Http/Controllers/Admin/MountController.php
Normal file
165
app/Http/Controllers/Admin/MountController.php
Normal file
|
@ -0,0 +1,165 @@
|
|||
<?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);
|
||||
}
|
||||
}
|
128
app/Http/Controllers/Admin/Nests/EggController.php
Normal file
128
app/Http/Controllers/Admin/Nests/EggController.php
Normal file
|
@ -0,0 +1,128 @@
|
|||
<?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\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;
|
||||
}
|
||||
}
|
65
app/Http/Controllers/Admin/Nests/EggScriptController.php
Normal file
65
app/Http/Controllers/Admin/Nests/EggScriptController.php
Normal file
|
@ -0,0 +1,65 @@
|
|||
<?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);
|
||||
}
|
||||
}
|
74
app/Http/Controllers/Admin/Nests/EggShareController.php
Normal file
74
app/Http/Controllers/Admin/Nests/EggShareController.php
Normal file
|
@ -0,0 +1,74 @@
|
|||
<?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]);
|
||||
}
|
||||
}
|
90
app/Http/Controllers/Admin/Nests/EggVariableController.php
Normal file
90
app/Http/Controllers/Admin/Nests/EggVariableController.php
Normal file
|
@ -0,0 +1,90 @@
|
|||
<?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);
|
||||
}
|
||||
}
|
102
app/Http/Controllers/Admin/Nests/NestController.php
Normal file
102
app/Http/Controllers/Admin/Nests/NestController.php
Normal file
|
@ -0,0 +1,102 @@
|
|||
<?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');
|
||||
}
|
||||
}
|
62
app/Http/Controllers/Admin/NodeAutoDeployController.php
Normal file
62
app/Http/Controllers/Admin/NodeAutoDeployController.php
Normal file
|
@ -0,0 +1,62 @@
|
|||
<?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),
|
||||
]);
|
||||
}
|
||||
}
|
35
app/Http/Controllers/Admin/Nodes/NodeController.php
Normal file
35
app/Http/Controllers/Admin/Nodes/NodeController.php
Normal file
|
@ -0,0 +1,35 @@
|
|||
<?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]);
|
||||
}
|
||||
}
|
102
app/Http/Controllers/Admin/Nodes/NodeViewController.php
Normal file
102
app/Http/Controllers/Admin/Nodes/NodeViewController.php
Normal file
|
@ -0,0 +1,102 @@
|
|||
<?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),
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
<?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,
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
183
app/Http/Controllers/Admin/NodesController.php
Normal file
183
app/Http/Controllers/Admin/NodesController.php
Normal file
|
@ -0,0 +1,183 @@
|
|||
<?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');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
<?php
|
||||
|
||||
namespace Pterodactyl\Http\Controllers\Admin\Servers;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
38
app/Http/Controllers/Admin/Servers/ServerController.php
Normal file
38
app/Http/Controllers/Admin/Servers/ServerController.php
Normal file
|
@ -0,0 +1,38 @@
|
|||
<?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]);
|
||||
}
|
||||
}
|
117
app/Http/Controllers/Admin/Servers/ServerTransferController.php
Normal file
117
app/Http/Controllers/Admin/Servers/ServerTransferController.php
Normal file
|
@ -0,0 +1,117 @@
|
|||
<?php
|
||||
|
||||
namespace Pterodactyl\Http\Controllers\Admin\Servers;
|
||||
|
||||
use Carbon\CarbonImmutable;
|
||||
use Illuminate\Http\Request;
|
||||
use Pterodactyl\Models\Server;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Prologue\Alerts\AlertsMessageBag;
|
||||
use Pterodactyl\Models\ServerTransfer;
|
||||
use Illuminate\Database\ConnectionInterface;
|
||||
use Pterodactyl\Http\Controllers\Controller;
|
||||
use Pterodactyl\Services\Nodes\NodeJWTService;
|
||||
use Pterodactyl\Repositories\Eloquent\NodeRepository;
|
||||
use Pterodactyl\Repositories\Wings\DaemonTransferRepository;
|
||||
use Pterodactyl\Contracts\Repository\AllocationRepositoryInterface;
|
||||
|
||||
class ServerTransferController extends Controller
|
||||
{
|
||||
/**
|
||||
* ServerTransferController constructor.
|
||||
*/
|
||||
public function __construct(
|
||||
private AlertsMessageBag $alert,
|
||||
private AllocationRepositoryInterface $allocationRepository,
|
||||
private ConnectionInterface $connection,
|
||||
private DaemonTransferRepository $daemonTransferRepository,
|
||||
private NodeJWTService $nodeJWTService,
|
||||
private NodeRepository $nodeRepository
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)) {
|
||||
$this->alert->danger(trans('admin/server.alerts.transfer_not_viable'))->flash();
|
||||
|
||||
return redirect()->route('admin.servers.view.manage', $server->id);
|
||||
}
|
||||
|
||||
$server->validateTransferState();
|
||||
|
||||
$this->connection->transaction(function () use ($server, $node_id, $allocation_id, $additional_allocations) {
|
||||
// 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);
|
||||
|
||||
// Generate a token for the destination node that the source node can use to authenticate with.
|
||||
$token = $this->nodeJWTService
|
||||
->setExpiresAt(CarbonImmutable::now()->addMinutes(15))
|
||||
->setSubject($server->uuid)
|
||||
->handle($transfer->newNode, $server->uuid, 'sha256');
|
||||
|
||||
// Notify the source node of the pending outgoing transfer.
|
||||
$this->daemonTransferRepository->setServer($server)->notify($transfer->newNode, $token);
|
||||
|
||||
return $transfer;
|
||||
});
|
||||
|
||||
$this->alert->success(trans('admin/server.alerts.transfer_started'))->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]);
|
||||
}
|
||||
}
|
||||
}
|
154
app/Http/Controllers/Admin/Servers/ServerViewController.php
Normal file
154
app/Http/Controllers/Admin/Servers/ServerViewController.php
Normal file
|
@ -0,0 +1,154 @@
|
|||
<?php
|
||||
|
||||
namespace Pterodactyl\Http\Controllers\Admin\Servers;
|
||||
|
||||
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'));
|
||||
}
|
||||
}
|
273
app/Http/Controllers/Admin/ServersController.php
Normal file
273
app/Http/Controllers/Admin/ServersController.php
Normal file
|
@ -0,0 +1,273 @@
|
|||
<?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);
|
||||
}
|
||||
}
|
62
app/Http/Controllers/Admin/Settings/AdvancedController.php
Normal file
62
app/Http/Controllers/Admin/Settings/AdvancedController.php
Normal file
|
@ -0,0 +1,62 @@
|
|||
<?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');
|
||||
}
|
||||
}
|
60
app/Http/Controllers/Admin/Settings/IndexController.php
Normal file
60
app/Http/Controllers/Admin/Settings/IndexController.php
Normal file
|
@ -0,0 +1,60 @@
|
|||
<?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');
|
||||
}
|
||||
}
|
90
app/Http/Controllers/Admin/Settings/MailController.php
Normal file
90
app/Http/Controllers/Admin/Settings/MailController.php
Normal file
|
@ -0,0 +1,90 @@
|
|||
<?php
|
||||
|
||||
namespace Pterodactyl\Http\Controllers\Admin\Settings;
|
||||
|
||||
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:mailers:smtp:password') === '!e') {
|
||||
$values['mail:mailers:smtp: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);
|
||||
}
|
||||
}
|
153
app/Http/Controllers/Admin/UserController.php
Normal file
153
app/Http/Controllers/Admin/UserController.php
Normal file
|
@ -0,0 +1,153 @@
|
|||
<?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,17 +3,20 @@
|
|||
namespace Pterodactyl\Http\Controllers\Api\Application;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Webmozart\Assert\Assert;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Container\Container;
|
||||
use Pterodactyl\Http\Controllers\Controller;
|
||||
use Pterodactyl\Extensions\Spatie\Fractalistic\Fractal;
|
||||
use Pterodactyl\Transformers\Api\Application\BaseTransformer;
|
||||
|
||||
abstract class ApplicationApiController extends Controller
|
||||
{
|
||||
protected Fractal $fractal;
|
||||
protected Request $request;
|
||||
|
||||
protected Fractal $fractal;
|
||||
|
||||
/**
|
||||
* ApplicationApiController constructor.
|
||||
*/
|
||||
|
@ -44,11 +47,21 @@ abstract class ApplicationApiController extends Controller
|
|||
}
|
||||
|
||||
/**
|
||||
* Return an HTTP/201 response for the API.
|
||||
* Return an instance of an application transformer.
|
||||
*
|
||||
* @template T of \Pterodactyl\Transformers\Api\Application\BaseTransformer
|
||||
*
|
||||
* @param class-string<T> $abstract
|
||||
*
|
||||
* @return T
|
||||
*
|
||||
* @noinspection PhpDocSignatureInspection
|
||||
*/
|
||||
protected function returnAccepted(): Response
|
||||
public function getTransformer(string $abstract)
|
||||
{
|
||||
return new Response('', Response::HTTP_ACCEPTED);
|
||||
Assert::subclassOf($abstract, BaseTransformer::class);
|
||||
|
||||
return $abstract::fromRequest($this->request);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,99 +0,0 @@
|
|||
<?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();
|
||||
}
|
||||
}
|
|
@ -1,118 +0,0 @@
|
|||
<?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();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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));
|
||||
}
|
||||
}
|
|
@ -1,75 +0,0 @@
|
|||
<?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,7 +10,6 @@ use Pterodactyl\Services\Locations\LocationUpdateService;
|
|||
use Pterodactyl\Services\Locations\LocationCreationService;
|
||||
use Pterodactyl\Services\Locations\LocationDeletionService;
|
||||
use Pterodactyl\Transformers\Api\Application\LocationTransformer;
|
||||
use Pterodactyl\Exceptions\Http\QueryValueOutOfRangeHttpException;
|
||||
use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController;
|
||||
use Pterodactyl\Http\Requests\Api\Application\Locations\GetLocationRequest;
|
||||
use Pterodactyl\Http\Requests\Api\Application\Locations\GetLocationsRequest;
|
||||
|
@ -36,18 +35,13 @@ class LocationController extends ApplicationApiController
|
|||
*/
|
||||
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())
|
||||
->allowedFilters(['short', 'long'])
|
||||
->allowedSorts(['id', 'short', 'long'])
|
||||
->paginate($perPage);
|
||||
->allowedSorts(['id'])
|
||||
->paginate($request->query('per_page') ?? 50);
|
||||
|
||||
return $this->fractal->collection($locations)
|
||||
->transformWith(LocationTransformer::class)
|
||||
->transformWith($this->getTransformer(LocationTransformer::class))
|
||||
->toArray();
|
||||
}
|
||||
|
||||
|
@ -57,7 +51,7 @@ class LocationController extends ApplicationApiController
|
|||
public function view(GetLocationRequest $request, Location $location): array
|
||||
{
|
||||
return $this->fractal->item($location)
|
||||
->transformWith(LocationTransformer::class)
|
||||
->transformWith($this->getTransformer(LocationTransformer::class))
|
||||
->toArray();
|
||||
}
|
||||
|
||||
|
@ -72,7 +66,12 @@ class LocationController extends ApplicationApiController
|
|||
$location = $this->creationService->handle($request->validated());
|
||||
|
||||
return $this->fractal->item($location)
|
||||
->transformWith(LocationTransformer::class)
|
||||
->transformWith($this->getTransformer(LocationTransformer::class))
|
||||
->addMeta([
|
||||
'resource' => route('api.application.locations.view', [
|
||||
'location' => $location->id,
|
||||
]),
|
||||
])
|
||||
->respond(201);
|
||||
}
|
||||
|
||||
|
@ -87,7 +86,7 @@ class LocationController extends ApplicationApiController
|
|||
$location = $this->updateService->handle($location, $request->validated());
|
||||
|
||||
return $this->fractal->item($location)
|
||||
->transformWith(LocationTransformer::class)
|
||||
->transformWith($this->getTransformer(LocationTransformer::class))
|
||||
->toArray();
|
||||
}
|
||||
|
||||
|
@ -100,6 +99,6 @@ class LocationController extends ApplicationApiController
|
|||
{
|
||||
$this->deletionService->handle($location);
|
||||
|
||||
return $this->returnNoContent();
|
||||
return response('', 204);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,163 +0,0 @@
|
|||
<?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();
|
||||
}
|
||||
}
|
33
app/Http/Controllers/Api/Application/Nests/EggController.php
Normal file
33
app/Http/Controllers/Api/Application/Nests/EggController.php
Normal file
|
@ -0,0 +1,33 @@
|
|||
<?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,21 +3,9 @@
|
|||
namespace Pterodactyl\Http\Controllers\Api\Application\Nests;
|
||||
|
||||
use Pterodactyl\Models\Nest;
|
||||
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\Contracts\Repository\NestRepositoryInterface;
|
||||
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\StoreNestRequest;
|
||||
use Pterodactyl\Http\Requests\Api\Application\Nests\DeleteNestRequest;
|
||||
use Pterodactyl\Http\Requests\Api\Application\Nests\UpdateNestRequest;
|
||||
use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController;
|
||||
|
||||
class NestController extends ApplicationApiController
|
||||
|
@ -25,12 +13,8 @@ class NestController extends ApplicationApiController
|
|||
/**
|
||||
* NestController constructor.
|
||||
*/
|
||||
public function __construct(
|
||||
private NestCreationService $nestCreationService,
|
||||
private NestDeletionService $nestDeletionService,
|
||||
private NestUpdateService $nestUpdateService,
|
||||
private EggImporterService $eggImporterService
|
||||
) {
|
||||
public function __construct(private NestRepositoryInterface $repository)
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
|
@ -39,87 +23,20 @@ class NestController extends ApplicationApiController
|
|||
*/
|
||||
public function index(GetNestsRequest $request): array
|
||||
{
|
||||
$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);
|
||||
}
|
||||
$nests = $this->repository->paginated($request->query('per_page') ?? 50);
|
||||
|
||||
return $this->fractal->collection($nests)
|
||||
->transformWith(NestTransformer::class)
|
||||
->transformWith($this->getTransformer(NestTransformer::class))
|
||||
->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return information about a single Nest model.
|
||||
*/
|
||||
public function view(GetNestRequest $request, Nest $nest): array
|
||||
public function view(GetNestsRequest $request, Nest $nest): array
|
||||
{
|
||||
return $this->fractal->item($nest)
|
||||
->transformWith(NestTransformer::class)
|
||||
->transformWith($this->getTransformer(NestTransformer::class))
|
||||
->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,14 +3,13 @@
|
|||
namespace Pterodactyl\Http\Controllers\Api\Application\Nodes;
|
||||
|
||||
use Pterodactyl\Models\Node;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Pterodactyl\Models\Allocation;
|
||||
use Spatie\QueryBuilder\QueryBuilder;
|
||||
use Spatie\QueryBuilder\AllowedFilter;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Pterodactyl\Services\Allocations\AssignmentService;
|
||||
use Pterodactyl\Services\Allocations\AllocationDeletionService;
|
||||
use Pterodactyl\Exceptions\Http\QueryValueOutOfRangeHttpException;
|
||||
use Pterodactyl\Transformers\Api\Application\AllocationTransformer;
|
||||
use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController;
|
||||
use Pterodactyl\Http\Requests\Api\Application\Allocations\GetAllocationsRequest;
|
||||
|
@ -34,27 +33,23 @@ class AllocationController extends ApplicationApiController
|
|||
*/
|
||||
public function index(GetAllocationsRequest $request, Node $node): array
|
||||
{
|
||||
$perPage = (int) $request->query('per_page', '10');
|
||||
if ($perPage < 1 || $perPage > 100) {
|
||||
throw new QueryValueOutOfRangeHttpException('per_page', 1, 100);
|
||||
$allocations = QueryBuilder::for($node->allocations())
|
||||
->allowedFilters([
|
||||
AllowedFilter::exact('ip'),
|
||||
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');
|
||||
}
|
||||
|
||||
$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);
|
||||
}
|
||||
return $builder->where('server_id', $value);
|
||||
}),
|
||||
])
|
||||
->allowedSorts(['id', 'ip', 'port', 'server_id'])
|
||||
->paginate($perPage);
|
||||
->paginate($request->query('per_page') ?? 50);
|
||||
|
||||
return $this->fractal->collection($allocations)
|
||||
->transformWith(AllocationTransformer::class)
|
||||
->transformWith($this->getTransformer(AllocationTransformer::class))
|
||||
->toArray();
|
||||
}
|
||||
|
||||
|
@ -67,11 +62,11 @@ class AllocationController extends ApplicationApiController
|
|||
* @throws \Pterodactyl\Exceptions\Service\Allocation\PortOutOfRangeException
|
||||
* @throws \Pterodactyl\Exceptions\Service\Allocation\TooManyPortsInRangeException
|
||||
*/
|
||||
public function store(StoreAllocationRequest $request, Node $node): Response
|
||||
public function store(StoreAllocationRequest $request, Node $node): JsonResponse
|
||||
{
|
||||
$this->assignmentService->handle($node, $request->validated());
|
||||
|
||||
return $this->returnNoContent();
|
||||
return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -79,10 +74,10 @@ class AllocationController extends ApplicationApiController
|
|||
*
|
||||
* @throws \Pterodactyl\Exceptions\Service\Allocation\ServerUsingAllocationException
|
||||
*/
|
||||
public function delete(DeleteAllocationRequest $request, Node $node, Allocation $allocation): Response
|
||||
public function delete(DeleteAllocationRequest $request, Node $node, Allocation $allocation): JsonResponse
|
||||
{
|
||||
$this->deletionService->handle($allocation);
|
||||
|
||||
return $this->returnNoContent();
|
||||
return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,12 +14,8 @@ class NodeConfigurationController extends ApplicationApiController
|
|||
* to remote machines so long as an API key is provided to the machine to make the request
|
||||
* with, and the node is known.
|
||||
*/
|
||||
public function __invoke(GetNodeRequest $request, Node $node): JsonResponse|string
|
||||
public function __invoke(GetNodeRequest $request, Node $node): JsonResponse
|
||||
{
|
||||
if ($request->query('format') === 'yaml') {
|
||||
return $node->getYamlConfiguration();
|
||||
}
|
||||
|
||||
return new JsonResponse($node->getConfiguration());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,14 +3,12 @@
|
|||
namespace Pterodactyl\Http\Controllers\Api\Application\Nodes;
|
||||
|
||||
use Pterodactyl\Models\Node;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Spatie\QueryBuilder\QueryBuilder;
|
||||
use Pterodactyl\Services\Nodes\NodeUpdateService;
|
||||
use Pterodactyl\Services\Nodes\NodeCreationService;
|
||||
use Pterodactyl\Services\Nodes\NodeDeletionService;
|
||||
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\GetNodesRequest;
|
||||
use Pterodactyl\Http\Requests\Api\Application\Nodes\StoreNodeRequest;
|
||||
|
@ -36,18 +34,13 @@ class NodeController extends ApplicationApiController
|
|||
*/
|
||||
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())
|
||||
->allowedFilters(['id', 'uuid', 'name', 'fqdn', 'daemon_token_id'])
|
||||
->allowedSorts(['id', 'uuid', 'name', 'location_id', 'fqdn', 'memory', 'disk'])
|
||||
->paginate($perPage);
|
||||
->allowedFilters(['uuid', 'name', 'fqdn', 'daemon_token_id'])
|
||||
->allowedSorts(['id', 'uuid', 'memory', 'disk'])
|
||||
->paginate($request->query('per_page') ?? 50);
|
||||
|
||||
return $this->fractal->collection($nodes)
|
||||
->transformWith(NodeTransformer::class)
|
||||
->transformWith($this->getTransformer(NodeTransformer::class))
|
||||
->toArray();
|
||||
}
|
||||
|
||||
|
@ -57,7 +50,7 @@ class NodeController extends ApplicationApiController
|
|||
public function view(GetNodeRequest $request, Node $node): array
|
||||
{
|
||||
return $this->fractal->item($node)
|
||||
->transformWith(NodeTransformer::class)
|
||||
->transformWith($this->getTransformer(NodeTransformer::class))
|
||||
->toArray();
|
||||
}
|
||||
|
||||
|
@ -72,7 +65,12 @@ class NodeController extends ApplicationApiController
|
|||
$node = $this->creationService->handle($request->validated());
|
||||
|
||||
return $this->fractal->item($node)
|
||||
->transformWith(NodeTransformer::class)
|
||||
->transformWith($this->getTransformer(NodeTransformer::class))
|
||||
->addMeta([
|
||||
'resource' => route('api.application.nodes.view', [
|
||||
'node' => $node->id,
|
||||
]),
|
||||
])
|
||||
->respond(201);
|
||||
}
|
||||
|
||||
|
@ -86,10 +84,11 @@ class NodeController extends ApplicationApiController
|
|||
$node = $this->updateService->handle(
|
||||
$node,
|
||||
$request->validated(),
|
||||
$request->input('reset_secret') === true
|
||||
);
|
||||
|
||||
return $this->fractal->item($node)
|
||||
->transformWith(NodeTransformer::class)
|
||||
->transformWith($this->getTransformer(NodeTransformer::class))
|
||||
->toArray();
|
||||
}
|
||||
|
||||
|
@ -99,10 +98,10 @@ class NodeController extends ApplicationApiController
|
|||
*
|
||||
* @throws \Pterodactyl\Exceptions\Service\HasActiveServersException
|
||||
*/
|
||||
public function delete(DeleteNodeRequest $request, Node $node): Response
|
||||
public function delete(DeleteNodeRequest $request, Node $node): JsonResponse
|
||||
{
|
||||
$this->deletionService->handle($node);
|
||||
|
||||
return $this->returnNoContent();
|
||||
return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@ class NodeDeploymentController extends ApplicationApiController
|
|||
->handle($request->query('per_page'), $request->query('page'));
|
||||
|
||||
return $this->fractal->collection($nodes)
|
||||
->transformWith(NodeTransformer::class)
|
||||
->transformWith($this->getTransformer(NodeTransformer::class))
|
||||
->toArray();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,47 +0,0 @@
|
|||
<?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,
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -1,96 +0,0 @@
|
|||
<?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();
|
||||
}
|
||||
}
|
|
@ -34,7 +34,7 @@ class DatabaseController extends ApplicationApiController
|
|||
public function index(GetServerDatabasesRequest $request, Server $server): array
|
||||
{
|
||||
return $this->fractal->collection($server->databases)
|
||||
->transformWith(ServerDatabaseTransformer::class)
|
||||
->transformWith($this->getTransformer(ServerDatabaseTransformer::class))
|
||||
->toArray();
|
||||
}
|
||||
|
||||
|
@ -44,7 +44,7 @@ class DatabaseController extends ApplicationApiController
|
|||
public function view(GetServerDatabaseRequest $request, Server $server, Database $database): array
|
||||
{
|
||||
return $this->fractal->item($database)
|
||||
->transformWith(ServerDatabaseTransformer::class)
|
||||
->transformWith($this->getTransformer(ServerDatabaseTransformer::class))
|
||||
->toArray();
|
||||
}
|
||||
|
||||
|
@ -53,11 +53,11 @@ class DatabaseController extends ApplicationApiController
|
|||
*
|
||||
* @throws \Throwable
|
||||
*/
|
||||
public function resetPassword(ServerDatabaseWriteRequest $request, Server $server, Database $database): Response
|
||||
public function resetPassword(ServerDatabaseWriteRequest $request, Server $server, Database $database): JsonResponse
|
||||
{
|
||||
$this->databasePasswordService->handle($database);
|
||||
|
||||
return $this->returnNoContent();
|
||||
return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -72,19 +72,23 @@ class DatabaseController extends ApplicationApiController
|
|||
]));
|
||||
|
||||
return $this->fractal->item($database)
|
||||
->transformWith(ServerDatabaseTransformer::class)
|
||||
->transformWith($this->getTransformer(ServerDatabaseTransformer::class))
|
||||
->addMeta([
|
||||
'resource' => route('api.application.servers.databases.view', [
|
||||
'server' => $server->id,
|
||||
'database' => $database->id,
|
||||
]),
|
||||
])
|
||||
->respond(Response::HTTP_CREATED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a request to delete a specific server database from the Panel.
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function delete(ServerDatabaseWriteRequest $request, Database $database): Response
|
||||
public function delete(ServerDatabaseWriteRequest $request, Server $server, Database $database): Response
|
||||
{
|
||||
$this->databaseManagementService->delete($database);
|
||||
|
||||
return $this->returnNoContent();
|
||||
return response('', 204);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ class ExternalServerController extends ApplicationApiController
|
|||
$server = Server::query()->where('external_id', $external_id)->firstOrFail();
|
||||
|
||||
return $this->fractal->item($server)
|
||||
->transformWith(ServerTransformer::class)
|
||||
->transformWith($this->getTransformer(ServerTransformer::class))
|
||||
->toArray();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,16 +8,12 @@ use Illuminate\Http\JsonResponse;
|
|||
use Spatie\QueryBuilder\QueryBuilder;
|
||||
use Pterodactyl\Services\Servers\ServerCreationService;
|
||||
use Pterodactyl\Services\Servers\ServerDeletionService;
|
||||
use Pterodactyl\Services\Servers\BuildModificationService;
|
||||
use Pterodactyl\Services\Servers\DetailsModificationService;
|
||||
use Pterodactyl\Transformers\Api\Application\ServerTransformer;
|
||||
use Pterodactyl\Exceptions\Http\QueryValueOutOfRangeHttpException;
|
||||
use Pterodactyl\Http\Requests\Api\Application\Servers\GetServerRequest;
|
||||
use Pterodactyl\Http\Requests\Api\Application\Servers\GetServersRequest;
|
||||
use Pterodactyl\Http\Requests\Api\Application\Servers\ServerWriteRequest;
|
||||
use Pterodactyl\Http\Requests\Api\Application\Servers\StoreServerRequest;
|
||||
use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController;
|
||||
use Pterodactyl\Http\Requests\Api\Application\Servers\UpdateServerRequest;
|
||||
|
||||
class ServerController extends ApplicationApiController
|
||||
{
|
||||
|
@ -25,8 +21,6 @@ class ServerController extends ApplicationApiController
|
|||
* ServerController constructor.
|
||||
*/
|
||||
public function __construct(
|
||||
private BuildModificationService $buildModificationService,
|
||||
private DetailsModificationService $detailsModificationService,
|
||||
private ServerCreationService $creationService,
|
||||
private ServerDeletionService $deletionService
|
||||
) {
|
||||
|
@ -38,18 +32,13 @@ class ServerController extends ApplicationApiController
|
|||
*/
|
||||
public function index(GetServersRequest $request): array
|
||||
{
|
||||
$perPage = (int) $request->query('per_page', '10');
|
||||
if ($perPage < 1 || $perPage > 100) {
|
||||
throw new QueryValueOutOfRangeHttpException('per_page', 1, 100);
|
||||
}
|
||||
|
||||
$servers = QueryBuilder::for(Server::query())
|
||||
->allowedFilters(['id', 'uuid', 'uuidShort', 'name', 'owner_id', 'node_id', 'external_id'])
|
||||
->allowedSorts(['id', 'uuid', 'uuidShort', 'name', 'owner_id', 'node_id', 'status'])
|
||||
->paginate($perPage);
|
||||
->allowedFilters(['uuid', 'uuidShort', 'name', 'description', 'image', 'external_id'])
|
||||
->allowedSorts(['id', 'uuid'])
|
||||
->paginate($request->query('per_page') ?? 50);
|
||||
|
||||
return $this->fractal->collection($servers)
|
||||
->transformWith(ServerTransformer::class)
|
||||
->transformWith($this->getTransformer(ServerTransformer::class))
|
||||
->toArray();
|
||||
}
|
||||
|
||||
|
@ -59,17 +48,18 @@ class ServerController extends ApplicationApiController
|
|||
* @throws \Throwable
|
||||
* @throws \Illuminate\Validation\ValidationException
|
||||
* @throws \Pterodactyl\Exceptions\DisplayException
|
||||
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
|
||||
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
||||
* @throws \Pterodactyl\Exceptions\Service\Deployment\NoViableAllocationException
|
||||
* @throws \Pterodactyl\Exceptions\Service\Deployment\NoViableNodeException
|
||||
*/
|
||||
public function store(StoreServerRequest $request): JsonResponse
|
||||
{
|
||||
$server = $this->creationService->handle($request->validated());
|
||||
$server = $this->creationService->handle($request->validated(), $request->getDeploymentObject());
|
||||
|
||||
return $this->fractal->item($server)
|
||||
->transformWith(ServerTransformer::class)
|
||||
->respond(Response::HTTP_CREATED);
|
||||
->transformWith($this->getTransformer(ServerTransformer::class))
|
||||
->respond(201);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -78,7 +68,7 @@ class ServerController extends ApplicationApiController
|
|||
public function view(GetServerRequest $request, Server $server): array
|
||||
{
|
||||
return $this->fractal->item($server)
|
||||
->transformWith(ServerTransformer::class)
|
||||
->transformWith($this->getTransformer(ServerTransformer::class))
|
||||
->toArray();
|
||||
}
|
||||
|
||||
|
@ -86,7 +76,6 @@ class ServerController extends ApplicationApiController
|
|||
* Deletes a server.
|
||||
*
|
||||
* @throws \Pterodactyl\Exceptions\DisplayException
|
||||
* @throws \Throwable
|
||||
*/
|
||||
public function delete(ServerWriteRequest $request, Server $server, string $force = ''): Response
|
||||
{
|
||||
|
@ -94,24 +83,4 @@ class ServerController extends ApplicationApiController
|
|||
|
||||
return $this->returnNoContent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a server.
|
||||
*
|
||||
* @throws \Throwable
|
||||
* @throws \Illuminate\Validation\ValidationException
|
||||
* @throws \Pterodactyl\Exceptions\DisplayException
|
||||
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
||||
* @throws \Pterodactyl\Exceptions\Service\Deployment\NoViableAllocationException
|
||||
* @throws \Pterodactyl\Exceptions\Service\Deployment\NoViableNodeException
|
||||
*/
|
||||
public function update(UpdateServerRequest $request, Server $server): array
|
||||
{
|
||||
$server = $this->buildModificationService->handle($server, $request->validated());
|
||||
$server = $this->detailsModificationService->returnUpdatedModel()->handle($server, $request->validated());
|
||||
|
||||
return $this->fractal->item($server)
|
||||
->transformWith(ServerTransformer::class)
|
||||
->toArray();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
<?php
|
||||
|
||||
namespace Pterodactyl\Http\Controllers\Api\Application\Servers;
|
||||
|
||||
use Pterodactyl\Models\Server;
|
||||
use Pterodactyl\Services\Servers\BuildModificationService;
|
||||
use Pterodactyl\Services\Servers\DetailsModificationService;
|
||||
use Pterodactyl\Transformers\Api\Application\ServerTransformer;
|
||||
use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController;
|
||||
use Pterodactyl\Http\Requests\Api\Application\Servers\UpdateServerDetailsRequest;
|
||||
use Pterodactyl\Http\Requests\Api\Application\Servers\UpdateServerBuildConfigurationRequest;
|
||||
|
||||
class ServerDetailsController extends ApplicationApiController
|
||||
{
|
||||
/**
|
||||
* ServerDetailsController constructor.
|
||||
*/
|
||||
public function __construct(
|
||||
private BuildModificationService $buildModificationService,
|
||||
private DetailsModificationService $detailsModificationService
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the details for a specific server.
|
||||
*
|
||||
* @throws \Pterodactyl\Exceptions\DisplayException
|
||||
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
|
||||
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
||||
*/
|
||||
public function details(UpdateServerDetailsRequest $request, Server $server): array
|
||||
{
|
||||
$updated = $this->detailsModificationService->returnUpdatedModel()->handle(
|
||||
$server,
|
||||
$request->validated()
|
||||
);
|
||||
|
||||
return $this->fractal->item($updated)
|
||||
->transformWith($this->getTransformer(ServerTransformer::class))
|
||||
->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the build details for a specific server.
|
||||
*
|
||||
* @throws \Pterodactyl\Exceptions\DisplayException
|
||||
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
|
||||
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
||||
*/
|
||||
public function build(UpdateServerBuildConfigurationRequest $request, Server $server): array
|
||||
{
|
||||
$server = $this->buildModificationService->handle($server, $request->validated());
|
||||
|
||||
return $this->fractal->item($server)
|
||||
->transformWith($this->getTransformer(ServerTransformer::class))
|
||||
->toArray();
|
||||
}
|
||||
}
|
|
@ -12,7 +12,7 @@ use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController;
|
|||
class ServerManagementController extends ApplicationApiController
|
||||
{
|
||||
/**
|
||||
* SuspensionController constructor.
|
||||
* ServerManagementController constructor.
|
||||
*/
|
||||
public function __construct(
|
||||
private ReinstallServerService $reinstallServerService,
|
||||
|
@ -48,7 +48,9 @@ class ServerManagementController extends ApplicationApiController
|
|||
/**
|
||||
* Mark a server as needing to be reinstalled.
|
||||
*
|
||||
* @throws \Throwable
|
||||
* @throws \Pterodactyl\Exceptions\DisplayException
|
||||
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
|
||||
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
||||
*/
|
||||
public function reinstall(ServerWriteRequest $request, Server $server): Response
|
||||
{
|
||||
|
|
|
@ -22,7 +22,10 @@ class StartupController extends ApplicationApiController
|
|||
/**
|
||||
* Update the startup and environment settings for a specific server.
|
||||
*
|
||||
* @throws \Throwable
|
||||
* @throws \Illuminate\Validation\ValidationException
|
||||
* @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException
|
||||
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
|
||||
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
||||
*/
|
||||
public function index(UpdateServerStartupRequest $request, Server $server): array
|
||||
{
|
||||
|
@ -31,7 +34,7 @@ class StartupController extends ApplicationApiController
|
|||
->handle($server, $request->validated());
|
||||
|
||||
return $this->fractal->item($server)
|
||||
->transformWith(ServerTransformer::class)
|
||||
->transformWith($this->getTransformer(ServerTransformer::class))
|
||||
->toArray();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ class ExternalUserController extends ApplicationApiController
|
|||
$user = User::query()->where('external_id', $external_id)->firstOrFail();
|
||||
|
||||
return $this->fractal->item($user)
|
||||
->transformWith(UserTransformer::class)
|
||||
->transformWith($this->getTransformer(UserTransformer::class))
|
||||
->toArray();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,19 +2,13 @@
|
|||
|
||||
namespace Pterodactyl\Http\Controllers\Api\Application\Users;
|
||||
|
||||
use Illuminate\Support\Arr;
|
||||
use Pterodactyl\Models\User;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Spatie\QueryBuilder\QueryBuilder;
|
||||
use Spatie\QueryBuilder\AllowedFilter;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Pterodactyl\Services\Users\UserUpdateService;
|
||||
use Pterodactyl\Services\Users\UserCreationService;
|
||||
use Pterodactyl\Services\Users\UserDeletionService;
|
||||
use Pterodactyl\Transformers\Api\Application\UserTransformer;
|
||||
use Pterodactyl\Exceptions\Http\QueryValueOutOfRangeHttpException;
|
||||
use Pterodactyl\Http\Requests\Api\Application\Users\GetUserRequest;
|
||||
use Pterodactyl\Http\Requests\Api\Application\Users\GetUsersRequest;
|
||||
use Pterodactyl\Http\Requests\Api\Application\Users\StoreUserRequest;
|
||||
use Pterodactyl\Http\Requests\Api\Application\Users\DeleteUserRequest;
|
||||
|
@ -41,48 +35,24 @@ class UserController extends ApplicationApiController
|
|||
*/
|
||||
public function index(GetUsersRequest $request): array
|
||||
{
|
||||
$perPage = (int) $request->query('per_page', '10');
|
||||
if ($perPage < 1 || $perPage > 100) {
|
||||
throw new QueryValueOutOfRangeHttpException('per_page', 1, 100);
|
||||
}
|
||||
|
||||
$users = QueryBuilder::for(User::query())
|
||||
->allowedFilters([
|
||||
AllowedFilter::exact('id'),
|
||||
AllowedFilter::exact('uuid'),
|
||||
AllowedFilter::exact('external_id'),
|
||||
'username',
|
||||
'email',
|
||||
AllowedFilter::callback('*', function (Builder $builder, $value) {
|
||||
foreach (Arr::wrap($value) as $datum) {
|
||||
$datum = '%' . $datum . '%';
|
||||
$builder->where(function (Builder $builder) use ($datum) {
|
||||
$builder->where('uuid', 'LIKE', $datum)
|
||||
->orWhere('username', 'LIKE', $datum)
|
||||
->orWhere('email', 'LIKE', $datum)
|
||||
->orWhere('external_id', 'LIKE', $datum);
|
||||
});
|
||||
}
|
||||
}),
|
||||
])
|
||||
->allowedSorts(['id', 'uuid', 'username', 'email', 'admin_role_id'])
|
||||
->paginate($perPage);
|
||||
->allowedFilters(['email', 'uuid', 'username', 'external_id'])
|
||||
->allowedSorts(['id', 'uuid'])
|
||||
->paginate($request->query('per_page') ?? 50);
|
||||
|
||||
return $this->fractal->collection($users)
|
||||
->transformWith(UserTransformer::class)
|
||||
->transformWith($this->getTransformer(UserTransformer::class))
|
||||
->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a request to view a single user. Includes any relations that
|
||||
* were defined in the request.
|
||||
*
|
||||
* @throws \Illuminate\Contracts\Container\BindingResolutionException
|
||||
*/
|
||||
public function view(GetUserRequest $request, User $user): array
|
||||
public function view(GetUsersRequest $request, User $user): array
|
||||
{
|
||||
return $this->fractal->item($user)
|
||||
->transformWith(UserTransformer::class)
|
||||
->transformWith($this->getTransformer(UserTransformer::class))
|
||||
->toArray();
|
||||
}
|
||||
|
||||
|
@ -94,20 +64,22 @@ class UserController extends ApplicationApiController
|
|||
* Revocation errors are returned under the 'revocation_errors' key in the response
|
||||
* meta. If there are no errors this is an empty array.
|
||||
*
|
||||
* @throws \Illuminate\Contracts\Container\BindingResolutionException
|
||||
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
|
||||
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
||||
*/
|
||||
public function update(UpdateUserRequest $request, User $user): array
|
||||
{
|
||||
$this->updateService->setUserLevel(User::USER_LEVEL_ADMIN);
|
||||
$user = $this->updateService->handle($user, $request->validated());
|
||||
|
||||
return $this->fractal->item($user)
|
||||
->transformWith(UserTransformer::class)
|
||||
->toArray();
|
||||
$response = $this->fractal->item($user)
|
||||
->transformWith($this->getTransformer(UserTransformer::class));
|
||||
|
||||
return $response->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a new user on the system. Returns the created user and a HTTP/201
|
||||
* Store a new user on the system. Returns the created user and an HTTP/201
|
||||
* header on successful creation.
|
||||
*
|
||||
* @throws \Exception
|
||||
|
@ -118,7 +90,12 @@ class UserController extends ApplicationApiController
|
|||
$user = $this->creationService->handle($request->validated());
|
||||
|
||||
return $this->fractal->item($user)
|
||||
->transformWith(UserTransformer::class)
|
||||
->transformWith($this->getTransformer(UserTransformer::class))
|
||||
->addMeta([
|
||||
'resource' => route('api.application.users.view', [
|
||||
'user' => $user->id,
|
||||
]),
|
||||
])
|
||||
->respond(201);
|
||||
}
|
||||
|
||||
|
@ -128,10 +105,10 @@ class UserController extends ApplicationApiController
|
|||
*
|
||||
* @throws \Pterodactyl\Exceptions\DisplayException
|
||||
*/
|
||||
public function delete(DeleteUserRequest $request, User $user): Response
|
||||
public function delete(DeleteUserRequest $request, User $user): JsonResponse
|
||||
{
|
||||
$this->deletionService->handle($user);
|
||||
|
||||
return $this->returnNoContent();
|
||||
return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Pterodactyl\Http\Controllers\Api\Application;
|
||||
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Pterodactyl\Services\Helpers\SoftwareVersionService;
|
||||
|
||||
class VersionController extends ApplicationApiController
|
||||
{
|
||||
/**
|
||||
* VersionController constructor.
|
||||
*/
|
||||
public function __construct(private SoftwareVersionService $softwareVersionService)
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns version information.
|
||||
*/
|
||||
public function __invoke(): JsonResponse
|
||||
{
|
||||
return new JsonResponse($this->softwareVersionService->getVersionData());
|
||||
}
|
||||
}
|
|
@ -25,7 +25,7 @@ class AccountController extends ClientApiController
|
|||
public function index(Request $request): array
|
||||
{
|
||||
return $this->fractal->item($request->user())
|
||||
->transformWith(AccountTransformer::class)
|
||||
->transformWith($this->getTransformer(AccountTransformer::class))
|
||||
->toArray();
|
||||
}
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ class ActivityLogController extends ClientApiController
|
|||
->appends($request->query());
|
||||
|
||||
return $this->fractal->collection($activity)
|
||||
->transformWith(ActivityLogTransformer::class)
|
||||
->transformWith($this->getTransformer(ActivityLogTransformer::class))
|
||||
->toArray();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ class ApiKeyController extends ClientApiController
|
|||
public function index(ClientApiRequest $request): array
|
||||
{
|
||||
return $this->fractal->collection($request->user()->apiKeys)
|
||||
->transformWith(ApiKeyTransformer::class)
|
||||
->transformWith($this->getTransformer(ApiKeyTransformer::class))
|
||||
->toArray();
|
||||
}
|
||||
|
||||
|
@ -44,7 +44,7 @@ class ApiKeyController extends ClientApiController
|
|||
->log();
|
||||
|
||||
return $this->fractal->item($token->accessToken)
|
||||
->transformWith(ApiKeyTransformer::class)
|
||||
->transformWith($this->getTransformer(ApiKeyTransformer::class))
|
||||
->addMeta(['secret_token' => $token->plainTextToken])
|
||||
->toArray();
|
||||
}
|
||||
|
|
|
@ -2,7 +2,8 @@
|
|||
|
||||
namespace Pterodactyl\Http\Controllers\Api\Client;
|
||||
|
||||
use Pterodactyl\Transformers\Api\Transformer;
|
||||
use Webmozart\Assert\Assert;
|
||||
use Pterodactyl\Transformers\Api\Client\BaseClientTransformer;
|
||||
use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController;
|
||||
|
||||
abstract class ClientApiController extends ApplicationApiController
|
||||
|
@ -10,7 +11,7 @@ abstract class ClientApiController extends ApplicationApiController
|
|||
/**
|
||||
* Returns only the includes which are valid for the given transformer.
|
||||
*/
|
||||
protected function getIncludesForTransformer(Transformer $transformer, array $merge = []): array
|
||||
protected function getIncludesForTransformer(BaseClientTransformer $transformer, array $merge = []): array
|
||||
{
|
||||
$filtered = array_filter($this->parseIncludes(), function ($datum) use ($transformer) {
|
||||
return in_array($datum, $transformer->getAvailableIncludes());
|
||||
|
@ -34,4 +35,22 @@ abstract class ClientApiController extends ApplicationApiController
|
|||
return trim($item);
|
||||
}, explode(',', $includes));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an instance of an application transformer.
|
||||
*
|
||||
* @template T of \Pterodactyl\Transformers\Api\Client\BaseClientTransformer
|
||||
*
|
||||
* @param class-string<T> $abstract
|
||||
*
|
||||
* @return T
|
||||
*
|
||||
* @noinspection PhpDocSignatureInspection
|
||||
*/
|
||||
public function getTransformer(string $abstract)
|
||||
{
|
||||
Assert::subclassOf($abstract, BaseClientTransformer::class);
|
||||
|
||||
return $abstract::fromRequest($this->request);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ class ClientController extends ClientApiController
|
|||
public function index(GetServersRequest $request): array
|
||||
{
|
||||
$user = $request->user();
|
||||
$transformer = new ServerTransformer();
|
||||
$transformer = $this->getTransformer(ServerTransformer::class);
|
||||
|
||||
// Start the query builder and ensure we eager load any requested relationships from the request.
|
||||
$builder = QueryBuilder::for(
|
||||
|
@ -40,11 +40,6 @@ class ClientController extends ClientApiController
|
|||
AllowedFilter::custom('*', new MultiFieldServerFilter()),
|
||||
]);
|
||||
|
||||
$loweredBindings = collect($builder->getBindings())
|
||||
->map(fn ($f, $key) => is_string($f) ? strtolower($f) : $f)
|
||||
->all();
|
||||
$builder->setBindings($loweredBindings);
|
||||
|
||||
$type = $request->input('type');
|
||||
// Either return all the servers the user has access to because they are an admin `?type=admin` or
|
||||
// just return all the servers the user has access to because they are the owner or a subuser of the
|
||||
|
|
|
@ -17,7 +17,7 @@ class SSHKeyController extends ClientApiController
|
|||
public function index(ClientApiRequest $request): array
|
||||
{
|
||||
return $this->fractal->collection($request->user()->sshKeys)
|
||||
->transformWith(UserSSHKeyTransformer::class)
|
||||
->transformWith($this->getTransformer(UserSSHKeyTransformer::class))
|
||||
->toArray();
|
||||
}
|
||||
|
||||
|
@ -38,7 +38,7 @@ class SSHKeyController extends ClientApiController
|
|||
->log();
|
||||
|
||||
return $this->fractal->item($model)
|
||||
->transformWith(UserSSHKeyTransformer::class)
|
||||
->transformWith($this->getTransformer(UserSSHKeyTransformer::class))
|
||||
->toArray();
|
||||
}
|
||||
|
||||
|
|
|
@ -48,7 +48,7 @@ class ActivityLogController extends ClientApiController
|
|||
->appends($request->query());
|
||||
|
||||
return $this->fractal->collection($activity)
|
||||
->transformWith(ActivityLogTransformer::class)
|
||||
->transformWith($this->getTransformer(ActivityLogTransformer::class))
|
||||
->toArray();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -50,7 +50,7 @@ class BackupController extends ClientApiController
|
|||
$limit = min($request->query('per_page') ?? 20, 50);
|
||||
|
||||
return $this->fractal->collection($server->backups()->paginate($limit))
|
||||
->transformWith(BackupTransformer::class)
|
||||
->transformWith($this->getTransformer(BackupTransformer::class))
|
||||
->addMeta([
|
||||
'backup_count' => $this->repository->getNonFailedBackups($server)->count(),
|
||||
])
|
||||
|
@ -85,7 +85,7 @@ class BackupController extends ClientApiController
|
|||
->log();
|
||||
|
||||
return $this->fractal->item($backup)
|
||||
->transformWith(BackupTransformer::class)
|
||||
->transformWith($this->getTransformer(BackupTransformer::class))
|
||||
->toArray();
|
||||
}
|
||||
|
||||
|
@ -108,7 +108,7 @@ class BackupController extends ClientApiController
|
|||
Activity::event($action)->subject($backup)->property('name', $backup->name)->log();
|
||||
|
||||
return $this->fractal->item($backup)
|
||||
->transformWith(BackupTransformer::class)
|
||||
->transformWith($this->getTransformer(BackupTransformer::class))
|
||||
->toArray();
|
||||
}
|
||||
|
||||
|
@ -124,7 +124,7 @@ class BackupController extends ClientApiController
|
|||
}
|
||||
|
||||
return $this->fractal->item($backup)
|
||||
->transformWith(BackupTransformer::class)
|
||||
->transformWith($this->getTransformer(BackupTransformer::class))
|
||||
->toArray();
|
||||
}
|
||||
|
||||
|
|
|
@ -35,7 +35,7 @@ class DatabaseController extends ClientApiController
|
|||
public function index(GetDatabasesRequest $request, Server $server): array
|
||||
{
|
||||
return $this->fractal->collection($server->databases)
|
||||
->transformWith(DatabaseTransformer::class)
|
||||
->transformWith($this->getTransformer(DatabaseTransformer::class))
|
||||
->toArray();
|
||||
}
|
||||
|
||||
|
@ -57,7 +57,7 @@ class DatabaseController extends ClientApiController
|
|||
|
||||
return $this->fractal->item($database)
|
||||
->parseIncludes(['password'])
|
||||
->transformWith(DatabaseTransformer::class)
|
||||
->transformWith($this->getTransformer(DatabaseTransformer::class))
|
||||
->toArray();
|
||||
}
|
||||
|
||||
|
@ -79,7 +79,7 @@ class DatabaseController extends ClientApiController
|
|||
|
||||
return $this->fractal->item($database)
|
||||
->parseIncludes(['password'])
|
||||
->transformWith(DatabaseTransformer::class)
|
||||
->transformWith($this->getTransformer(DatabaseTransformer::class))
|
||||
->toArray();
|
||||
}
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue