diff --git a/.babel-plugin-macrosrc.js b/.babel-plugin-macrosrc.js new file mode 100644 index 00000000..fad1194f --- /dev/null +++ b/.babel-plugin-macrosrc.js @@ -0,0 +1,12 @@ +module.exports = { + twin: { + preset: 'styled-components', + autoCssProp: true, + config: './tailwind.config.js', + }, + styledComponents: { + pure: true, + displayName: false, + fileName: false, + }, +}; diff --git a/.babelrc b/.babelrc deleted file mode 100644 index d8a888ed..00000000 --- a/.babelrc +++ /dev/null @@ -1,8 +0,0 @@ -{ - "presets": ["es2015"], - "compact": true, - "minified": true, - "only": "public/themes/pterodactyl/js/frontend/files/src/*.js", - "sourceMaps": "inline", - "comments": false -} diff --git a/.dev/vagrant/.env.vagrant b/.dev/vagrant/.env.vagrant deleted file mode 100644 index 2427ec04..00000000 --- a/.dev/vagrant/.env.vagrant +++ /dev/null @@ -1,39 +0,0 @@ -APP_ENV=develop -APP_DEBUG=true -APP_KEY=SomeRandomString3232RandomString -APP_THEME=pterodactyl -APP_TIMEZONE=UTC -APP_CLEAR_TASKLOG=720 -APP_DELETE_MINUTES=10 -APP_URL=http://192.168.50.2/ - -DB_HOST=localhost -DB_PORT=3306 -DB_DATABASE=panel -DB_USERNAME=pterodactyl -DB_PASSWORD=pterodactyl - -CACHE_DRIVER=memcached -MEMCACHED_HOST=127.0.0.1 -SESSION_DRIVER=database - -MAIL_DRIVER=smtp -MAIL_HOST=127.0.0.1 -MAIL_PORT=1025 -MAIL_USERNAME= -MAIL_PASSWORD= -MAIL_ENCRYPTION= -MAIL_FROM=support@pterodactyl.io - -API_PREFIX=api -API_VERSION=v1 -API_DEBUG=true - -QUEUE_DRIVER=database -QUEUE_HIGH=high -QUEUE_STANDARD=standard -QUEUE_LOW=low - -SQS_KEY=aws-public -SQS_SECRET=aws-secret -SQS_QUEUE_PREFIX=aws-queue-prefix diff --git a/.dev/vagrant/mailhog.service b/.dev/vagrant/mailhog.service deleted file mode 100644 index 01334183..00000000 --- a/.dev/vagrant/mailhog.service +++ /dev/null @@ -1,13 +0,0 @@ -[Unit] -Description=Mailhog - -[Service] -# On some systems the user and group might be different. -# Some systems use `apache` as the user and group. -User=www-data -Group=www-data -Restart=on-failure -ExecStart=/usr/bin/mailhog - -[Install] -WantedBy=multi-user.target diff --git a/.dev/vagrant/mariadb.cnf b/.dev/vagrant/mariadb.cnf deleted file mode 100644 index 48b31ed8..00000000 --- a/.dev/vagrant/mariadb.cnf +++ /dev/null @@ -1,189 +0,0 @@ -# MariaDB database server configuration file. -# -# You can copy this file to one of: -# - "/etc/mysql/my.cnf" to set global options, -# - "~/.my.cnf" to set user-specific options. -# -# One can use all long options that the program supports. -# Run program with --help to get a list of available options and with -# --print-defaults to see which it would actually understand and use. -# -# For explanations see -# http://dev.mysql.com/doc/mysql/en/server-system-variables.html - -# This will be passed to all mysql clients -# It has been reported that passwords should be enclosed with ticks/quotes -# escpecially if they contain "#" chars... -# Remember to edit /etc/mysql/debian.cnf when changing the socket location. -[client] -port = 3306 -socket = /var/run/mysqld/mysqld.sock - -# Here is entries for some specific programs -# The following values assume you have at least 32M ram - -# This was formally known as [safe_mysqld]. Both versions are currently parsed. -[mysqld_safe] -socket = /var/run/mysqld/mysqld.sock -nice = 0 - -[mysqld] -# -# * Basic Settings -# -user = mysql -pid-file = /var/run/mysqld/mysqld.pid -socket = /var/run/mysqld/mysqld.sock -port = 3306 -basedir = /usr -datadir = /var/lib/mysql -tmpdir = /tmp -lc_messages_dir = /usr/share/mysql -lc_messages = en_US -skip-external-locking -# -# Instead of skip-networking the default is now to listen only on -# localhost which is more compatible and is not less secure. -bind-address = 0.0.0.0 -# -# * Fine Tuning -# -max_connections = 100 -connect_timeout = 5 -wait_timeout = 600 -max_allowed_packet = 16M -thread_cache_size = 128 -sort_buffer_size = 4M -bulk_insert_buffer_size = 16M -tmp_table_size = 32M -max_heap_table_size = 32M -# -# * MyISAM -# -# This replaces the startup script and checks MyISAM tables if needed -# the first time they are touched. On error, make copy and try a repair. -myisam_recover_options = BACKUP -key_buffer_size = 128M -#open-files-limit = 2000 -table_open_cache = 400 -myisam_sort_buffer_size = 512M -concurrent_insert = 2 -read_buffer_size = 2M -read_rnd_buffer_size = 1M -# -# * Query Cache Configuration -# -# Cache only tiny result sets, so we can fit more in the query cache. -query_cache_limit = 128K -query_cache_size = 64M -# for more write intensive setups, set to DEMAND or OFF -#query_cache_type = DEMAND -# -# * Logging and Replication -# -# Both location gets rotated by the cronjob. -# Be aware that this log type is a performance killer. -# As of 5.1 you can enable the log at runtime! -#general_log_file = /var/log/mysql/mysql.log -#general_log = 1 -# -# Error logging goes to syslog due to /etc/mysql/conf.d/mysqld_safe_syslog.cnf. -# -# we do want to know about network errors and such -log_warnings = 2 -# -# Enable the slow query log to see queries with especially long duration -#slow_query_log[={0|1}] -slow_query_log_file = /var/log/mysql/mariadb-slow.log -long_query_time = 10 -#log_slow_rate_limit = 1000 -log_slow_verbosity = query_plan - -#log-queries-not-using-indexes -#log_slow_admin_statements -# -# The following can be used as easy to replay backup logs or for replication. -# note: if you are setting up a replication slave, see README.Debian about -# other settings you may need to change. -#server-id = 1 -#report_host = master1 -#auto_increment_increment = 2 -#auto_increment_offset = 1 -log_bin = /var/log/mysql/mariadb-bin -log_bin_index = /var/log/mysql/mariadb-bin.index -# not fab for performance, but safer -#sync_binlog = 1 -expire_logs_days = 10 -max_binlog_size = 100M -# slaves -#relay_log = /var/log/mysql/relay-bin -#relay_log_index = /var/log/mysql/relay-bin.index -#relay_log_info_file = /var/log/mysql/relay-bin.info -#log_slave_updates -#read_only -# -# If applications support it, this stricter sql_mode prevents some -# mistakes like inserting invalid dates etc. -#sql_mode = NO_ENGINE_SUBSTITUTION,TRADITIONAL -# -# * InnoDB -# -# InnoDB is enabled by default with a 10MB datafile in /var/lib/mysql/. -# Read the manual for more InnoDB related options. There are many! -default_storage_engine = InnoDB -# you can't just change log file size, requires special procedure -#innodb_log_file_size = 50M -innodb_buffer_pool_size = 256M -innodb_log_buffer_size = 8M -innodb_file_per_table = 1 -innodb_open_files = 400 -innodb_io_capacity = 400 -innodb_flush_method = O_DIRECT -# -# * Security Features -# -# Read the manual, too, if you want chroot! -# chroot = /var/lib/mysql/ -# -# For generating SSL certificates I recommend the OpenSSL GUI "tinyca". -# -# ssl-ca=/etc/mysql/cacert.pem -# ssl-cert=/etc/mysql/server-cert.pem -# ssl-key=/etc/mysql/server-key.pem - -# -# * Galera-related settings -# -[galera] -# Mandatory settings -#wsrep_on=ON -#wsrep_provider= -#wsrep_cluster_address= -#binlog_format=row -#default_storage_engine=InnoDB -#innodb_autoinc_lock_mode=2 -# -# Allow server to accept connections on all interfaces. -# -#bind-address=0.0.0.0 -# -# Optional setting -#wsrep_slave_threads=1 -#innodb_flush_log_at_trx_commit=0 - -[mysqldump] -quick -quote-names -max_allowed_packet = 16M - -[mysql] -#no-auto-rehash # faster start of mysql but no tab completion - -[isamchk] -key_buffer = 16M - -# -# * IMPORTANT: Additional settings that can override those from this file! -# The files must end with '.cnf', otherwise they'll be ignored. -# -!includedir /etc/mysql/conf.d/ diff --git a/.dev/vagrant/motd.txt b/.dev/vagrant/motd.txt deleted file mode 100644 index 22089d55..00000000 --- a/.dev/vagrant/motd.txt +++ /dev/null @@ -1,17 +0,0 @@ -##################################################### - Pterodactyl Panel Vagrant VM - -Install: /var/www/html/pterodactyl -Ports: - Panel: 80 (50080 on host) - MailHog: 8025 (58025 on host) - MySQL: 3306 (53306 on host) - -Default panel users: - user: admin passwd: Ptero123 (admin user) - user: user passwd: Ptero123 (standard user) - -MySQL is accessible using root/pterodactyl or pterodactyl/pterodactyl - -Service for pteroq and mailhog are running -##################################################### diff --git a/.dev/vagrant/provision.sh b/.dev/vagrant/provision.sh deleted file mode 100644 index 38dc27ad..00000000 --- a/.dev/vagrant/provision.sh +++ /dev/null @@ -1,84 +0,0 @@ -#!/bin/bash - -echo "Provisioning development environment for Pterodactyl Panel." -cp /var/www/html/pterodactyl/.dev/vagrant/motd.txt /etc/motd -chmod -x /etc/update-motd.d/10-help-text /etc/update-motd.d/51-cloudguest - -apt-get install -y software-properties-common > /dev/null - -echo "Add the ondrej/php ppa repository" -add-apt-repository -y ppa:ondrej/php > /dev/null -echo "Add the mariadb repository" -curl -sS https://downloads.mariadb.com/MariaDB/mariadb_repo_setup | bash > /dev/null - -apt-get update > /dev/null - -echo "Install the dependencies" -export DEBIAN_FRONTEND=noninteractive -# set the mariadb root password because mariadb asks for it -debconf-set-selections <<< 'mariadb-server-5.5 mysql-server/root_password password pterodactyl' -debconf-set-selections <<< 'mariadb-server-5.5 mysql-server/root_password_again password pterodactyl' -# actually install -apt-get install -y php7.2 php7.2-cli php7.2-gd php7.2-mysql php7.2-pdo php7.2-mbstring php7.2-tokenizer php7.2-bcmath php7.2-xml php7.2-fpm php7.2-memcached php7.2-curl php7.2-zip php-xdebug mariadb-server nginx curl tar unzip git memcached > /dev/null - -echo "Install composer" -curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer - -echo "Install and run mailhog" -curl -sL -o /usr/bin/mailhog https://github.com/mailhog/MailHog/releases/download/v1.0.0/MailHog_linux_amd64 -chmod +x /usr/bin/mailhog -cp /var/www/html/pterodactyl/.dev/vagrant/mailhog.service /etc/systemd/system/ -systemctl enable mailhog.service -systemctl start mailhog - -echo "Configure xDebug" -cp /var/www/html/pterodactyl/.dev/vagrant/xdebug.ini /etc/php/7.2/mods-available/ -systemctl restart php7.2-fpm - -echo "Configure nginx" -cp /var/www/html/pterodactyl/.dev/vagrant/pterodactyl.conf /etc/nginx/sites-available/ -rm /etc/nginx/sites-available/default -ln -s /etc/nginx/sites-available/pterodactyl.conf /etc/nginx/sites-enabled/pterodactyl.conf -systemctl restart nginx - -echo "Setup database" -# Replace default config with custom one to bind mysql to 0.0.0.0 to make it accessible from the host -cp /var/www/html/pterodactyl/.dev/vagrant/mariadb.cnf /etc/mysql/my.cnf -systemctl restart mariadb -mysql -u root -ppterodactyl << SQL -CREATE DATABASE panel; -GRANT ALL PRIVILEGES ON panel.* TO 'pterodactyl'@'%' IDENTIFIED BY 'pterodactyl' WITH GRANT OPTION; -GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY 'pterodactyl' WITH GRANT OPTION; -FLUSH PRIVILEGES; -SQL - -echo "Setup pterodactyl queue worker service" -cp /var/www/html/pterodactyl/.dev/vagrant/pteroq.service /etc/systemd/system/ -systemctl enable pteroq.service - - -echo "Setup panel with base settings" -cp /var/www/html/pterodactyl/.dev/vagrant/.env.vagrant /var/www/html/pterodactyl/.env -cd /var/www/html/pterodactyl -chmod -R 755 storage/* bootstrap/cache -composer install --no-progress -php artisan key:generate --force -php artisan migrate -php artisan db:seed -php artisan p:user:make --name-first Test --name-last Admin --username admin --email testadmin@pterodactyl.io --password Ptero123 --admin 1 -php artisan p:user:make --name-first Test --name-last User --username user --email testuser@pterodactyl.io --password Ptero123 --admin 0 - -echo "Add queue cronjob and start queue worker" -(crontab -l 2>/dev/null; echo "* * * * * php /var/www/html/pterodactyl/artisan schedule:run >> /dev/null 2>&1") | crontab - -systemctl start pteroq - -echo " ----------------" -echo "Provisioning is completed." -echo "The panel should be available at http://localhost:50080/" -echo "You may use the default admin user to login: admin/Ptero123" -echo "A normal user has also been created: user/Ptero123" -echo "MailHog is available at http://localhost:58025/" -echo "Connect to the database using root/pterodactyl or pterodactyl/pterodactyl on localhost:53306" -echo "If you want to access the panel using http://pterodactyl.app you can use the vagrant-dns plugin" -echo "Install it with 'vagrant plugin install vagrant-dns', then run 'vagrant dns --install' once" -echo "On first use you'll have to manually start vagrant-dns with 'vagrant dns --start'" diff --git a/.dev/vagrant/pterodactyl.conf b/.dev/vagrant/pterodactyl.conf deleted file mode 100644 index 343cbad5..00000000 --- a/.dev/vagrant/pterodactyl.conf +++ /dev/null @@ -1,51 +0,0 @@ -# If using Ubuntu this file should be placed in: -# /etc/nginx/sites-available/ -# -# If using CentOS this file should be placed in: -# /etc/nginx/conf.d/ -# -server { - listen 80; - server_name 0.0.0.0; - - root /var/www/html/pterodactyl/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 unix:/var/run/php/php7.2-fpm.sock; - 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; - } -} diff --git a/.dev/vagrant/pteroq.service b/.dev/vagrant/pteroq.service deleted file mode 100644 index 7828ee91..00000000 --- a/.dev/vagrant/pteroq.service +++ /dev/null @@ -1,20 +0,0 @@ -# Pterodactyl Queue Worker File -# ---------------------------------- -# File should be placed in: -# /etc/systemd/system -# -# nano /etc/systemd/system/pteroq.service - -[Unit] -Description=Pterodactyl Queue Worker - -[Service] -# On some systems the user and group might be different. -# Some systems use `apache` as the user and group. -User=www-data -Group=www-data -Restart=on-failure -ExecStart=/usr/bin/php /var/www/html/pterodactyl/artisan queue:work database --queue=high,standard,low --sleep=3 --tries=3 - -[Install] -WantedBy=multi-user.target diff --git a/.dev/vagrant/xdebug.ini b/.dev/vagrant/xdebug.ini deleted file mode 100644 index 1725b8e8..00000000 --- a/.dev/vagrant/xdebug.ini +++ /dev/null @@ -1,10 +0,0 @@ -zend_extension=xdebug.so - -xdebug.remote_enable=1 -xdebug.remote_connect_back=1 -xdebug.remote_port=9000 -xdebug.scream=0 -xdebug.show_local_vars=1 -xdebug.idekey=PHPSTORM - -xdebug.remote_log=/tmp/xdebug.log \ No newline at end of file diff --git a/.editorconfig b/.editorconfig index bc49d523..1ba6cf9f 100644 --- a/.editorconfig +++ b/.editorconfig @@ -8,5 +8,8 @@ indent_size = 4 charset = utf-8 trim_trailing_whitespace = true +[.*yml] +indent_size = 2 + [*.md] trim_trailing_whitespace = false diff --git a/.env.travis b/.env.ci similarity index 77% rename from .env.travis rename to .env.ci index e0040b94..e99f4669 100644 --- a/.env.travis +++ b/.env.ci @@ -2,13 +2,13 @@ APP_ENV=testing APP_DEBUG=true APP_KEY=SomeRandomString3232RandomString APP_THEME=pterodactyl -APP_TIMEZONE=UTC +APP_TIMEZONE=America/Los_Angeles APP_URL=http://localhost/ TESTING_DB_HOST=127.0.0.1 -TESTING_DB_DATABASE=travis +TESTING_DB_DATABASE=panel_test TESTING_DB_USERNAME=root -TESTING_DB_PASSWORD="" +TESTING_DB_PASSWORD= CACHE_DRIVER=array SESSION_DRIVER=array diff --git a/.env.dusk b/.env.dusk new file mode 100644 index 00000000..237f61d3 --- /dev/null +++ b/.env.dusk @@ -0,0 +1,26 @@ +APP_ENV=local +APP_DEBUG=false +APP_KEY=NDWgIKKi9ovNK1PXZpzfNVSBdfCXGb5i +APP_JWT_KEY=test1234 +APP_TIMEZONE=America/Los_Angeles +APP_URL=http://pterodactyl.local + +CACHE_DRIVER=file +SESSION_DRIVER=file + +HASHIDS_SALT=IqRr0g82tCTeuyxGs8RV +HASHIDS_LENGTH=8 + +MAIL_DRIVER=log +MAIL_FROM=support@pterodactyl.io +QUEUE_DRIVER=array + +APP_SERVICE_AUTHOR=testing@pterodactyl.io +MAIL_FROM_NAME="Pterodactyl Panel" +RECAPTCHA_ENABLED=false + +DB_CONNECTION=testing +TESTING_DB_HOST=192.168.1.202 +TESTING_DB_DATABASE=panel_test +TESTING_DB_USERNAME=panel_test +TESTING_DB_PASSWORD=Test1234 diff --git a/.env.example b/.env.example index 0b59de0c..9062de21 100644 --- a/.env.example +++ b/.env.example @@ -28,4 +28,4 @@ MAIL_FROM=no-reply@example.com QUEUE_HIGH=high QUEUE_STANDARD=standard -QUEUE_LOW=low \ No newline at end of file +QUEUE_LOW=low diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000..985087d1 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,2 @@ +github: [DaneEveritt] +custom: ["https://paypal.me/PterodactylSoftware"] diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..06582c0e --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,87 @@ +name: "Release" + +on: + push: + tags: + - 'v*' + +jobs: + release: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v1 + with: + node-version: '12' + + - name: Create release branch and bump version + env: + REF: ${{ github.ref }} + run: | + BRANCH=release/${REF:10} + git config --local user.email "ci@pterodactyl.io" + git config --local user.name "Pterodactyl CI" + git checkout -b $BRANCH + git push -u origin $BRANCH + sed -i "s/ 'version' => 'canary',/ 'version' => '${REF:11}',/" config/app.php + git add config/app.php + git commit -m "bump version for release" + git push + + - name: Build assets + run: | + yarn install + yarn run build:production + + - name: Create release archive + run: | + rm -rf node_modules/ test/ codecov.yml CODE_OF_CONDUCT.md CONTRIBUTING.md phpunit.dusk.xml phpunit.xml Vagrantfile + tar -czf panel.tar.gz * .env.example + + - name: Extract changelog + id: extract_changelog + env: + REF: ${{ github.ref }} + run: | + sed -n "/^## ${REF:10}/,/^## /{/^## /b;p}" CHANGELOG.md > ./RELEASE_CHANGELOG + echo ::set-output name=version_name::`sed -nr "s/^## (${REF:10} .*)$/\1/p" CHANGELOG.md` + + - name: Create checksum and add to changelog + run: | + SUM=`sha256sum panel.tar.gz` + echo -e "\n#### SHA256 Checksum\n\n\`\`\`\n$SUM\n\`\`\`\n" >> ./RELEASE_CHANGELOG + echo $SUM > checksum.txt + + - name: Create Release + id: create_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ github.ref }} + release_name: ${{ steps.extract_changelog.outputs.version_name }} + body_path: ./RELEASE_CHANGELOG + draft: true + prerelease: ${{ contains(github.ref, 'beta') || contains(github.ref, 'alpha') }} + + - name: Upload binary + id: upload-release-archive + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: panel.tar.gz + asset_name: panel.tar.gz + asset_content_type: application/gzip + + - name: Upload checksum + id: upload-release-checksum + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: ./checksum.txt + asset_name: checksum.txt + asset_content_type: text/plain diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 00000000..00c37cc5 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,67 @@ +name: tests +on: + push: + branch-ignore: + - 'master' + - 'release/**' + pull_request: +jobs: + integration_tests: + if: "!contains(github.event.head_commit.message, '[skip ci]') && !contains(github.event.head_commit.message, '[ci skip]')" + runs-on: ubuntu-latest + services: + mysql: + image: mysql:5.7 + env: + MYSQL_ALLOW_EMPTY_PASSWORD: yes + MYSQL_DATABASE: panel_test + ports: + - 3306 + options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 + strategy: + fail-fast: true + matrix: + php: [7.3, 7.4] + name: PHP ${{ matrix.php }} + steps: + - name: checkout + uses: actions/checkout@v2 + - name: get cache directory + id: composer-cache + run: | + echo "::set-output name=dir::$(composer config cache-files-dir)" + - name: cache dependencies + uses: actions/cache@v2 + with: + path: | + ~/.php_cs.cache + ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-cache-${{ matrix.php }}-${{ hashFiles('**.composer.lock') }} + restore-keys: | + ${{ runner.os }}-cache-${{ matrix.php }}- + - name: setup + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + extensions: cli, openssl, gd, mysql, pdo, mbstring, tokenizer, bcmath, xml, curl, zip + tools: composer:v1 + coverage: none + - name: configure + run: cp .env.ci .env + - name: install dependencies + run: composer install --prefer-dist --no-interaction --no-progress + - name: run cs-fixer + run: vendor/bin/php-cs-fixer fix --dry-run --diff --diff-format=udiff + continue-on-error: true + - name: execute unit tests + run: vendor/bin/phpunit --bootstrap bootstrap/app.php tests/Unit + if: ${{ always() }} + env: + DB_CONNECTION: testing + TESTING_DB_HOST: UNIT_NO_DB + - name: execute integration tests + run: vendor/bin/phpunit tests/Integration + if: ${{ always() }} + env: + TESTING_DB_PORT: ${{ job.services.mysql.ports[3306] }} + TESTING_DB_USERNAME: root diff --git a/.gitignore b/.gitignore index 9fc4aee5..58250120 100644 --- a/.gitignore +++ b/.gitignore @@ -7,15 +7,12 @@ storage/framework/* /.idea /nbproject -package-lock.json -composer.lock node_modules - -_ide_helper_models.php +*.log _ide_helper.php - -sami.phar -/.sami +.phpstorm.meta.php +.php_cs.cache +public/assets/manifest.json # For local development with docker # Remove if we ever put the Dockerfile in the repo @@ -32,3 +29,7 @@ coverage.xml # Vagrant *.log +resources/lang/locales.js +resources/assets/pterodactyl/scripts/helpers/ziggy.js +resources/assets/scripts/helpers/ziggy.js +.phpunit.result.cache diff --git a/.php_cs b/.php_cs index c854af47..e72e8e70 100644 --- a/.php_cs +++ b/.php_cs @@ -33,18 +33,21 @@ return PhpCsFixer\Config::create() 'new_with_braces' => false, 'no_alias_functions' => true, 'no_multiline_whitespace_before_semicolons' => true, + 'no_superfluous_phpdoc_tags' => false, 'no_unreachable_default_argument_value' => true, 'no_useless_return' => true, 'not_operator_with_successor_space' => true, 'ordered_imports' => [ 'sortAlgorithm' => 'length', ], - 'phpdoc_align' => ['tags' => ['param']], + 'phpdoc_align' => false, 'phpdoc_separation' => false, 'protected_to_private' => false, 'psr0' => ['dir' => 'app'], 'psr4' => true, 'random_api_migration' => true, + 'single_line_throw' => false, + 'single_trait_insert_per_statement' => false, 'standardize_not_equals' => true, 'ternary_to_null_coalescing' => true, 'yoda_style' => [ diff --git a/.travis.yml b/.travis.yml index ff915fa1..06d9924c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,7 +13,7 @@ env: - TEST_SUITE=Coverage - TEST_SUITE=Integration php: - - 7.2 + - 7.4 sudo: false cache: directories: @@ -32,13 +32,5 @@ script: - if [ "$TEST_SUITE" = "Integration" ]; then vendor/bin/phpunit tests/Integration; fi; notifications: email: false - webhooks: - urls: - - https://misc.schrej.net/travistodiscord/pterodev.php - on_success: change - on_failure: always - on_error: always - on_cancel: always - on_start: never after_success: - bash <(curl -s https://codecov.io/bash) diff --git a/.yarnclean b/.yarnclean new file mode 100644 index 00000000..7bbe5eba --- /dev/null +++ b/.yarnclean @@ -0,0 +1 @@ +@types/react-native diff --git a/BUILDING.md b/BUILDING.md new file mode 100644 index 00000000..286cc3c6 --- /dev/null +++ b/BUILDING.md @@ -0,0 +1,57 @@ +# Local Development +Pterodactyl is now powered by Vuejs and Tailwindcss and uses webpack at its core to generate compiled assets. Release +versions of Pterodactyl will include pre-compiled, minified, and hashed assets ready-to-go. + +However, if you are interested in running custom themes or making modifications to the Vue files you'll need a build +system in place to generate these compiled assets. To get your environment setup, you'll first need to install at least Nodejs +`8`, and it is _highly_ recommended that you also install [Yarn](https://yarnpkg.com) to manage your `node_modules`. + +### Install Dependencies +```bash +yarn install +``` + +The command above will download all of the dependencies necessary to get Pterodactyl assets building. After that, its as +simple as running the command below to generate assets while you're developing. + +```bash +# build the compiled assets for development +yarn run build + +# build the assets automatically when files are modified +yarn run watch +``` + + +### Hot Module Reloading +For more advanced users, we also support 'Hot Module Reloading', allowing you to quickly see changes you're making +to the Vue template files without having to reload the page you're on. To Get started with this, you just need +to run the command below. + +```bash +PUBLIC_PATH=http://192.168.1.1:8080 yarn run serve --host 192.168.1.1 +``` + +There are two _very important_ parts of this command to take note of and change for your specific environment. The first +is the `--host` flag, which is required and should point to the machine where the `webpack-serve` server will be running. +The second is the `PUBLIC_PATH` environment variable which is the URL pointing to the HMR server and is appended to all of +the asset URLs used in Pterodactyl. + +#### Vagrant +If you want to use HMR with our Vagrant image, you can use `yarn run v:serve` as a shortcut for the correct parameters. +In order to have proper file change detection you can use the [`vagrant-notify-forwarder`](https://github.com/mhallin/vagrant-notify-forwarder) to notify file events from the host to the VM. +```sh +vagrant plugin install vagrant-notify-forwarder +vagrant reload +``` + +### Building for Production +Once you have your files squared away and ready for the live server, you'll be needing to generate compiled, minified, and +hashed assets to push live. To do so, run the command below: + +```bash +yarn run build:production +``` + +This will generate a production ready `bundle.js` and `bundle.css` as well as a `manifest.json` and store them in +the `/public/assets` directory where they can then be access by clients, and read by the Panel. diff --git a/CHANGELOG.md b/CHANGELOG.md index 36586d88..faf955b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,49 @@ 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.0.0 +Pterodactyl 1.0 represents the culmination of over two years of work, almost 2,000 commits, endless bug and feature requests, and a dream that +has been in the making since 2013. 🎉 + +Due to the sheer size and timeline of this release I've massively truncated the listing below. There are numerous smaller +bug fixes and changes that would simply be too difficult to keep track of here. Please feel free to browse through the releases +tab for this repository to see more specific changes that have been made. + +### Added +* Adds a new client-facing API allowing a user to control all aspects of their individual servers, or servers +which they have been granted access to as a subuser. +* Adds the ability for backups to be created for a server both manually and via a scheduled task. +* Adds the ability for users to modify their server allocations on the fly and include notes for each allocation. +* Adds the ability for users to generate recovery tokens for 2FA protected logins which can be used in place of +a code should their device be inaccessible. +* Adds support for transfering servers between Nodes via the Panel. +* Adds the ability to assign specific CPU cores to a server (CPU Pinning) process. +* Server owners can now reinstall their assigned server egg automatically with a button on the frontend. + +### Changed +* The entire user frontend has been replaced with a responsive, React backed design implemented using Tailwind CSS. +* Replaces a large amount of complex daemon authentication logic by funneling most API calls through the Panel, and using +JSON Web Tokens where necessary to handle one-time direct authentication with Wings. +* Frontend server listing now includes a toggle to show or hide servers which an administrator has access to, rather +than always showing all servers on the system when logged into an admin account. +* We've replaced Ace Editor on the frontend with a better solution to allow lighter builds and more end-user functionality. +* Server permissions have been overhauled to be both easier to understand in the codebase, and allows plugins to better +hook into the permission system. + +### Removed +* Removes large swaths of code complexity and confusing interface designs that caused a lot of pain to new developers +trying to jump into the codebase. We've simplified this to stick to more established Laravel design standards to make +it easy to parse through the project and make contributions. + +## v0.7.19 (Derelict Dermodactylus) +### Fixed +* **[Security]** Fixes XSS in the admin area's server owner selection. + +## v0.7.18 (Derelict Dermodactylus) +### Fixed +* **[Security]** Re-addressed missed endpoint that would not properly limit a user account to 5 API keys. +* **[Security]** Addresses a Client API vulnerability that would allow a user to list all servers on the system ([`GHSA-6888-7f3w-92jx`](https://github.com/pterodactyl/panel/security/advisories/GHSA-6888-7f3w-92jx)) + ## v0.7.17 (Derelict Dermodactylus) ### Fixed * Limited accounts to 5 API keys at a time. diff --git a/Dockerfile b/Dockerfile index 22c151b3..f00d54d5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,9 +1,18 @@ -FROM alpine:3.8 +FROM php:7.4-fpm-alpine WORKDIR /app -RUN apk add --no-cache --update ca-certificates certbot nginx dcron curl tini php7 php7-bcmath php7-common php7-dom php7-fpm php7-gd php7-mbstring php7-openssl php7-zip php7-pdo php7-phar php7-json php7-pdo_mysql php7-session php7-ctype php7-tokenizer php7-zlib php7-simplexml php7-fileinfo supervisor \ - && curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer +RUN apk add --no-cache --update ca-certificates dcron curl git supervisor tar unzip nginx libpng-dev libxml2-dev libzip-dev certbot yarn; \ + docker-php-ext-install bcmath; \ + docker-php-ext-install gd; \ + docker-php-ext-install mbstring; \ + docker-php-ext-install pdo; \ + docker-php-ext-install pdo_mysql; \ + docker-php-ext-install tokenizer; \ + docker-php-ext-install xml; \ + docker-php-ext-configure zip --with-libzip=/usr/include; \ + docker-php-ext-install zip; \ + curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer COPY . ./ @@ -12,15 +21,16 @@ RUN cp .env.example .env \ && rm .env \ && chown -R nginx:nginx . && chmod -R 777 storage/* bootstrap/cache -RUN cp .dev/docker/default.conf /etc/nginx/conf.d/default.conf \ - && cp .dev/docker/www.conf /etc/php7/php-fpm.d/www.conf \ - && cat .dev/docker/supervisord.conf > /etc/supervisord.conf \ - && echo "* * * * * /usr/bin/php /app/artisan schedule:run >> /dev/null 2>&1" >> /var/spool/cron/crontabs/root \ +RUN cp docker/default.conf /etc/nginx/conf.d/default.conf \ + && cat docker/www.conf > /usr/local/etc/php-fpm.d/www.conf \ + && rm /usr/local/etc/php-fpm.d/www.conf.default \ + && cat docker/supervisord.conf > /etc/supervisord.conf \ + && echo "* * * * * /usr/local/bin/php /app/artisan schedule:run >> /dev/null 2>&1" >> /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 EXPOSE 80 443 -ENTRYPOINT ["/bin/ash", ".dev/docker/entrypoint.sh"] +ENTRYPOINT ["/bin/ash", "docker/entrypoint.sh"] -CMD [ "supervisord", "-n", "-c", "/etc/supervisord.conf" ] \ No newline at end of file +CMD [ "supervisord", "-n", "-c", "/etc/supervisord.conf" ] diff --git a/README.md b/README.md index 8ff13e8c..df1b21d4 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,47 @@ [![Logo Image](https://cdn.pterodactyl.io/logos/new/pterodactyl_logo.png)](https://pterodactyl.io) -[![Build status](https://img.shields.io/travis/pterodactyl/panel/develop.svg?style=flat-square)](https://travis-ci.org/pterodactyl/panel) -[![StyleCI](https://styleci.io/repos/47508644/shield?branch=develop)](https://styleci.io/repos/47508644) -[![Codecov](https://img.shields.io/codecov/c/github/pterodactyl/panel/develop.svg?style=flat-square)](https://codecov.io/gh/Pterodactyl/Panel) -[![Discord](https://img.shields.io/discord/122900397965705216.svg?style=flat-square&label=Discord)](https://pterodactyl.io/discord) +![GitHub Workflow Status](https://img.shields.io/github/workflow/status/pterodactyl/panel/tests?label=Tests&style=for-the-badge) +![Discord](https://img.shields.io/discord/122900397965705216?label=Discord&logo=Discord&logoColor=white&style=for-the-badge) +![GitHub Releases](https://img.shields.io/github/downloads/pterodactyl/panel/latest/total?style=for-the-badge) +![GitHub Pre-Releases](https://img.shields.io/github/downloads-pre/pterodactyl/panel/v1.0.0-rc.7/total?style=for-the-badge) +![GitHub contributors](https://img.shields.io/github/contributors/pterodactyl/panel?style=for-the-badge) # Pterodactyl Panel +Pterodactyl is an open-source game server management panel built with PHP 7, React, and Go. Designed with security +in mind, Pterodactyl runs all game servers in isolated Docker container while exposing a beautiful and intuitive +UI to end users. -Pterodactyl is the open-source game server management panel built with PHP7, Nodejs, and Go. Designed with security in mind, Pterodactyl runs all game servers in isolated Docker containers while exposing a beautiful and intuitive UI to administrators and users. -What more are you waiting for? Make game servers a first class citizen on your platform today. +Stop settling for less. Make game servers a first class citizen on your platform. -![Image](https://cdn.pterodactyl.io/site-assets/mockup-macbook-grey.png) +![Image](https://cdn.pterodactyl.io/site-assets/pterodactyl_v1_demo.gif) -## Support & Documentation -Support for using Pterodactyl can be found on our [Documentation Website](https://pterodactyl.io/project/introduction.html), [Guides Website](https://guides.pterodactyl.io), or via our [Discord Chat](https://discord.gg/QRDZvVm). +## Sponsors +I would like to extend my sincere thanks to the following sponsors for helping find Pterodactyl's developement. +[Interested in becoming a sponsor?](https://github.com/sponsors/DaneEveritt) + +| Company | About | +| ------- | ----- | +| [**Bloom.host**](https://bloom.host) | Bloom.host offers dedicated core VPS and Minecraft hosting with Ryzen 9 processors. With owned-hardware, we offer truly unbeatable prices on high-performance hosting. | +| [**VersatileNode**](https://versatilenode.com/) | Looking to host a minecraft server, vps, or a website? VersatileNode is one of the most affordable hosting providers to provide quality yet cheap services with incredible support. | +| [**MineStrator**](https://minestrator.com/) | Looking for a French highend hosting company for you minecraft server? More than 14,000 members on our discord, trust us. | +| [**DedicatedMC**](https://dedicatedmc.io/) | DedicatedMC provides Raw Power hosting at affordable pricing, making sure to never compromise on your performance and giving you the best performance money can buy. | +| [**Skynode**](https://www.skynode.pro/) | Skynode provides blazing fast game servers along with a top-notch user experience. Whatever our clients are looking for, we're able to provide it! | +| [**XCORE-SERVER.de**](https://xcore-server.de/) | XCORE-SERVER.de offers High-End Servers for hosting and gaming since 2012. Fast, excellent and well-known for eSports Gaming. | +| [**RoyaleHosting**](https://royalehosting.net/) | Build your dreams and deploy them with RoyaleHosting’s reliable servers and network. Easy to use, provisioned in a couple of minutes. | + +## Documentation +* [Panel Documentation](https://pterodactyl.io/panel/1.0/getting_started.html) +* [Wings Documentation](https://pterodactyl.io/wings/1.0/installing.html) +* [Community Guides](https://pterodactyl.io/community/about.html) +* Or, get additional help [via Discord](https://discord.gg/pterodactyl) ### Supported Games -We support a huge variety of games by utilizing Docker containers to isolate each instance, giving you the power to host your games across the world without having to bloat each physical machine with additional dependencies. +We support a huge variety of games by utilizing Docker containers to isolate each instance, giving you the power to +host your games across the world without having to bloat each physical machine with additional dependencies. Some of our core supported games include: -* Minecraft — including Spigot, Sponge, Bungeecord, Waterfall, and more +* Minecraft — including Paper, Sponge, Bungeecord, Waterfall, and more * Rust * Terraria * Teamspeak @@ -30,7 +51,8 @@ Some of our core supported games include: * Garry's Mod * ARK: Survival Evolved -In addition to our standard nest of supported games, our community is constantly pushing the limits of this software and there are plenty more games available provided by the community. Some of these games include: +In addition to our standard nest of supported games, our community is constantly pushing the limits of this software +and there are plenty more games available provided by the community. Some of these games include: * Factorio * San Andreas: MP @@ -38,22 +60,13 @@ In addition to our standard nest of supported games, our community is constantly * Squad * FiveM * Xonotic -* Discord ATLBot - -## Credits -This software would not be possible without the work of other open-source authors who provide tools such as: - -[Ace Editor](https://ace.c9.io), [AdminLTE](https://almsaeedstudio.com), [Animate.css](http://daneden.github.io/animate.css/), [AnsiUp](https://github.com/drudru/ansi_up), [Async.js](https://github.com/caolan/async), -[Bootstrap](http://getbootstrap.com), [Bootstrap Notify](http://bootstrap-notify.remabledesigns.com), [Chart.js](http://www.chartjs.org), [FontAwesome](http://fontawesome.io), -[FontAwesome Animations](https://github.com/l-lin/font-awesome-animation), [jQuery](http://jquery.com), [Laravel](https://laravel.com), [Lodash](https://lodash.com), -[Select2](https://select2.github.io), [Socket.io](http://socket.io), [Socket.io File Upload](https://github.com/vote539/socketio-file-upload), [SweetAlert](http://t4t5.github.io/sweetalert), -[Typeahead](https://github.com/bassjobsen/Bootstrap-3-Typeahead), and [Particles.js](http://vincentgarreau.com/particles.js). - -Some Javascript and CSS used within the panel is licensed under a `MIT` or `Apache 2.0` license. Please check their respective header files for more information. +* Starmade +* Discord ATLBot, and most other Node.js/Python discord bots +* [and many more...](https://github.com/parkervcp/eggs) ## License ``` -Copyright (c) 2015 - 2018 Dane Everitt . +Copyright (c) 2015 - 2020 Dane Everitt & Contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -73,3 +86,6 @@ 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. ``` + +Some Javascript and CSS used within the panel are licensed under a `MIT` or `Apache 2.0` license. Please check their +respective header files for more information. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000..eae32035 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,19 @@ +# Security Policy + +## Supported Versions +The following versions of Pterodactyl are receiving active support and maintenance. Any security vulnerabilities discovered must be reproducible in supported versions. + +| Panel | Daemon | Supported | +| ----- | ------------ | ------------------ | +| 1.0.x | wings@1.0.x | :white_check_mark: | +| 0.7.x | daemon@0.6.x | :white_check_mark: | +| 0.6.x | daemon@0.5.x | :x: | +| 0.5.x | daemon@0.4.x | :x: | + +## Reporting a Vulnerability + +Please reach out directly to any project team member on Discord when reporting a security vulnerability, or you can send an email to `dane [ät] pterodactyl.io`. + +We make every effort to respond as soon as possible, although it may take a day or two for us to sync internally and determine the severity of the report and its impact. Please, _do not_ use a public facing channel or GitHub issues to report sensitive security issues. + +As part of our process, we will create a security advisory for the affected versions and disclose it publicly, usually two to four weeks after a releasing a version that addresses it. diff --git a/app/Console/Commands/Environment/AppSettingsCommand.php b/app/Console/Commands/Environment/AppSettingsCommand.php index 9a10bfd0..60254a9e 100644 --- a/app/Console/Commands/Environment/AppSettingsCommand.php +++ b/app/Console/Commands/Environment/AppSettingsCommand.php @@ -68,7 +68,7 @@ class AppSettingsCommand extends Command {--redis-host= : Redis host to use for connections.} {--redis-pass= : Password used to connect to redis.} {--redis-port= : Port to connect to redis over.} - {--disable-settings-ui}'; + {--settings-ui= : Enable or disable the settings UI.}'; /** * @var array @@ -79,7 +79,7 @@ class AppSettingsCommand extends Command * AppSettingsCommand constructor. * * @param \Illuminate\Contracts\Config\Repository $config - * @param \Illuminate\Contracts\Console\Kernel $command + * @param \Illuminate\Contracts\Console\Kernel $command */ public function __construct(ConfigRepository $config, Kernel $command) { @@ -102,44 +102,44 @@ class AppSettingsCommand extends Command $this->output->comment(trans('command/messages.environment.app.author_help')); $this->variables['APP_SERVICE_AUTHOR'] = $this->option('author') ?? $this->ask( - trans('command/messages.environment.app.author'), $this->config->get('pterodactyl.service.author', 'unknown@unknown.com') - ); + trans('command/messages.environment.app.author'), $this->config->get('pterodactyl.service.author', 'unknown@unknown.com') + ); $this->output->comment(trans('command/messages.environment.app.app_url_help')); $this->variables['APP_URL'] = $this->option('url') ?? $this->ask( - trans('command/messages.environment.app.app_url'), $this->config->get('app.url', 'http://example.org') - ); + trans('command/messages.environment.app.app_url'), $this->config->get('app.url', 'http://example.org') + ); $this->output->comment(trans('command/messages.environment.app.timezone_help')); $this->variables['APP_TIMEZONE'] = $this->option('timezone') ?? $this->anticipate( - trans('command/messages.environment.app.timezone'), - DateTimeZone::listIdentifiers(DateTimeZone::ALL), - $this->config->get('app.timezone') - ); + trans('command/messages.environment.app.timezone'), + DateTimeZone::listIdentifiers(DateTimeZone::ALL), + $this->config->get('app.timezone') + ); $selected = $this->config->get('cache.default', 'redis'); $this->variables['CACHE_DRIVER'] = $this->option('cache') ?? $this->choice( - trans('command/messages.environment.app.cache_driver'), - self::ALLOWED_CACHE_DRIVERS, - array_key_exists($selected, self::ALLOWED_CACHE_DRIVERS) ? $selected : null - ); + trans('command/messages.environment.app.cache_driver'), + self::ALLOWED_CACHE_DRIVERS, + array_key_exists($selected, self::ALLOWED_CACHE_DRIVERS) ? $selected : null + ); $selected = $this->config->get('session.driver', 'redis'); $this->variables['SESSION_DRIVER'] = $this->option('session') ?? $this->choice( - trans('command/messages.environment.app.session_driver'), - self::ALLOWED_SESSION_DRIVERS, - array_key_exists($selected, self::ALLOWED_SESSION_DRIVERS) ? $selected : null - ); + trans('command/messages.environment.app.session_driver'), + self::ALLOWED_SESSION_DRIVERS, + array_key_exists($selected, self::ALLOWED_SESSION_DRIVERS) ? $selected : null + ); $selected = $this->config->get('queue.default', 'redis'); $this->variables['QUEUE_CONNECTION'] = $this->option('queue') ?? $this->choice( - trans('command/messages.environment.app.queue_driver'), - self::ALLOWED_QUEUE_DRIVERS, - array_key_exists($selected, self::ALLOWED_QUEUE_DRIVERS) ? $selected : null - ); + trans('command/messages.environment.app.queue_driver'), + self::ALLOWED_QUEUE_DRIVERS, + array_key_exists($selected, self::ALLOWED_QUEUE_DRIVERS) ? $selected : null + ); - if ($this->option('disable-settings-ui')) { - $this->variables['APP_ENVIRONMENT_ONLY'] = 'true'; + if (! is_null($this->option('settings-ui'))) { + $this->variables['APP_ENVIRONMENT_ONLY'] = $this->option('settings-ui') == 'true' ? 'false' : 'true'; } else { $this->variables['APP_ENVIRONMENT_ONLY'] = $this->confirm(trans('command/messages.environment.app.settings'), true) ? 'false' : 'true'; } @@ -166,8 +166,8 @@ class AppSettingsCommand extends Command $this->output->note(trans('command/messages.environment.app.using_redis')); $this->variables['REDIS_HOST'] = $this->option('redis-host') ?? $this->ask( - trans('command/messages.environment.app.redis_host'), $this->config->get('database.redis.default.host') - ); + trans('command/messages.environment.app.redis_host'), $this->config->get('database.redis.default.host') + ); $askForRedisPassword = true; if (! empty($this->config->get('database.redis.default.password'))) { @@ -178,8 +178,8 @@ class AppSettingsCommand extends Command if ($askForRedisPassword) { $this->output->comment(trans('command/messages.environment.app.redis_pass_help')); $this->variables['REDIS_PASSWORD'] = $this->option('redis-pass') ?? $this->output->askHidden( - trans('command/messages.environment.app.redis_password') - ); + trans('command/messages.environment.app.redis_password') + ); } if (empty($this->variables['REDIS_PASSWORD'])) { @@ -187,7 +187,7 @@ class AppSettingsCommand extends Command } $this->variables['REDIS_PORT'] = $this->option('redis-port') ?? $this->ask( - trans('command/messages.environment.app.redis_port'), $this->config->get('database.redis.default.port') - ); + trans('command/messages.environment.app.redis_port'), $this->config->get('database.redis.default.port') + ); } } diff --git a/app/Console/Commands/Environment/DatabaseSettingsCommand.php b/app/Console/Commands/Environment/DatabaseSettingsCommand.php index 02396142..0a0e56b2 100644 --- a/app/Console/Commands/Environment/DatabaseSettingsCommand.php +++ b/app/Console/Commands/Environment/DatabaseSettingsCommand.php @@ -59,8 +59,8 @@ class DatabaseSettingsCommand extends Command * DatabaseSettingsCommand constructor. * * @param \Illuminate\Contracts\Config\Repository $config - * @param \Illuminate\Database\DatabaseManager $database - * @param \Illuminate\Contracts\Console\Kernel $console + * @param \Illuminate\Database\DatabaseManager $database + * @param \Illuminate\Contracts\Console\Kernel $console */ public function __construct(ConfigRepository $config, DatabaseManager $database, Kernel $console) { @@ -82,21 +82,21 @@ class DatabaseSettingsCommand extends Command { $this->output->note(trans('command/messages.environment.database.host_warning')); $this->variables['DB_HOST'] = $this->option('host') ?? $this->ask( - trans('command/messages.environment.database.host'), $this->config->get('database.connections.mysql.host', '127.0.0.1') - ); + trans('command/messages.environment.database.host'), $this->config->get('database.connections.mysql.host', '127.0.0.1') + ); $this->variables['DB_PORT'] = $this->option('port') ?? $this->ask( - trans('command/messages.environment.database.port'), $this->config->get('database.connections.mysql.port', 3306) - ); + trans('command/messages.environment.database.port'), $this->config->get('database.connections.mysql.port', 3306) + ); $this->variables['DB_DATABASE'] = $this->option('database') ?? $this->ask( - trans('command/messages.environment.database.database'), $this->config->get('database.connections.mysql.database', 'panel') - ); + trans('command/messages.environment.database.database'), $this->config->get('database.connections.mysql.database', 'panel') + ); $this->output->note(trans('command/messages.environment.database.username_warning')); $this->variables['DB_USERNAME'] = $this->option('username') ?? $this->ask( - trans('command/messages.environment.database.username'), $this->config->get('database.connections.mysql.username', 'pterodactyl') - ); + trans('command/messages.environment.database.username'), $this->config->get('database.connections.mysql.username', 'pterodactyl') + ); $askForMySQLPassword = true; if (! empty($this->config->get('database.connections.mysql.password')) && $this->input->isInteractive()) { @@ -136,15 +136,15 @@ class DatabaseSettingsCommand extends Command private function testMySQLConnection() { $this->config->set('database.connections._pterodactyl_command_test', [ - 'driver' => 'mysql', - 'host' => $this->variables['DB_HOST'], - 'port' => $this->variables['DB_PORT'], - 'database' => $this->variables['DB_DATABASE'], - 'username' => $this->variables['DB_USERNAME'], - 'password' => $this->variables['DB_PASSWORD'], - 'charset' => 'utf8mb4', - 'collation' => 'utf8mb4_unicode_ci', - 'strict' => true, + 'driver' => 'mysql', + 'host' => $this->variables['DB_HOST'], + 'port' => $this->variables['DB_PORT'], + 'database' => $this->variables['DB_DATABASE'], + 'username' => $this->variables['DB_USERNAME'], + 'password' => $this->variables['DB_PASSWORD'], + 'charset' => 'utf8mb4', + 'collation' => 'utf8mb4_unicode_ci', + 'strict' => true, ]); $this->database->connection('_pterodactyl_command_test')->getPdo(); diff --git a/app/Console/Commands/Environment/EmailSettingsCommand.php b/app/Console/Commands/Environment/EmailSettingsCommand.php index 0a29f2da..add1296e 100644 --- a/app/Console/Commands/Environment/EmailSettingsCommand.php +++ b/app/Console/Commands/Environment/EmailSettingsCommand.php @@ -59,6 +59,7 @@ class EmailSettingsCommand extends Command /** * Handle command execution. + * * @throws \Pterodactyl\Exceptions\PterodactylException */ public function handle() @@ -79,16 +80,16 @@ class EmailSettingsCommand extends Command } $this->variables['MAIL_FROM'] = $this->option('email') ?? $this->ask( - trans('command/messages.environment.mail.ask_mail_from'), $this->config->get('mail.from.address') - ); + trans('command/messages.environment.mail.ask_mail_from'), $this->config->get('mail.from.address') + ); $this->variables['MAIL_FROM_NAME'] = $this->option('from') ?? $this->ask( - trans('command/messages.environment.mail.ask_mail_name'), $this->config->get('mail.from.name') - ); + trans('command/messages.environment.mail.ask_mail_name'), $this->config->get('mail.from.name') + ); $this->variables['MAIL_ENCRYPTION'] = $this->option('encryption') ?? $this->choice( - trans('command/messages.environment.mail.ask_encryption'), ['tls' => 'TLS', 'ssl' => 'SSL', '' => 'None'], $this->config->get('mail.encryption', 'tls') - ); + trans('command/messages.environment.mail.ask_encryption'), ['tls' => 'TLS', 'ssl' => 'SSL', '' => 'None'], $this->config->get('mail.encryption', 'tls') + ); $this->writeToEnvironment($this->variables); @@ -102,20 +103,20 @@ class EmailSettingsCommand extends Command private function setupSmtpDriverVariables() { $this->variables['MAIL_HOST'] = $this->option('host') ?? $this->ask( - trans('command/messages.environment.mail.ask_smtp_host'), $this->config->get('mail.host') - ); + trans('command/messages.environment.mail.ask_smtp_host'), $this->config->get('mail.host') + ); $this->variables['MAIL_PORT'] = $this->option('port') ?? $this->ask( - trans('command/messages.environment.mail.ask_smtp_port'), $this->config->get('mail.port') - ); + trans('command/messages.environment.mail.ask_smtp_port'), $this->config->get('mail.port') + ); $this->variables['MAIL_USERNAME'] = $this->option('username') ?? $this->ask( - trans('command/messages.environment.mail.ask_smtp_username'), $this->config->get('mail.username') - ); + trans('command/messages.environment.mail.ask_smtp_username'), $this->config->get('mail.username') + ); $this->variables['MAIL_PASSWORD'] = $this->option('password') ?? $this->secret( - trans('command/messages.environment.mail.ask_smtp_password') - ); + trans('command/messages.environment.mail.ask_smtp_password') + ); } /** @@ -124,12 +125,12 @@ class EmailSettingsCommand extends Command private function setupMailgunDriverVariables() { $this->variables['MAILGUN_DOMAIN'] = $this->option('host') ?? $this->ask( - trans('command/messages.environment.mail.ask_mailgun_domain'), $this->config->get('services.mailgun.domain') - ); + trans('command/messages.environment.mail.ask_mailgun_domain'), $this->config->get('services.mailgun.domain') + ); $this->variables['MAILGUN_SECRET'] = $this->option('password') ?? $this->ask( - trans('command/messages.environment.mail.ask_mailgun_secret'), $this->config->get('services.mailgun.secret') - ); + trans('command/messages.environment.mail.ask_mailgun_secret'), $this->config->get('services.mailgun.secret') + ); } /** @@ -138,8 +139,8 @@ class EmailSettingsCommand extends Command private function setupMandrillDriverVariables() { $this->variables['MANDRILL_SECRET'] = $this->option('password') ?? $this->ask( - trans('command/messages.environment.mail.ask_mandrill_secret'), $this->config->get('services.mandrill.secret') - ); + trans('command/messages.environment.mail.ask_mandrill_secret'), $this->config->get('services.mandrill.secret') + ); } /** @@ -151,7 +152,7 @@ class EmailSettingsCommand extends Command $this->variables['MAIL_HOST'] = 'smtp.postmarkapp.com'; $this->variables['MAIL_PORT'] = 587; $this->variables['MAIL_USERNAME'] = $this->variables['MAIL_PASSWORD'] = $this->option('username') ?? $this->ask( - trans('command/messages.environment.mail.ask_postmark_username'), $this->config->get('mail.username') - ); + trans('command/messages.environment.mail.ask_postmark_username'), $this->config->get('mail.username') + ); } } diff --git a/app/Console/Commands/InfoCommand.php b/app/Console/Commands/InfoCommand.php index d8544477..7ee140a1 100644 --- a/app/Console/Commands/InfoCommand.php +++ b/app/Console/Commands/InfoCommand.php @@ -38,7 +38,7 @@ class InfoCommand extends Command /** * VersionCommand constructor. * - * @param \Illuminate\Contracts\Config\Repository $config + * @param \Illuminate\Contracts\Config\Repository $config * @param \Pterodactyl\Services\Helpers\SoftwareVersionService $versionService */ public function __construct(ConfigRepository $config, SoftwareVersionService $versionService) diff --git a/app/Console/Commands/Location/DeleteLocationCommand.php b/app/Console/Commands/Location/DeleteLocationCommand.php index 77e5606a..b6782207 100644 --- a/app/Console/Commands/Location/DeleteLocationCommand.php +++ b/app/Console/Commands/Location/DeleteLocationCommand.php @@ -44,7 +44,7 @@ class DeleteLocationCommand extends Command * DeleteLocationCommand constructor. * * @param \Pterodactyl\Contracts\Repository\LocationRepositoryInterface $repository - * @param \Pterodactyl\Services\Locations\LocationDeletionService $deletionService + * @param \Pterodactyl\Services\Locations\LocationDeletionService $deletionService */ public function __construct( LocationDeletionService $deletionService, @@ -66,8 +66,8 @@ class DeleteLocationCommand extends Command { $this->locations = $this->locations ?? $this->repository->all(); $short = $this->option('short') ?? $this->anticipate( - trans('command/messages.location.ask_short'), $this->locations->pluck('short')->toArray() - ); + trans('command/messages.location.ask_short'), $this->locations->pluck('short')->toArray() + ); $location = $this->locations->where('short', $short)->first(); if (is_null($location)) { diff --git a/app/Console/Commands/Maintenance/PruneOrphanedBackupsCommand.php b/app/Console/Commands/Maintenance/PruneOrphanedBackupsCommand.php new file mode 100644 index 00000000..af4590d4 --- /dev/null +++ b/app/Console/Commands/Maintenance/PruneOrphanedBackupsCommand.php @@ -0,0 +1,51 @@ +option('since-minutes'); + if (! is_digit($since)) { + throw new InvalidArgumentException('The --since-minutes option must be a valid numeric digit.'); + } + + $query = $repository->getBuilder() + ->whereNull('completed_at') + ->whereDate('created_at', '<=', CarbonImmutable::now()->subMinutes($since)); + + $count = $query->count(); + if (! $count) { + $this->info('There are no orphaned backups to be marked as failed.'); + + return; + } + + $this->warn("Marking {$count} backups that have not been marked as completed in the last {$since} minutes as failed."); + + $query->update([ + 'is_successful' => false, + 'completed_at' => CarbonImmutable::now(), + 'updated_at' => CarbonImmutable::now(), + ]); + } +} diff --git a/app/Console/Commands/Migration/CleanOrphanedApiKeysCommand.php b/app/Console/Commands/Migration/CleanOrphanedApiKeysCommand.php index b9e007ee..e7d86399 100644 --- a/app/Console/Commands/Migration/CleanOrphanedApiKeysCommand.php +++ b/app/Console/Commands/Migration/CleanOrphanedApiKeysCommand.php @@ -38,7 +38,7 @@ class CleanOrphanedApiKeysCommand extends Command /** * Delete all orphaned API keys from the database when upgrading from 0.6 to 0.7. * - * @return null|void + * @return void|null */ public function handle() { diff --git a/app/Console/Commands/Schedule/ProcessRunnableCommand.php b/app/Console/Commands/Schedule/ProcessRunnableCommand.php index e5a40fb4..67654708 100644 --- a/app/Console/Commands/Schedule/ProcessRunnableCommand.php +++ b/app/Console/Commands/Schedule/ProcessRunnableCommand.php @@ -40,7 +40,7 @@ class ProcessRunnableCommand extends Command /** * ProcessRunnableCommand constructor. * - * @param \Pterodactyl\Services\Schedules\ProcessScheduleService $processScheduleService + * @param \Pterodactyl\Services\Schedules\ProcessScheduleService $processScheduleService * @param \Pterodactyl\Contracts\Repository\ScheduleRepositoryInterface $repository */ public function __construct(ProcessScheduleService $processScheduleService, ScheduleRepositoryInterface $repository) diff --git a/app/Console/Commands/Server/BulkPowerActionCommand.php b/app/Console/Commands/Server/BulkPowerActionCommand.php index c6b5e435..38387990 100644 --- a/app/Console/Commands/Server/BulkPowerActionCommand.php +++ b/app/Console/Commands/Server/BulkPowerActionCommand.php @@ -6,13 +6,13 @@ use Illuminate\Console\Command; use GuzzleHttp\Exception\RequestException; use Illuminate\Validation\ValidationException; use Illuminate\Validation\Factory as ValidatorFactory; +use Pterodactyl\Repositories\Wings\DaemonPowerRepository; use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; -use Pterodactyl\Contracts\Repository\Daemon\PowerRepositoryInterface; class BulkPowerActionCommand extends Command { /** - * @var \Pterodactyl\Contracts\Repository\Daemon\PowerRepositoryInterface + * @var \Pterodactyl\Repositories\Wings\DaemonPowerRepository */ private $powerRepository; @@ -42,27 +42,26 @@ class BulkPowerActionCommand extends Command /** * BulkPowerActionCommand constructor. * - * @param \Pterodactyl\Contracts\Repository\Daemon\PowerRepositoryInterface $powerRepository - * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository - * @param \Illuminate\Validation\Factory $validator + * @param \Pterodactyl\Repositories\Wings\DaemonPowerRepository $powerRepository + * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository + * @param \Illuminate\Validation\Factory $validator */ public function __construct( - PowerRepositoryInterface $powerRepository, + DaemonPowerRepository $powerRepository, ServerRepositoryInterface $repository, ValidatorFactory $validator ) { parent::__construct(); - $this->powerRepository = $powerRepository; $this->repository = $repository; $this->validator = $validator; + $this->powerRepository = $powerRepository; } /** * Handle the bulk power request. * * @throws \Illuminate\Validation\ValidationException - * @throws \Pterodactyl\Exceptions\Repository\Daemon\InvalidPowerSignalException */ public function handle() { @@ -91,21 +90,21 @@ class BulkPowerActionCommand extends Command } $count = $this->repository->getServersForPowerActionCount($servers, $nodes); - if (! $this->confirm(trans('command/messages.server.power.confirm', ['action' => $action, 'count' => $count]))) { + if (! $this->confirm(trans('command/messages.server.power.confirm', ['action' => $action, 'count' => $count])) && $this->input->isInteractive()) { return; } $bar = $this->output->createProgressBar($count); $servers = $this->repository->getServersForPowerAction($servers, $nodes); - foreach ($servers as $server) { + $servers->each(function ($server) use ($action, &$bar) { $bar->clear(); try { $this->powerRepository ->setNode($server->node) ->setServer($server) - ->sendSignal($action); + ->send($action); } catch (RequestException $exception) { $this->output->error(trans('command/messages.server.power.action_failed', [ 'name' => $server->name, @@ -117,7 +116,7 @@ class BulkPowerActionCommand extends Command $bar->advance(); $bar->display(); - } + }); $this->line(''); } diff --git a/app/Console/Commands/Server/RebuildServerCommand.php b/app/Console/Commands/Server/BulkReinstallActionCommand.php similarity index 58% rename from app/Console/Commands/Server/RebuildServerCommand.php rename to app/Console/Commands/Server/BulkReinstallActionCommand.php index ac239b1e..a56cefc7 100644 --- a/app/Console/Commands/Server/RebuildServerCommand.php +++ b/app/Console/Commands/Server/BulkReinstallActionCommand.php @@ -12,50 +12,50 @@ namespace Pterodactyl\Console\Commands\Server; use Webmozart\Assert\Assert; use Illuminate\Console\Command; use GuzzleHttp\Exception\RequestException; -use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; +use Pterodactyl\Repositories\Eloquent\ServerRepository; +use Pterodactyl\Repositories\Wings\DaemonServerRepository; use Pterodactyl\Services\Servers\ServerConfigurationStructureService; -use Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface as DaemonServerRepositoryInterface; -class RebuildServerCommand extends Command +class BulkReinstallActionCommand extends Command { /** * @var \Pterodactyl\Services\Servers\ServerConfigurationStructureService */ - protected $configurationStructureService; + private $configurationStructureService; /** - * @var \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface + * @var \Pterodactyl\Repositories\Wings\DaemonServerRepository */ - protected $daemonRepository; + private $daemonRepository; + + /** + * @var \Pterodactyl\Repositories\Eloquent\ServerRepository + */ + private $repository; /** * @var string */ - protected $description = 'Rebuild a single server, all servers on a node, or all servers on the panel.'; - - /** - * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface - */ - protected $repository; + protected $description = 'Reinstall a single server, all servers on a node, or all servers on the panel.'; /** * @var string */ - protected $signature = 'p:server:rebuild - {server? : The ID of the server to rebuild.} - {--node= : ID of the node to rebuild all servers on. Ignored if server is passed.}'; + protected $signature = 'p:server:reinstall + {server? : The ID of the server to reinstall.} + {--node= : ID of the node to reinstall all servers on. Ignored if server is passed.}'; /** - * RebuildServerCommand constructor. + * BulkReinstallActionCommand constructor. * - * @param \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface $daemonRepository - * @param \Pterodactyl\Services\Servers\ServerConfigurationStructureService $configurationStructureService - * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository + * @param \Pterodactyl\Repositories\Wings\DaemonServerRepository $daemonRepository + * @param \Pterodactyl\Services\Servers\ServerConfigurationStructureService $configurationStructureService + * @param \Pterodactyl\Repositories\Eloquent\ServerRepository $repository */ public function __construct( - DaemonServerRepositoryInterface $daemonRepository, + DaemonServerRepository $daemonRepository, ServerConfigurationStructureService $configurationStructureService, - ServerRepositoryInterface $repository + ServerRepository $repository ) { parent::__construct(); @@ -70,16 +70,20 @@ class RebuildServerCommand extends Command public function handle() { $servers = $this->getServersToProcess(); + + if (! $this->confirm(trans('command/messages.server.reinstall.confirm')) && $this->input->isInteractive()) { + return; + } + $bar = $this->output->createProgressBar(count($servers)); $servers->each(function ($server) use ($bar) { $bar->clear(); - $json = array_merge($this->configurationStructureService->handle($server), ['rebuild' => true]); try { - $this->daemonRepository->setServer($server)->update($json); + $this->daemonRepository->setServer($server)->reinstall(); } catch (RequestException $exception) { - $this->output->error(trans('command/messages.server.rebuild_failed', [ + $this->output->error(trans('command/messages.server.reinstall.failed', [ 'name' => $server->name, 'id' => $server->id, 'node' => $server->node->name, @@ -95,15 +99,15 @@ class RebuildServerCommand extends Command } /** - * Return the servers to be rebuilt. + * Return the servers to be reinstalled. * - * @return \Illuminate\Database\Eloquent\Collection + * @return \Illuminate\Support\Collection */ private function getServersToProcess() { Assert::nullOrIntegerish($this->argument('server'), 'Value passed in server argument must be null or an integer, received %s.'); Assert::nullOrIntegerish($this->option('node'), 'Value passed in node option must be null or integer, received %s.'); - return $this->repository->getDataForRebuild($this->argument('server'), $this->option('node')); + return $this->repository->getDataForReinstall($this->argument('server'), $this->option('node')); } } diff --git a/app/Console/Commands/User/DeleteUserCommand.php b/app/Console/Commands/User/DeleteUserCommand.php index c9a69bee..f458b51d 100644 --- a/app/Console/Commands/User/DeleteUserCommand.php +++ b/app/Console/Commands/User/DeleteUserCommand.php @@ -10,6 +10,7 @@ namespace Pterodactyl\Console\Commands\User; use Webmozart\Assert\Assert; +use Pterodactyl\Models\User; use Illuminate\Console\Command; use Pterodactyl\Services\Users\UserDeletionService; use Pterodactyl\Contracts\Repository\UserRepositoryInterface; @@ -39,17 +40,12 @@ class DeleteUserCommand extends Command /** * DeleteUserCommand constructor. * - * @param \Pterodactyl\Services\Users\UserDeletionService $deletionService - * @param \Pterodactyl\Contracts\Repository\UserRepositoryInterface $repository + * @param \Pterodactyl\Services\Users\UserDeletionService $deletionService */ - public function __construct( - UserDeletionService $deletionService, - UserRepositoryInterface $repository - ) { + public function __construct(UserDeletionService $deletionService) { parent::__construct(); $this->deletionService = $deletionService; - $this->repository = $repository; } /** @@ -59,9 +55,13 @@ class DeleteUserCommand extends Command public function handle() { $search = $this->option('user') ?? $this->ask(trans('command/messages.user.search_users')); - Assert::notEmpty($search, 'Search term must be a non-null value, received %s.'); + Assert::notEmpty($search, 'Search term should be an email address, got: %s.'); + + $results = User::query() + ->where('email', 'LIKE', "$search%") + ->where('username', 'LIKE', "$search%") + ->get(); - $results = $this->repository->setSearchTerm($search)->all(); if (count($results) < 1) { $this->error(trans('command/messages.user.no_users_found')); if ($this->input->isInteractive()) { @@ -95,5 +95,7 @@ class DeleteUserCommand extends Command $this->deletionService->handle($deleteUser); $this->info(trans('command/messages.user.deleted')); } + + return; } } diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index c87cd539..1f83ddf9 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -22,7 +22,16 @@ class Kernel extends ConsoleKernel */ protected function schedule(Schedule $schedule) { + // Execute scheduled commands for servers every minute, as if there was a normal cron running. $schedule->command('p:schedule:process')->everyMinute()->withoutOverlapping(); + + // Every 30 minutes, run the backup pruning command so that any abandoned backups can be removed + // from the UI view for the server. + $schedule->command('p:maintenance:prune-backups', [ + '--since-minutes' => '30', + ])->everyThirtyMinutes(); + + // Every day cleanup any internal backups of service files. $schedule->command('p:maintenance:clean-service-backups')->daily(); } } diff --git a/app/Contracts/Criteria/CriteriaInterface.php b/app/Contracts/Criteria/CriteriaInterface.php index 628aee94..1b832819 100644 --- a/app/Contracts/Criteria/CriteriaInterface.php +++ b/app/Contracts/Criteria/CriteriaInterface.php @@ -16,7 +16,7 @@ interface CriteriaInterface /** * Apply selected criteria to a repository call. * - * @param \Illuminate\Database\Eloquent\Model $model + * @param \Illuminate\Database\Eloquent\Model $model * @param \Pterodactyl\Repositories\Repository $repository * @return mixed */ diff --git a/app/Contracts/Extensions/HashidsInterface.php b/app/Contracts/Extensions/HashidsInterface.php index 39fa7d62..8e1e6990 100644 --- a/app/Contracts/Extensions/HashidsInterface.php +++ b/app/Contracts/Extensions/HashidsInterface.php @@ -17,7 +17,7 @@ interface HashidsInterface extends VendorHashidsInterface * Decode an encoded hashid and return the first result. * * @param string $encoded - * @param null $default + * @param null $default * @return mixed * * @throws \InvalidArgumentException diff --git a/app/Contracts/Http/ClientPermissionsRequest.php b/app/Contracts/Http/ClientPermissionsRequest.php new file mode 100644 index 00000000..6b863fce --- /dev/null +++ b/app/Contracts/Http/ClientPermissionsRequest.php @@ -0,0 +1,15 @@ +shouldntReport($exception)) { return null; @@ -102,7 +105,7 @@ class Handler extends ExceptionHandler return $logger->error($exception); } - private function generateCleanedExceptionStack(Exception $exception) + private function generateCleanedExceptionStack(Throwable $exception) { $cleanedStack = ''; foreach ($exception->getTrace() as $index => $item) { @@ -132,12 +135,12 @@ class Handler extends ExceptionHandler * Render an exception into an HTTP response. * * @param \Illuminate\Http\Request $request - * @param \Exception $exception + * @param \Throwable $exception * @return \Symfony\Component\HttpFoundation\Response * - * @throws \Exception + * @throws \Throwable */ - public function render($request, Exception $exception) + public function render($request, Throwable $exception) { $connections = Container::getInstance()->make(Connection::class); @@ -154,26 +157,6 @@ class Handler extends ExceptionHandler $connections->rollBack(0); } - // Because of some breaking change snuck into a Laravel update that didn't get caught - // by any of the tests, exceptions implementing the HttpExceptionInterface get marked - // as being HttpExceptions, but aren't actually implementing the HttpException abstract. - // - // This is incredibly annoying because we can't just temporarily override the handler to - // allow these (at least without taking on a high maintenance cost). Laravel 5.8 fixes this, - // so when we update (or have updated) this code can be removed. - // - // @see https://github.com/laravel/framework/pull/25975 - // @todo remove this code when upgrading to Laravel 5.8 - if ($exception instanceof HttpExceptionInterface && ! $exception instanceof HttpException) { - $exception = new HttpException( - $exception->getStatusCode(), - $exception->getMessage(), - $exception, - $exception->getHeaders(), - $exception->getCode() - ); - } - return parent::render($request, $exception); } @@ -181,7 +164,7 @@ class Handler extends ExceptionHandler * Transform a validation exception into a consistent format to be returned for * calls to the API. * - * @param \Illuminate\Http\Request $request + * @param \Illuminate\Http\Request $request * @param \Illuminate\Validation\ValidationException $exception * @return \Illuminate\Http\JsonResponse */ @@ -196,16 +179,21 @@ class Handler extends ExceptionHandler return [str_replace('.', '_', $field) => $cleaned]; })->toArray(); - $errors = collect($exception->errors())->map(function ($errors, $field) use ($codes) { + $errors = collect($exception->errors())->map(function ($errors, $field) use ($codes, $exception) { $response = []; foreach ($errors as $key => $error) { - $response[] = [ - 'code' => str_replace(self::PTERODACTYL_RULE_STRING, 'p_', array_get( + $meta = [ + 'source_field' => $field, + 'rule' => str_replace(self::PTERODACTYL_RULE_STRING, 'p_', array_get( $codes, str_replace('.', '_', $field) . '.' . $key )), - 'detail' => $error, - 'source' => ['field' => $field], ]; + + $converted = self::convertToArray($exception)['errors'][0]; + $converted['detail'] = $error; + $converted['meta'] = is_array($converted['meta'] ?? null) ? array_merge($converted['meta'], $meta) : $meta; + + $response[] = $converted; } return $response; @@ -219,18 +207,29 @@ class Handler extends ExceptionHandler /** * Return the exception as a JSONAPI representation for use on API requests. * - * @param \Exception $exception - * @param array $override + * @param \Throwable $exception + * @param array $override * @return array */ - public static function convertToArray(Exception $exception, array $override = []): array + public static function convertToArray(Throwable $exception, array $override = []): array { $error = [ 'code' => class_basename($exception), - 'status' => method_exists($exception, 'getStatusCode') ? strval($exception->getStatusCode()) : '500', - 'detail' => 'An error was encountered while processing this request.', + 'status' => method_exists($exception, 'getStatusCode') + ? strval($exception->getStatusCode()) + : ($exception instanceof ValidationException ? '422' : '500'), + 'detail' => $exception instanceof HttpExceptionInterface + ? $exception->getMessage() + : 'An unexpected error was encountered while processing this request, please try again.', ]; + if ($exception instanceof ModelNotFoundException || $exception->getPrevious() instanceof ModelNotFoundException) { + // Show a nicer error message compared to the standard "No query results for model" + // response that is normally returned. If we are in debug mode this will get overwritten + // with a more specific error message to help narrow down things. + $error['detail'] = 'The requested resource could not be found on the server.'; + } + if (config('app.debug')) { $error = array_merge($error, [ 'detail' => $exception->getMessage(), @@ -261,14 +260,14 @@ class Handler extends ExceptionHandler /** * Convert an authentication exception into an unauthenticated response. * - * @param \Illuminate\Http\Request $request + * @param \Illuminate\Http\Request $request * @param \Illuminate\Auth\AuthenticationException $exception * @return \Illuminate\Http\Response */ protected function unauthenticated($request, AuthenticationException $exception) { if ($request->expectsJson()) { - return response()->json(['error' => 'Unauthenticated.'], 401); + return response()->json(self::convertToArray($exception), 401); } return redirect()->guest(route('auth.login')); @@ -278,10 +277,10 @@ class Handler extends ExceptionHandler * Converts an exception into an array to render in the response. Overrides * Laravel's built-in converter to output as a JSONAPI spec compliant object. * - * @param \Exception $exception + * @param \Throwable $exception * @return array */ - protected function convertExceptionToArray(Exception $exception) + protected function convertExceptionToArray(Throwable $exception) { return self::convertToArray($exception); } diff --git a/app/Exceptions/Http/Connection/DaemonConnectionException.php b/app/Exceptions/Http/Connection/DaemonConnectionException.php index f2892789..e6765b8a 100644 --- a/app/Exceptions/Http/Connection/DaemonConnectionException.php +++ b/app/Exceptions/Http/Connection/DaemonConnectionException.php @@ -2,10 +2,14 @@ namespace Pterodactyl\Exceptions\Http\Connection; +use Illuminate\Support\Arr; use Illuminate\Http\Response; use GuzzleHttp\Exception\GuzzleException; use Pterodactyl\Exceptions\DisplayException; +/** + * @method \GuzzleHttp\Exception\GuzzleException getPrevious() + */ class DaemonConnectionException extends DisplayException { /** @@ -17,20 +21,36 @@ class DaemonConnectionException extends DisplayException * Throw a displayable exception caused by a daemon connection error. * * @param \GuzzleHttp\Exception\GuzzleException $previous - * @param bool $useStatusCode + * @param bool $useStatusCode */ - public function __construct(GuzzleException $previous, bool $useStatusCode = false) + public function __construct(GuzzleException $previous, bool $useStatusCode = true) { /** @var \GuzzleHttp\Psr7\Response|null $response */ $response = method_exists($previous, 'getResponse') ? $previous->getResponse() : null; if ($useStatusCode) { - $this->statusCode = is_null($response) ? 500 : $response->getStatusCode(); + $this->statusCode = is_null($response) ? $this->statusCode : $response->getStatusCode(); } - parent::__construct(trans('admin/server.exceptions.daemon_exception', [ + $message = trans('admin/server.exceptions.daemon_exception', [ 'code' => is_null($response) ? 'E_CONN_REFUSED' : $response->getStatusCode(), - ]), $previous, DisplayException::LEVEL_WARNING); + ]); + + // Attempt to pull the actual error message off the response and return that if it is not + // a 500 level error. + if ($this->statusCode < 500 && ! is_null($response)) { + $body = $response->getBody(); + if (is_string($body) || (is_object($body) && method_exists($body, '__toString'))) { + $body = json_decode(is_string($body) ? $body : $body->__toString(), true); + $message = "[Wings Error]: " . Arr::get($body, 'error', $message); + } + } + + $level = $this->statusCode >= 500 && $this->statusCode !== 504 + ? DisplayException::LEVEL_ERROR + : DisplayException::LEVEL_WARNING; + + parent::__construct($message, $previous, $level); } /** diff --git a/app/Exceptions/Http/HttpForbiddenException.php b/app/Exceptions/Http/HttpForbiddenException.php new file mode 100644 index 00000000..fa2aae9b --- /dev/null +++ b/app/Exceptions/Http/HttpForbiddenException.php @@ -0,0 +1,20 @@ +. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Exceptions\Http\Server; @@ -13,4 +6,11 @@ use Pterodactyl\Exceptions\DisplayException; class FileSizeTooLargeException extends DisplayException { + /** + * FileSizeTooLargeException constructor. + */ + public function __construct() + { + parent::__construct('The file you are attempting to open is too large to view in the file editor.'); + } } diff --git a/app/Exceptions/PterodactylException.php b/app/Exceptions/PterodactylException.php index 451ae92c..51766a92 100644 --- a/app/Exceptions/PterodactylException.php +++ b/app/Exceptions/PterodactylException.php @@ -2,6 +2,8 @@ namespace Pterodactyl\Exceptions; -class PterodactylException extends \Exception +use Exception; + +class PterodactylException extends Exception { } diff --git a/app/Exceptions/Service/Backup/TooManyBackupsException.php b/app/Exceptions/Service/Backup/TooManyBackupsException.php new file mode 100644 index 00000000..a96baed5 --- /dev/null +++ b/app/Exceptions/Service/Backup/TooManyBackupsException.php @@ -0,0 +1,20 @@ +. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -namespace Pterodactyl\Exceptions\Service\Pack; - -use Pterodactyl\Exceptions\DisplayException; - -class InvalidFileMimeTypeException extends DisplayException -{ -} diff --git a/app/Exceptions/Service/Pack/InvalidPackArchiveFormatException.php b/app/Exceptions/Service/Pack/InvalidPackArchiveFormatException.php deleted file mode 100644 index 5e216fed..00000000 --- a/app/Exceptions/Service/Pack/InvalidPackArchiveFormatException.php +++ /dev/null @@ -1,16 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -namespace Pterodactyl\Exceptions\Service\Pack; - -use Pterodactyl\Exceptions\DisplayException; - -class InvalidPackArchiveFormatException extends DisplayException -{ -} diff --git a/app/Exceptions/Service/Pack/UnreadableZipArchiveException.php b/app/Exceptions/Service/Pack/UnreadableZipArchiveException.php deleted file mode 100644 index f1608936..00000000 --- a/app/Exceptions/Service/Pack/UnreadableZipArchiveException.php +++ /dev/null @@ -1,16 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -namespace Pterodactyl\Exceptions\Service\Pack; - -use Pterodactyl\Exceptions\DisplayException; - -class UnreadableZipArchiveException extends DisplayException -{ -} diff --git a/app/Exceptions/Service/Pack/ZipArchiveCreationException.php b/app/Exceptions/Service/Pack/ZipArchiveCreationException.php deleted file mode 100644 index 79caab26..00000000 --- a/app/Exceptions/Service/Pack/ZipArchiveCreationException.php +++ /dev/null @@ -1,14 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -namespace Pterodactyl\Exceptions\Service\Pack; - -class ZipArchiveCreationException extends \Exception -{ -} diff --git a/app/Exceptions/Service/Pack/ZipExtractionException.php b/app/Exceptions/Service/Pack/ZipExtractionException.php deleted file mode 100644 index 8a6a82c2..00000000 --- a/app/Exceptions/Service/Pack/ZipExtractionException.php +++ /dev/null @@ -1,16 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -namespace Pterodactyl\Exceptions\Service\Pack; - -use Pterodactyl\Exceptions\DisplayException; - -class ZipExtractionException extends DisplayException -{ -} diff --git a/app/Exceptions/Service/ServiceLimitExceededException.php b/app/Exceptions/Service/ServiceLimitExceededException.php new file mode 100644 index 00000000..55ee6c94 --- /dev/null +++ b/app/Exceptions/Service/ServiceLimitExceededException.php @@ -0,0 +1,21 @@ +. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Exceptions\Service\User; -class TwoFactorAuthenticationTokenInvalid extends \Exception +use Pterodactyl\Exceptions\DisplayException; + +class TwoFactorAuthenticationTokenInvalid extends DisplayException { } diff --git a/app/Extensions/Backups/BackupManager.php b/app/Extensions/Backups/BackupManager.php new file mode 100644 index 00000000..fda52f90 --- /dev/null +++ b/app/Extensions/Backups/BackupManager.php @@ -0,0 +1,227 @@ +app = $app; + $this->config = $app->make(Repository::class); + } + + /** + * Returns a backup adapter instance. + * + * @param string|null $name + * @return \League\Flysystem\AdapterInterface + */ + public function adapter(string $name = null) + { + return $this->get($name ?: $this->getDefaultAdapter()); + } + + /** + * Set the given backup adapter instance. + * + * @param string $name + * @param \League\Flysystem\AdapterInterface $disk + * @return $this + */ + public function set(string $name, $disk) + { + $this->adapters[$name] = $disk; + + return $this; + } + + /** + * Gets a backup adapter. + * + * @param string $name + * @return \League\Flysystem\AdapterInterface + */ + protected function get(string $name) + { + return $this->adapters[$name] = $this->resolve($name); + } + + /** + * Resolve the given backup disk. + * + * @param string $name + * @return \League\Flysystem\AdapterInterface + */ + protected function resolve(string $name) + { + $config = $this->getConfig($name); + + if (empty($config['adapter'])) { + throw new InvalidArgumentException( + "Backup disk [{$name}] does not have a configured adapter." + ); + } + + $adapter = $config['adapter']; + + if (isset($this->customCreators[$name])) { + return $this->callCustomCreator($config); + } + + $adapterMethod = 'create' . Str::studly($adapter) . 'Adapter'; + if (method_exists($this, $adapterMethod)) { + $instance = $this->{$adapterMethod}($config); + + Assert::isInstanceOf($instance, AdapterInterface::class); + + return $instance; + } + + throw new InvalidArgumentException("Adapter [{$adapter}] is not supported."); + } + + /** + * Calls a custom creator for a given adapter type. + * + * @param array $config + * @return \League\Flysystem\AdapterInterface + */ + protected function callCustomCreator(array $config) + { + $adapter = $this->customCreators[$config['adapter']]($this->app, $config); + + Assert::isInstanceOf($adapter, AdapterInterface::class); + + return $adapter; + } + + /** + * Creates a new wings adapter. + * + * @param array $config + * @return \League\Flysystem\AdapterInterface + */ + public function createWingsAdapter(array $config) + { + return new MemoryAdapter(null); + } + + /** + * Creates a new S3 adapter. + * + * @param array $config + * @return \League\Flysystem\AdapterInterface + */ + public function createS3Adapter(array $config) + { + $config['version'] = 'latest'; + + if (! empty($config['key']) && ! empty($config['secret'])) { + $config['credentials'] = Arr::only($config, ['key', 'secret', 'token']); + } + + $client = new S3Client($config); + + return new AwsS3Adapter($client, $config['bucket'], $config['prefix'] ?? '', $config['options'] ?? []); + } + + /** + * Returns the configuration associated with a given backup type. + * + * @param string $name + * @return array + */ + protected function getConfig(string $name) + { + return $this->config->get("backups.disks.{$name}") ?: []; + } + + /** + * Get the default backup driver name. + * + * @return string + */ + public function getDefaultAdapter() + { + return $this->config->get('backups.default'); + } + + /** + * Set the default session driver name. + * + * @param string $name + */ + public function setDefaultAdapter(string $name) + { + $this->config->set('backups.default', $name); + } + + /** + * Unset the given adapter instances. + * + * @param string|string[] $adapter + * @return $this + */ + public function forget($adapter) + { + foreach ((array) $adapter as $adapterName) { + unset($this->adapters[$adapter]); + } + + return $this; + } + + /** + * Register a custom adapter creator closure. + * + * @param string $adapter + * @param \Closure $callback + * @return $this + */ + public function extend(string $adapter, Closure $callback) + { + $this->customCreators[$adapter] = $callback; + + return $this; + } +} diff --git a/app/Extensions/DynamicDatabaseConnection.php b/app/Extensions/DynamicDatabaseConnection.php index 670e75e4..f31a92b1 100644 --- a/app/Extensions/DynamicDatabaseConnection.php +++ b/app/Extensions/DynamicDatabaseConnection.php @@ -38,9 +38,9 @@ class DynamicDatabaseConnection /** * DynamicDatabaseConnection constructor. * - * @param \Illuminate\Config\Repository $config + * @param \Illuminate\Config\Repository $config * @param \Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface $repository - * @param \Illuminate\Contracts\Encryption\Encrypter $encrypter + * @param \Illuminate\Contracts\Encryption\Encrypter $encrypter */ public function __construct( ConfigRepository $config, @@ -55,9 +55,9 @@ class DynamicDatabaseConnection /** * Adds a dynamic database connection entry to the runtime config. * - * @param string $connection + * @param string $connection * @param \Pterodactyl\Models\DatabaseHost|int $host - * @param string $database + * @param string $database * * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ diff --git a/app/Extensions/Facades/Theme.php b/app/Extensions/Facades/Theme.php new file mode 100644 index 00000000..69e8e0da --- /dev/null +++ b/app/Extensions/Facades/Theme.php @@ -0,0 +1,16 @@ +' . PHP_EOL, $this->getUrl($path)); + } + + public function css($path) + { + return sprintf('' . PHP_EOL, $this->getUrl($path)); + } + + protected function getUrl($path) + { + return '/themes/pterodactyl/' . ltrim($path, '/'); + } +} diff --git a/app/Helpers/Utilities.php b/app/Helpers/Utilities.php index 5de685fe..d900425d 100644 --- a/app/Helpers/Utilities.php +++ b/app/Helpers/Utilities.php @@ -3,7 +3,10 @@ namespace Pterodactyl\Helpers; use Exception; +use Carbon\Carbon; +use Cron\CronExpression; use Illuminate\Support\Facades\Log; +use Illuminate\Support\ViewErrorBag; class Utilities { @@ -32,4 +35,31 @@ class Utilities return $string; } + + /** + * Converts schedule cron data into a carbon object. + * + * @param string $minute + * @param string $hour + * @param string $dayOfMonth + * @param string $dayOfWeek + * @return \Carbon\Carbon + */ + public static function getScheduleNextRunDate(string $minute, string $hour, string $dayOfMonth, string $dayOfWeek) + { + return Carbon::instance(CronExpression::factory( + sprintf('%s %s %s * %s', $minute, $hour, $dayOfMonth, $dayOfWeek) + )->getNextRunDate()); + } + + public static function checked($name, $default) + { + $errors = session('errors'); + + if (isset($errors) && $errors instanceof ViewErrorBag && $errors->any()) { + return old($name) ? 'checked' : ''; + } + + return ($default) ? 'checked' : ''; + } } diff --git a/app/Http/Controllers/Admin/ApiController.php b/app/Http/Controllers/Admin/ApiController.php index 2fa541bc..6067b423 100644 --- a/app/Http/Controllers/Admin/ApiController.php +++ b/app/Http/Controllers/Admin/ApiController.php @@ -34,9 +34,9 @@ class ApiController extends Controller /** * ApplicationApiController constructor. * - * @param \Prologue\Alerts\AlertsMessageBag $alert + * @param \Prologue\Alerts\AlertsMessageBag $alert * @param \Pterodactyl\Contracts\Repository\ApiKeyRepositoryInterface $repository - * @param \Pterodactyl\Services\Api\KeyCreationService $keyCreationService + * @param \Pterodactyl\Services\Api\KeyCreationService $keyCreationService */ public function __construct( AlertsMessageBag $alert, @@ -106,7 +106,7 @@ class ApiController extends Controller * Delete an application API key from the database. * * @param \Illuminate\Http\Request $request - * @param string $identifier + * @param string $identifier * @return \Illuminate\Http\Response */ public function delete(Request $request, string $identifier): Response diff --git a/app/Http/Controllers/Admin/DatabaseController.php b/app/Http/Controllers/Admin/DatabaseController.php index 4fecbbcc..0aee8680 100644 --- a/app/Http/Controllers/Admin/DatabaseController.php +++ b/app/Http/Controllers/Admin/DatabaseController.php @@ -57,13 +57,13 @@ class DatabaseController extends Controller /** * DatabaseController constructor. * - * @param \Prologue\Alerts\AlertsMessageBag $alert + * @param \Prologue\Alerts\AlertsMessageBag $alert * @param \Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface $repository - * @param \Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface $databaseRepository - * @param \Pterodactyl\Services\Databases\Hosts\HostCreationService $creationService - * @param \Pterodactyl\Services\Databases\Hosts\HostDeletionService $deletionService - * @param \Pterodactyl\Services\Databases\Hosts\HostUpdateService $updateService - * @param \Pterodactyl\Contracts\Repository\LocationRepositoryInterface $locationRepository + * @param \Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface $databaseRepository + * @param \Pterodactyl\Services\Databases\Hosts\HostCreationService $creationService + * @param \Pterodactyl\Services\Databases\Hosts\HostDeletionService $deletionService + * @param \Pterodactyl\Services\Databases\Hosts\HostUpdateService $updateService + * @param \Pterodactyl\Contracts\Repository\LocationRepositoryInterface $locationRepository */ public function __construct( AlertsMessageBag $alert, @@ -146,7 +146,7 @@ class DatabaseController extends Controller * Handle updating database host. * * @param \Pterodactyl\Http\Requests\Admin\DatabaseHostFormRequest $request - * @param \Pterodactyl\Models\DatabaseHost $host + * @param \Pterodactyl\Models\DatabaseHost $host * @return \Illuminate\Http\RedirectResponse * * @throws \Throwable @@ -165,6 +165,7 @@ class DatabaseController extends Controller $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; diff --git a/app/Http/Controllers/Admin/LocationController.php b/app/Http/Controllers/Admin/LocationController.php index 9239889c..286d15cf 100644 --- a/app/Http/Controllers/Admin/LocationController.php +++ b/app/Http/Controllers/Admin/LocationController.php @@ -49,11 +49,11 @@ class LocationController extends Controller /** * LocationController constructor. * - * @param \Prologue\Alerts\AlertsMessageBag $alert - * @param \Pterodactyl\Services\Locations\LocationCreationService $creationService - * @param \Pterodactyl\Services\Locations\LocationDeletionService $deletionService + * @param \Prologue\Alerts\AlertsMessageBag $alert + * @param \Pterodactyl\Services\Locations\LocationCreationService $creationService + * @param \Pterodactyl\Services\Locations\LocationDeletionService $deletionService * @param \Pterodactyl\Contracts\Repository\LocationRepositoryInterface $repository - * @param \Pterodactyl\Services\Locations\LocationUpdateService $updateService + * @param \Pterodactyl\Services\Locations\LocationUpdateService $updateService */ public function __construct( AlertsMessageBag $alert, @@ -116,7 +116,7 @@ class LocationController extends Controller * Handle request to update or delete location. * * @param \Pterodactyl\Http\Requests\Admin\LocationFormRequest $request - * @param \Pterodactyl\Models\Location $location + * @param \Pterodactyl\Models\Location $location * @return \Illuminate\Http\RedirectResponse * * @throws \Throwable diff --git a/app/Http/Controllers/Admin/MountController.php b/app/Http/Controllers/Admin/MountController.php new file mode 100644 index 00000000..3f40e555 --- /dev/null +++ b/app/Http/Controllers/Admin/MountController.php @@ -0,0 +1,224 @@ +alert = $alert; + $this->nestRepository = $nestRepository; + $this->locationRepository = $locationRepository; + $this->repository = $repository; + } + + /** + * Return the mount overview page. + * + * @return \Illuminate\View\View + */ + public function index() + { + return view('admin.mounts.index', [ + 'mounts' => $this->repository->getAllWithDetails(), + ]); + } + + /** + * Return the mount view page. + * + * @param string $id + * @return \Illuminate\View\View + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function view($id) + { + $nests = Nest::query()->with('eggs')->get(); + $locations = Location::query()->with('nodes')->get(); + + return view('admin.mounts.view', [ + 'mount' => $this->repository->getWithRelations($id), + 'nests' => $nests, + 'locations' => $locations, + ]); + } + + /** + * Handle request to create new mount. + * + * @param \Pterodactyl\Http\Requests\Admin\MountFormRequest $request + * @return \Illuminate\Http\RedirectResponse + * + * @throws \Throwable + */ + public function create(MountFormRequest $request) + { + /** @var \Pterodactyl\Models\Mount $mount */ + $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. + * + * @param \Pterodactyl\Http\Requests\Admin\MountFormRequest $request + * @param \Pterodactyl\Models\Mount $mount + * @return \Illuminate\Http\RedirectResponse + * + * @throws \Throwable + */ + public function update(MountFormRequest $request, Mount $mount) + { + 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. + * + * @param \Pterodactyl\Models\Mount $mount + * @return \Illuminate\Http\RedirectResponse + * + * @throws \Exception + */ + public function delete(Mount $mount) + { + $mount->delete(); + + return redirect()->route('admin.mounts'); + } + + /** + * Adds eggs to the mount's many to many relation. + * + * @param \Illuminate\Http\Request $request + * @param \Pterodactyl\Models\Mount $mount + * @return \Illuminate\Http\RedirectResponse + */ + public function addEggs(Request $request, Mount $mount) + { + $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. + * + * @param \Illuminate\Http\Request $request + * @param \Pterodactyl\Models\Mount $mount + * @return \Illuminate\Http\RedirectResponse + */ + public function addNodes(Request $request, Mount $mount) + { + $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. + * + * @param \Pterodactyl\Models\Mount $mount + * @param int $egg_id + * @return \Illuminate\Http\Response + */ + public function deleteEgg(Mount $mount, int $egg_id) + { + $mount->eggs()->detach($egg_id); + + return response('', 204); + } + + /** + * Deletes an node from the mount's many to many relation. + * + * @param \Pterodactyl\Models\Mount $mount + * @param int $node_id + * @return \Illuminate\Http\Response + */ + public function deleteNode(Mount $mount, int $node_id) + { + $mount->nodes()->detach($node_id); + + return response('', 204); + } +} diff --git a/app/Http/Controllers/Admin/Nests/EggController.php b/app/Http/Controllers/Admin/Nests/EggController.php index 56d69e3a..209dad69 100644 --- a/app/Http/Controllers/Admin/Nests/EggController.php +++ b/app/Http/Controllers/Admin/Nests/EggController.php @@ -25,10 +25,15 @@ use Pterodactyl\Contracts\Repository\NestRepositoryInterface; class EggController extends Controller { protected $alert; + protected $creationService; + protected $deletionService; + protected $nestRepository; + protected $repository; + protected $updateService; public function __construct( @@ -94,7 +99,7 @@ class EggController extends Controller * Handle request to update an Egg. * * @param \Pterodactyl\Http\Requests\Admin\Egg\EggFormRequest $request - * @param \Pterodactyl\Models\Egg $egg + * @param \Pterodactyl\Models\Egg $egg * @return \Illuminate\Http\RedirectResponse * * @throws \Pterodactyl\Exceptions\Model\DataValidationException diff --git a/app/Http/Controllers/Admin/Nests/EggScriptController.php b/app/Http/Controllers/Admin/Nests/EggScriptController.php index ac67a2a6..ea8d4dfa 100644 --- a/app/Http/Controllers/Admin/Nests/EggScriptController.php +++ b/app/Http/Controllers/Admin/Nests/EggScriptController.php @@ -1,15 +1,9 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Http\Controllers\Admin\Nests; use Illuminate\View\View; +use Pterodactyl\Models\Egg; use Illuminate\Http\RedirectResponse; use Prologue\Alerts\AlertsMessageBag; use Pterodactyl\Http\Controllers\Controller; @@ -37,9 +31,9 @@ class EggScriptController extends Controller /** * EggScriptController constructor. * - * @param \Prologue\Alerts\AlertsMessageBag $alert + * @param \Prologue\Alerts\AlertsMessageBag $alert * @param \Pterodactyl\Contracts\Repository\EggRepositoryInterface $repository - * @param \Pterodactyl\Services\Eggs\Scripts\InstallScriptService $installScriptService + * @param \Pterodactyl\Services\Eggs\Scripts\InstallScriptService $installScriptService */ public function __construct( AlertsMessageBag $alert, @@ -81,14 +75,14 @@ class EggScriptController extends Controller * Handle a request to update the installation script for an Egg. * * @param \Pterodactyl\Http\Requests\Admin\Egg\EggScriptFormRequest $request - * @param int $egg + * @param \Pterodactyl\Models\Egg $egg * @return \Illuminate\Http\RedirectResponse * * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException * @throws \Pterodactyl\Exceptions\Service\Egg\InvalidCopyFromException */ - public function update(EggScriptFormRequest $request, int $egg): RedirectResponse + 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(); diff --git a/app/Http/Controllers/Admin/Nests/EggShareController.php b/app/Http/Controllers/Admin/Nests/EggShareController.php index 80e8e30b..7845680e 100644 --- a/app/Http/Controllers/Admin/Nests/EggShareController.php +++ b/app/Http/Controllers/Admin/Nests/EggShareController.php @@ -44,9 +44,9 @@ class EggShareController extends Controller /** * OptionShareController constructor. * - * @param \Prologue\Alerts\AlertsMessageBag $alert - * @param \Pterodactyl\Services\Eggs\Sharing\EggExporterService $exporterService - * @param \Pterodactyl\Services\Eggs\Sharing\EggImporterService $importerService + * @param \Prologue\Alerts\AlertsMessageBag $alert + * @param \Pterodactyl\Services\Eggs\Sharing\EggExporterService $exporterService + * @param \Pterodactyl\Services\Eggs\Sharing\EggImporterService $importerService * @param \Pterodactyl\Services\Eggs\Sharing\EggUpdateImporterService $updateImporterService */ public function __construct( @@ -102,7 +102,7 @@ class EggShareController extends Controller * Update an existing Egg using a new imported file. * * @param \Pterodactyl\Http\Requests\Admin\Egg\EggImportFormRequest $request - * @param int $egg + * @param \Pterodactyl\Models\Egg $egg * @return \Illuminate\Http\RedirectResponse * * @throws \Pterodactyl\Exceptions\Model\DataValidationException @@ -110,7 +110,7 @@ class EggShareController extends Controller * @throws \Pterodactyl\Exceptions\Service\Egg\BadJsonFormatException * @throws \Pterodactyl\Exceptions\Service\InvalidFileUploadException */ - public function update(EggImportFormRequest $request, int $egg): RedirectResponse + 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(); diff --git a/app/Http/Controllers/Admin/Nests/EggVariableController.php b/app/Http/Controllers/Admin/Nests/EggVariableController.php index df29de5a..c0677abb 100644 --- a/app/Http/Controllers/Admin/Nests/EggVariableController.php +++ b/app/Http/Controllers/Admin/Nests/EggVariableController.php @@ -51,10 +51,10 @@ class EggVariableController extends Controller /** * EggVariableController constructor. * - * @param \Prologue\Alerts\AlertsMessageBag $alert - * @param \Pterodactyl\Services\Eggs\Variables\VariableCreationService $creationService - * @param \Pterodactyl\Services\Eggs\Variables\VariableUpdateService $updateService - * @param \Pterodactyl\Contracts\Repository\EggRepositoryInterface $repository + * @param \Prologue\Alerts\AlertsMessageBag $alert + * @param \Pterodactyl\Services\Eggs\Variables\VariableCreationService $creationService + * @param \Pterodactyl\Services\Eggs\Variables\VariableUpdateService $updateService + * @param \Pterodactyl\Contracts\Repository\EggRepositoryInterface $repository * @param \Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface $variableRepository */ public function __construct( @@ -109,8 +109,8 @@ class EggVariableController extends Controller * Handle a request to update an existing Egg variable. * * @param \Pterodactyl\Http\Requests\Admin\Egg\EggVariableFormRequest $request - * @param \Pterodactyl\Models\Egg $egg - * @param \Pterodactyl\Models\EggVariable $variable + * @param \Pterodactyl\Models\Egg $egg + * @param \Pterodactyl\Models\EggVariable $variable * @return \Illuminate\Http\RedirectResponse * * @throws \Pterodactyl\Exceptions\DisplayException @@ -131,7 +131,7 @@ class EggVariableController extends Controller /** * Handle a request to delete an existing Egg variable from the Panel. * - * @param int $egg + * @param int $egg * @param \Pterodactyl\Models\EggVariable $variable * @return \Illuminate\Http\RedirectResponse */ diff --git a/app/Http/Controllers/Admin/Nests/NestController.php b/app/Http/Controllers/Admin/Nests/NestController.php index b62753ca..155df2a5 100644 --- a/app/Http/Controllers/Admin/Nests/NestController.php +++ b/app/Http/Controllers/Admin/Nests/NestController.php @@ -49,11 +49,11 @@ class NestController extends Controller /** * NestController constructor. * - * @param \Prologue\Alerts\AlertsMessageBag $alert - * @param \Pterodactyl\Services\Nests\NestCreationService $nestCreationService - * @param \Pterodactyl\Services\Nests\NestDeletionService $nestDeletionService + * @param \Prologue\Alerts\AlertsMessageBag $alert + * @param \Pterodactyl\Services\Nests\NestCreationService $nestCreationService + * @param \Pterodactyl\Services\Nests\NestDeletionService $nestDeletionService * @param \Pterodactyl\Contracts\Repository\NestRepositoryInterface $repository - * @param \Pterodactyl\Services\Nests\NestUpdateService $nestUpdateService + * @param \Pterodactyl\Services\Nests\NestUpdateService $nestUpdateService */ public function __construct( AlertsMessageBag $alert, @@ -128,7 +128,7 @@ class NestController extends Controller * Handle request to update a nest. * * @param \Pterodactyl\Http\Requests\Admin\Nest\StoreNestFormRequest $request - * @param int $nest + * @param int $nest * * @return \Illuminate\Http\RedirectResponse * @throws \Pterodactyl\Exceptions\Model\DataValidationException diff --git a/app/Http/Controllers/Admin/NodeAutoDeployController.php b/app/Http/Controllers/Admin/NodeAutoDeployController.php new file mode 100644 index 00000000..be6301c3 --- /dev/null +++ b/app/Http/Controllers/Admin/NodeAutoDeployController.php @@ -0,0 +1,88 @@ +keyCreationService = $keyCreationService; + $this->repository = $repository; + $this->encrypter = $encrypter; + } + + /** + * 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. + * + * @param \Illuminate\Http\Request $request + * @param \Pterodactyl\Models\Node $node + * @return \Illuminate\Http\JsonResponse + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + */ + public function __invoke(Request $request, Node $node) + { + /** @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 JsonResponse::create([ + 'node' => $node->id, + 'token' => $key->identifier . $this->encrypter->decrypt($key->token), + ]); + } +} diff --git a/app/Http/Controllers/Admin/Nodes/NodeController.php b/app/Http/Controllers/Admin/Nodes/NodeController.php new file mode 100644 index 00000000..5a7bc1e3 --- /dev/null +++ b/app/Http/Controllers/Admin/Nodes/NodeController.php @@ -0,0 +1,53 @@ +view = $view; + $this->repository = $repository; + } + + /** + * Returns a listing of nodes on the system. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Contracts\View\View + */ + public function index(Request $request) + { + $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]); + } +} diff --git a/app/Http/Controllers/Admin/Nodes/NodeViewController.php b/app/Http/Controllers/Admin/Nodes/NodeViewController.php new file mode 100644 index 00000000..2121985c --- /dev/null +++ b/app/Http/Controllers/Admin/Nodes/NodeViewController.php @@ -0,0 +1,164 @@ +repository = $repository; + $this->view = $view; + $this->versionService = $versionService; + $this->locationRepository = $locationRepository; + $this->allocationRepository = $allocationRepository; + $this->serverRepository = $serverRepository; + } + + /** + * Returns index view for a specific node on the system. + * + * @param \Illuminate\Http\Request $request + * @param \Pterodactyl\Models\Node $node + * @return \Illuminate\Contracts\View\View + */ + public function index(Request $request, Node $node) + { + $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. + * + * @param \Illuminate\Http\Request $request + * @param \Pterodactyl\Models\Node $node + * @return \Illuminate\Contracts\View\View + */ + public function settings(Request $request, Node $node) + { + return $this->view->make('admin.nodes.view.settings', [ + 'node' => $node, + 'locations' => $this->locationRepository->all(), + ]); + } + + /** + * Return the node configuration page for a specific node. + * + * @param \Illuminate\Http\Request $request + * @param \Pterodactyl\Models\Node $node + * @return \Illuminate\Contracts\View\View + */ + public function configuration(Request $request, Node $node) + { + return $this->view->make('admin.nodes.view.configuration', compact('node')); + } + + /** + * Return the node allocation management page. + * + * @param \Illuminate\Http\Request $request + * @param \Pterodactyl\Models\Node $node + * @return \Illuminate\Contracts\View\View + */ + public function allocations(Request $request, Node $node) + { + $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. + * + * @param \Illuminate\Http\Request $request + * @param \Pterodactyl\Models\Node $node + * @return \Illuminate\Contracts\View\View + */ + public function servers(Request $request, Node $node) + { + $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), + ]); + } +} diff --git a/app/Http/Controllers/Admin/Nodes/SystemInformationController.php b/app/Http/Controllers/Admin/Nodes/SystemInformationController.php new file mode 100644 index 00000000..4e1a3fe8 --- /dev/null +++ b/app/Http/Controllers/Admin/Nodes/SystemInformationController.php @@ -0,0 +1,52 @@ +repository = $repository; + } + + /** + * Returns system information from the Daemon. + * + * @param \Illuminate\Http\Request $request + * @param \Pterodactyl\Models\Node $node + * @return \Illuminate\Http\JsonResponse + * + * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException + */ + public function __invoke(Request $request, Node $node) + { + $data = $this->repository->setNode($node)->getSystemInformation(); + + return JsonResponse::create([ + 'version' => $data['version'] ?? '', + 'system' => [ + 'type' => Str::title($data['os'] ?? 'Unknown'), + 'arch' => $data['architecture'] ?? '--', + 'release' => $data['kernel_version'] ?? '--', + 'cpus' => $data['cpu_count'] ?? 0, + ], + ]); + } +} diff --git a/app/Http/Controllers/Admin/NodesController.php b/app/Http/Controllers/Admin/NodesController.php index 28266eac..d482c47a 100644 --- a/app/Http/Controllers/Admin/NodesController.php +++ b/app/Http/Controllers/Admin/NodesController.php @@ -9,7 +9,6 @@ namespace Pterodactyl\Http\Controllers\Admin; -use Javascript; use Illuminate\Http\Request; use Pterodactyl\Models\Node; use Illuminate\Http\Response; @@ -96,18 +95,18 @@ class NodesController extends Controller /** * NodesController constructor. * - * @param \Prologue\Alerts\AlertsMessageBag $alert - * @param \Pterodactyl\Services\Allocations\AllocationDeletionService $allocationDeletionService + * @param \Prologue\Alerts\AlertsMessageBag $alert + * @param \Pterodactyl\Services\Allocations\AllocationDeletionService $allocationDeletionService * @param \Pterodactyl\Contracts\Repository\AllocationRepositoryInterface $allocationRepository - * @param \Pterodactyl\Services\Allocations\AssignmentService $assignmentService - * @param \Illuminate\Cache\Repository $cache - * @param \Pterodactyl\Services\Nodes\NodeCreationService $creationService - * @param \Pterodactyl\Services\Nodes\NodeDeletionService $deletionService - * @param \Pterodactyl\Contracts\Repository\LocationRepositoryInterface $locationRepository - * @param \Pterodactyl\Contracts\Repository\NodeRepositoryInterface $repository - * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $serverRepository - * @param \Pterodactyl\Services\Nodes\NodeUpdateService $updateService - * @param \Pterodactyl\Services\Helpers\SoftwareVersionService $versionService + * @param \Pterodactyl\Services\Allocations\AssignmentService $assignmentService + * @param \Illuminate\Cache\Repository $cache + * @param \Pterodactyl\Services\Nodes\NodeCreationService $creationService + * @param \Pterodactyl\Services\Nodes\NodeDeletionService $deletionService + * @param \Pterodactyl\Contracts\Repository\LocationRepositoryInterface $locationRepository + * @param \Pterodactyl\Contracts\Repository\NodeRepositoryInterface $repository + * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $serverRepository + * @param \Pterodactyl\Services\Nodes\NodeUpdateService $updateService + * @param \Pterodactyl\Services\Helpers\SoftwareVersionService $versionService */ public function __construct( AlertsMessageBag $alert, @@ -137,19 +136,6 @@ class NodesController extends Controller $this->versionService = $versionService; } - /** - * Displays the index page listing all nodes on the panel. - * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\View\View - */ - public function index(Request $request) - { - return view('admin.nodes.index', [ - 'nodes' => $this->repository->setSearchTerm($request->input('query'))->getNodeListingData(), - ]); - } - /** * Displays create new node page. * @@ -183,84 +169,11 @@ class NodesController extends Controller return redirect()->route('admin.nodes.view.allocation', $node->id); } - /** - * Shows the index overview page for a specific node. - * - * @param \Pterodactyl\Models\Node $node - * @return \Illuminate\View\View - */ - public function viewIndex(Node $node) - { - return view('admin.nodes.view.index', [ - 'node' => $this->repository->loadLocationAndServerCount($node), - 'stats' => $this->repository->getUsageStats($node), - 'version' => $this->versionService, - ]); - } - - /** - * Shows the settings page for a specific node. - * - * @param \Pterodactyl\Models\Node $node - * @return \Illuminate\View\View - */ - public function viewSettings(Node $node) - { - return view('admin.nodes.view.settings', [ - 'node' => $node, - 'locations' => $this->locationRepository->all(), - ]); - } - - /** - * Shows the configuration page for a specific node. - * - * @param \Pterodactyl\Models\Node $node - * @return \Illuminate\View\View - */ - public function viewConfiguration(Node $node) - { - return view('admin.nodes.view.configuration', ['node' => $node]); - } - - /** - * Shows the allocation page for a specific node. - * - * @param \Pterodactyl\Models\Node $node - * @return \Illuminate\View\View - */ - public function viewAllocation(Node $node) - { - $this->repository->loadNodeAllocations($node); - Javascript::put(['node' => collect($node)->only(['id'])]); - - return view('admin.nodes.view.allocation', [ - 'allocations' => $this->allocationRepository->setColumns(['ip'])->getUniqueAllocationIpsForNode($node->id), - 'node' => $node, - ]); - } - - /** - * Shows the server listing page for a specific node. - * - * @param \Pterodactyl\Models\Node $node - * @return \Illuminate\View\View - */ - public function viewServers(Node $node) - { - $servers = $this->serverRepository->loadAllServersForNode($node->id, 25); - Javascript::put([ - 'node' => collect($node->makeVisible('daemonSecret'))->only(['scheme', 'fqdn', 'daemonListen', 'daemonSecret']), - ]); - - return view('admin.nodes.view.servers', ['node' => $node, 'servers' => $servers]); - } - /** * Updates settings for a node. * * @param \Pterodactyl\Http\Requests\Admin\Node\NodeFormRequest $request - * @param \Pterodactyl\Models\Node $node + * @param \Pterodactyl\Models\Node $node * @return \Illuminate\Http\RedirectResponse * * @throws \Pterodactyl\Exceptions\DisplayException @@ -278,7 +191,7 @@ class NodesController extends Controller /** * Removes a single allocation from a node. * - * @param int $node + * @param int $node * @param \Pterodactyl\Models\Allocation $allocation * @return \Illuminate\Http\Response * @@ -295,7 +208,7 @@ class NodesController extends Controller * Removes multiple individual allocations from a node. * * @param \Illuminate\Http\Request $request - * @param int $node + * @param int $node * @return \Illuminate\Http\Response * * @throws \Pterodactyl\Exceptions\Service\Allocation\ServerUsingAllocationException @@ -316,7 +229,7 @@ class NodesController extends Controller * Remove all allocations for a specific IP at once on a node. * * @param \Illuminate\Http\Request $request - * @param int $node + * @param int $node * @return \Illuminate\Http\RedirectResponse */ public function allocationRemoveBlock(Request $request, $node) @@ -355,7 +268,7 @@ class NodesController extends Controller * Creates new allocations on a node. * * @param \Pterodactyl\Http\Requests\Admin\Node\AllocationFormRequest $request - * @param int|\Pterodactyl\Models\Node $node + * @param int|\Pterodactyl\Models\Node $node * @return \Illuminate\Http\RedirectResponse * * @throws \Pterodactyl\Exceptions\Service\Allocation\CidrOutOfRangeException @@ -386,18 +299,4 @@ class NodesController extends Controller return redirect()->route('admin.nodes'); } - - /** - * Returns the configuration token to auto-deploy a node. - * - * @param \Pterodactyl\Models\Node $node - * @return \Illuminate\Http\JsonResponse - */ - public function setToken(Node $node) - { - $token = bin2hex(random_bytes(16)); - $this->cache->put('Node:Configuration:' . $token, $node->id, 5); - - return response()->json(['token' => $token]); - } } diff --git a/app/Http/Controllers/Admin/PackController.php b/app/Http/Controllers/Admin/PackController.php deleted file mode 100644 index 1dd7c881..00000000 --- a/app/Http/Controllers/Admin/PackController.php +++ /dev/null @@ -1,250 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -namespace Pterodactyl\Http\Controllers\Admin; - -use Illuminate\Http\Request; -use Pterodactyl\Models\Pack; -use Prologue\Alerts\AlertsMessageBag; -use Pterodactyl\Http\Controllers\Controller; -use Pterodactyl\Services\Packs\ExportPackService; -use Pterodactyl\Services\Packs\PackUpdateService; -use Pterodactyl\Services\Packs\PackCreationService; -use Pterodactyl\Services\Packs\PackDeletionService; -use Pterodactyl\Http\Requests\Admin\PackFormRequest; -use Pterodactyl\Services\Packs\TemplateUploadService; -use Pterodactyl\Contracts\Repository\NestRepositoryInterface; -use Pterodactyl\Contracts\Repository\PackRepositoryInterface; -use Illuminate\Contracts\Config\Repository as ConfigRepository; - -class PackController extends Controller -{ - /** - * @var \Prologue\Alerts\AlertsMessageBag - */ - protected $alert; - - /** - * @var \Illuminate\Contracts\Config\Repository - */ - protected $config; - - /** - * @var \Pterodactyl\Services\Packs\PackCreationService - */ - protected $creationService; - - /** - * @var \Pterodactyl\Services\Packs\PackDeletionService - */ - protected $deletionService; - - /** - * @var \Pterodactyl\Services\Packs\ExportPackService - */ - protected $exportService; - - /** - * @var \Pterodactyl\Contracts\Repository\PackRepositoryInterface - */ - protected $repository; - - /** - * @var \Pterodactyl\Services\Packs\PackUpdateService - */ - protected $updateService; - - /** - * @var \Pterodactyl\Contracts\Repository\NestRepositoryInterface - */ - protected $serviceRepository; - - /** - * @var \Pterodactyl\Services\Packs\TemplateUploadService - */ - protected $templateUploadService; - - /** - * PackController constructor. - * - * @param \Prologue\Alerts\AlertsMessageBag $alert - * @param \Illuminate\Contracts\Config\Repository $config - * @param \Pterodactyl\Services\Packs\ExportPackService $exportService - * @param \Pterodactyl\Services\Packs\PackCreationService $creationService - * @param \Pterodactyl\Services\Packs\PackDeletionService $deletionService - * @param \Pterodactyl\Contracts\Repository\PackRepositoryInterface $repository - * @param \Pterodactyl\Services\Packs\PackUpdateService $updateService - * @param \Pterodactyl\Contracts\Repository\NestRepositoryInterface $serviceRepository - * @param \Pterodactyl\Services\Packs\TemplateUploadService $templateUploadService - */ - public function __construct( - AlertsMessageBag $alert, - ConfigRepository $config, - ExportPackService $exportService, - PackCreationService $creationService, - PackDeletionService $deletionService, - PackRepositoryInterface $repository, - PackUpdateService $updateService, - NestRepositoryInterface $serviceRepository, - TemplateUploadService $templateUploadService - ) { - $this->alert = $alert; - $this->config = $config; - $this->creationService = $creationService; - $this->deletionService = $deletionService; - $this->exportService = $exportService; - $this->repository = $repository; - $this->updateService = $updateService; - $this->serviceRepository = $serviceRepository; - $this->templateUploadService = $templateUploadService; - } - - /** - * Display listing of all packs on the system. - * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\View\View - */ - public function index(Request $request) - { - return view('admin.packs.index', [ - 'packs' => $this->repository->setSearchTerm($request->input('query'))->paginateWithEggAndServerCount(), - ]); - } - - /** - * Display new pack creation form. - * - * @return \Illuminate\View\View - * - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - */ - public function create() - { - return view('admin.packs.new', [ - 'nests' => $this->serviceRepository->getWithEggs(), - ]); - } - - /** - * Display new pack creation modal for use with template upload. - * - * @return \Illuminate\View\View - * - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - */ - public function newTemplate() - { - return view('admin.packs.modal', [ - 'nests' => $this->serviceRepository->getWithEggs(), - ]); - } - - /** - * Handle create pack request and route user to location. - * - * @param \Pterodactyl\Http\Requests\Admin\PackFormRequest $request - * @return \Illuminate\View\View - * - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Service\Pack\InvalidFileMimeTypeException - * @throws \Pterodactyl\Exceptions\Service\InvalidFileUploadException - * @throws \Pterodactyl\Exceptions\Service\Pack\InvalidPackArchiveFormatException - * @throws \Pterodactyl\Exceptions\Service\Pack\UnreadableZipArchiveException - * @throws \Pterodactyl\Exceptions\Service\Pack\ZipExtractionException - */ - public function store(PackFormRequest $request) - { - if ($request->filled('from_template')) { - $pack = $this->templateUploadService->handle($request->input('egg_id'), $request->file('file_upload')); - } else { - $pack = $this->creationService->handle($request->normalize(), $request->file('file_upload')); - } - - $this->alert->success(trans('admin/pack.notices.pack_created'))->flash(); - - return redirect()->route('admin.packs.view', $pack->id); - } - - /** - * Display pack view template to user. - * - * @param \Pterodactyl\Models\Pack $pack - * @return \Illuminate\View\View - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - */ - public function view(Pack $pack) - { - return view('admin.packs.view', [ - 'pack' => $this->repository->loadServerData($pack), - 'nests' => $this->serviceRepository->getWithEggs(), - ]); - } - - /** - * Handle updating or deleting pack information. - * - * @param \Pterodactyl\Http\Requests\Admin\PackFormRequest $request - * @param \Pterodactyl\Models\Pack $pack - * @return \Illuminate\Http\RedirectResponse - * - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - * @throws \Pterodactyl\Exceptions\Service\HasActiveServersException - */ - public function update(PackFormRequest $request, Pack $pack) - { - $this->updateService->handle($pack, $request->normalize()); - $this->alert->success(trans('admin/pack.notices.pack_updated'))->flash(); - - return redirect()->route('admin.packs.view', $pack->id); - } - - /** - * Delete a pack if no servers are attached to it currently. - * - * @param \Pterodactyl\Models\Pack $pack - * @return \Illuminate\Http\RedirectResponse - * - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - * @throws \Pterodactyl\Exceptions\Service\HasActiveServersException - */ - public function destroy(Pack $pack) - { - $this->deletionService->handle($pack->id); - $this->alert->success(trans('admin/pack.notices.pack_deleted', [ - 'name' => $pack->name, - ]))->flash(); - - return redirect()->route('admin.packs'); - } - - /** - * Creates an archive of the pack and downloads it to the browser. - * - * @param \Pterodactyl\Models\Pack $pack - * @param bool|string $files - * @return \Symfony\Component\HttpFoundation\Response - * - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - * @throws \Pterodactyl\Exceptions\Service\Pack\ZipArchiveCreationException - */ - public function export(Pack $pack, $files = false) - { - $filename = $this->exportService->handle($pack, is_string($files)); - - if (is_string($files)) { - return response()->download($filename, 'pack-' . $pack->name . '.zip')->deleteFileAfterSend(true); - } - - return response()->download($filename, 'pack-' . $pack->name . '.json', [ - 'Content-Type' => 'application/json', - ])->deleteFileAfterSend(true); - } -} diff --git a/app/Http/Controllers/Admin/Servers/CreateServerController.php b/app/Http/Controllers/Admin/Servers/CreateServerController.php new file mode 100644 index 00000000..f63cf814 --- /dev/null +++ b/app/Http/Controllers/Admin/Servers/CreateServerController.php @@ -0,0 +1,132 @@ +repository = $repository; + $this->nodeRepository = $nodeRepository; + $this->alert = $alert; + $this->nestRepository = $nestRepository; + $this->locationRepository = $locationRepository; + $this->creationService = $creationService; + } + + /** + * Displays the create server page. + * + * @return \Illuminate\Contracts\View\Factory + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function index() + { + $nodes = $this->nodeRepository->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 view('admin.servers.new', [ + 'locations' => $this->locationRepository->all(), + 'nests' => $nests, + ]); + } + + /** + * Create a new server on the remote system. + * + * @param \Pterodactyl\Http\Requests\Admin\ServerFormRequest $request + * @return \Illuminate\Http\RedirectResponse + * + * @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 + * @throws \Throwable + */ + public function store(ServerFormRequest $request) + { + $server = $this->creationService->handle( + $request->except(['_token']) + ); + + $this->alert->success( + trans('admin/server.alerts.server_created') + )->flash(); + + return RedirectResponse::create('/admin/servers/view/' . $server->id); + } +} diff --git a/app/Http/Controllers/Admin/Servers/ServerController.php b/app/Http/Controllers/Admin/Servers/ServerController.php new file mode 100644 index 00000000..a0b73f55 --- /dev/null +++ b/app/Http/Controllers/Admin/Servers/ServerController.php @@ -0,0 +1,54 @@ +view = $view; + $this->repository = $repository; + } + + /** + * Returns all of 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. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Contracts\View\View + */ + public function index(Request $request) + { + $servers = QueryBuilder::for(Server::query()->with('node', 'user', 'allocation')) + ->allowedFilters(['uuid', 'name', 'image']) + ->allowedSorts(['id', 'uuid']) + ->paginate(config()->get('pterodactyl.paginate.admin.servers')); + + return $this->view->make('admin.servers.index', ['servers' => $servers]); + } +} diff --git a/app/Http/Controllers/Admin/Servers/ServerTransferController.php b/app/Http/Controllers/Admin/Servers/ServerTransferController.php new file mode 100644 index 00000000..323ffb7b --- /dev/null +++ b/app/Http/Controllers/Admin/Servers/ServerTransferController.php @@ -0,0 +1,177 @@ +alert = $alert; + $this->allocationRepository = $allocationRepository; + $this->repository = $repository; + $this->locationRepository = $locationRepository; + $this->nodeRepository = $nodeRepository; + $this->suspensionService = $suspensionService; + $this->transferService = $transferService; + $this->daemonConfigurationRepository = $daemonConfigurationRepository; + } + + /** + * Starts a transfer of a server to a new node. + * + * @param \Illuminate\Http\Request $request + * @param \Pterodactyl\Models\Server $server + * @return \Illuminate\Http\RedirectResponse + * + * @throws \Throwable + */ + public function transfer(Request $request, Server $server) + { + $validatedData = $request->validate([ + 'node_id' => 'required|exists:nodes,id', + 'allocation_id' => 'required|bail|unique:servers|exists:allocations,id', + 'allocation_additional' => 'nullable', + ]); + + $node_id = $validatedData['node_id']; + $allocation_id = intval($validatedData['allocation_id']); + $additional_allocations = array_map('intval', $validatedData['allocation_additional'] ?? []); + + // Check if the node is viable for the transfer. + $node = $this->nodeRepository->getNodeWithResourceUsage($node_id); + if ($node->isViable($server->memory, $server->disk)) { + // Check if the selected daemon is online. + $this->daemonConfigurationRepository->setNode($node)->getSystemInformation(); + + // Suspend the server and request an archive to be created. + $this->suspensionService->toggle($server, 'suspend'); + + // 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 = json_encode($server->allocations->where('id', '!=', $server->allocation_id)->pluck('id')); + $transfer->new_additional_allocations = json_encode($additional_allocations); + + $transfer->save(); + + // Add the allocations to the server so they cannot be automatically assigned while the transfer is in progress. + $this->assignAllocationsToServer($server, $node_id, $allocation_id, $additional_allocations); + + // Request an archive from the server's current daemon. (this also checks if the daemon is online) + $this->transferService->requestArchive($server); + + $this->alert->success(trans('admin/server.alerts.transfer_started'))->flash(); + } else { + $this->alert->danger(trans('admin/server.alerts.transfer_not_viable'))->flash(); + } + + return redirect()->route('admin.servers.view.manage', $server->id); + } + + /** + * Assigns the specified allocations to the specified server. + * + * @param Server $server + * @param int $node_id + * @param int $allocation_id + * @param array $additional_allocations + */ + private function assignAllocationsToServer(Server $server, int $node_id, int $allocation_id, array $additional_allocations) + { + $allocations = $additional_allocations; + array_push($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]); + } + } +} diff --git a/app/Http/Controllers/Admin/Servers/ServerViewController.php b/app/Http/Controllers/Admin/Servers/ServerViewController.php new file mode 100644 index 00000000..5c2440b2 --- /dev/null +++ b/app/Http/Controllers/Admin/Servers/ServerViewController.php @@ -0,0 +1,245 @@ +view = $view; + $this->databaseHostRepository = $databaseHostRepository; + $this->locationRepository = $locationRepository; + $this->mountRepository = $mountRepository; + $this->nestRepository = $nestRepository; + $this->nodeRepository = $nodeRepository; + $this->repository = $repository; + $this->environmentService = $environmentService; + } + + /** + * Returns the index view for a server. + * + * @param \Illuminate\Http\Request $request + * @param \Pterodactyl\Models\Server $server + * @return \Illuminate\Contracts\View\View + */ + public function index(Request $request, Server $server) + { + return $this->view->make('admin.servers.view.index', compact('server')); + } + + /** + * Returns the server details page. + * + * @param \Illuminate\Http\Request $request + * @param \Pterodactyl\Models\Server $server + * @return \Illuminate\Contracts\View\View + */ + public function details(Request $request, Server $server) + { + return $this->view->make('admin.servers.view.details', compact('server')); + } + + /** + * Returns a view of server build settings. + * + * @param \Illuminate\Http\Request $request + * @param \Pterodactyl\Models\Server $server + * @return \Illuminate\Contracts\View\View + */ + public function build(Request $request, Server $server) + { + $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. + * + * @param \Illuminate\Http\Request $request + * @param \Pterodactyl\Models\Server $server + * @return \Illuminate\Contracts\View\View + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function startup(Request $request, Server $server) + { + $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 of the databases that exist for the server. + * + * @param \Illuminate\Http\Request $request + * @param \Pterodactyl\Models\Server $server + * @return \Illuminate\Contracts\View\View + */ + public function database(Request $request, Server $server) + { + return $this->view->make('admin.servers.view.database', [ + 'hosts' => $this->databaseHostRepository->all(), + 'server' => $server, + ]); + } + + /** + * Returns all of the mounts that exist for the server. + * + * @param \Illuminate\Http\Request $request + * @param \Pterodactyl\Models\Server $server + * @return \Illuminate\Contracts\View\View + */ + public function mounts(Request $request, Server $server) + { + $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. + * + * @param \Illuminate\Http\Request $request + * @param \Pterodactyl\Models\Server $server + * @return \Illuminate\Contracts\View\View + * + * @throws \Pterodactyl\Exceptions\DisplayException + */ + public function manage(Request $request, Server $server) + { + if ($server->installed > 1) { + 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. + * + * @param \Illuminate\Http\Request $request + * @param \Pterodactyl\Models\Server $server + * @return \Illuminate\Contracts\View\View + */ + public function delete(Request $request, Server $server) + { + return $this->view->make('admin.servers.view.delete', compact('server')); + } +} diff --git a/app/Http/Controllers/Admin/ServersController.php b/app/Http/Controllers/Admin/ServersController.php index 6bf21b24..5555f658 100644 --- a/app/Http/Controllers/Admin/ServersController.php +++ b/app/Http/Controllers/Admin/ServersController.php @@ -9,32 +9,35 @@ namespace Pterodactyl\Http\Controllers\Admin; -use Javascript; +use Illuminate\Support\Arr; use Illuminate\Http\Request; use Pterodactyl\Models\User; +use Pterodactyl\Models\Mount; use Pterodactyl\Models\Server; use Prologue\Alerts\AlertsMessageBag; +use GuzzleHttp\Exception\RequestException; use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Http\Controllers\Controller; +use Illuminate\Validation\ValidationException; use Pterodactyl\Services\Servers\SuspensionService; -use Pterodactyl\Http\Requests\Admin\ServerFormRequest; -use Pterodactyl\Services\Servers\ServerCreationService; +use Pterodactyl\Repositories\Eloquent\MountRepository; use Pterodactyl\Services\Servers\ServerDeletionService; use Pterodactyl\Services\Servers\ReinstallServerService; -use Pterodactyl\Services\Servers\ContainerRebuildService; +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\Contracts\Repository\NodeRepositoryInterface; 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\LocationRepositoryInterface; use Pterodactyl\Contracts\Repository\AllocationRepositoryInterface; +use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException; +use Pterodactyl\Services\Servers\ServerConfigurationStructureService; use Pterodactyl\Http\Requests\Admin\Servers\Databases\StoreServerDatabaseRequest; class ServersController extends Controller @@ -60,9 +63,9 @@ class ServersController extends Controller protected $config; /** - * @var \Pterodactyl\Services\Servers\ContainerRebuildService + * @var \Pterodactyl\Repositories\Wings\DaemonServerRepository */ - protected $containerRebuildService; + private $daemonServerRepository; /** * @var \Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface @@ -95,20 +98,15 @@ class ServersController extends Controller protected $detailsModificationService; /** - * @var \Pterodactyl\Contracts\Repository\LocationRepositoryInterface + * @var \Pterodactyl\Repositories\Eloquent\MountRepository */ - protected $locationRepository; + protected $mountRepository; /** * @var \Pterodactyl\Contracts\Repository\NestRepositoryInterface */ protected $nestRepository; - /** - * @var \Pterodactyl\Contracts\Repository\NodeRepositoryInterface - */ - protected $nodeRepository; - /** * @var \Pterodactyl\Services\Servers\ReinstallServerService */ @@ -120,9 +118,9 @@ class ServersController extends Controller protected $repository; /** - * @var \Pterodactyl\Services\Servers\ServerCreationService + * @var \Pterodactyl\Services\Servers\ServerConfigurationStructureService */ - protected $service; + private $serverConfigurationStructureService; /** * @var \Pterodactyl\Services\Servers\StartupModificationService @@ -137,44 +135,42 @@ class ServersController extends Controller /** * ServersController constructor. * - * @param \Prologue\Alerts\AlertsMessageBag $alert + * @param \Prologue\Alerts\AlertsMessageBag $alert * @param \Pterodactyl\Contracts\Repository\AllocationRepositoryInterface $allocationRepository - * @param \Pterodactyl\Services\Servers\BuildModificationService $buildModificationService - * @param \Illuminate\Contracts\Config\Repository $config - * @param \Pterodactyl\Services\Servers\ContainerRebuildService $containerRebuildService - * @param \Pterodactyl\Services\Servers\ServerCreationService $service - * @param \Pterodactyl\Services\Databases\DatabaseManagementService $databaseManagementService - * @param \Pterodactyl\Services\Databases\DatabasePasswordService $databasePasswordService - * @param \Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface $databaseRepository - * @param \Pterodactyl\Repositories\Eloquent\DatabaseHostRepository $databaseHostRepository - * @param \Pterodactyl\Services\Servers\ServerDeletionService $deletionService - * @param \Pterodactyl\Services\Servers\DetailsModificationService $detailsModificationService - * @param \Pterodactyl\Contracts\Repository\LocationRepositoryInterface $locationRepository - * @param \Pterodactyl\Contracts\Repository\NodeRepositoryInterface $nodeRepository - * @param \Pterodactyl\Services\Servers\ReinstallServerService $reinstallService - * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository - * @param \Pterodactyl\Contracts\Repository\NestRepositoryInterface $nestRepository - * @param \Pterodactyl\Services\Servers\StartupModificationService $startupModificationService - * @param \Pterodactyl\Services\Servers\SuspensionService $suspensionService + * @param \Pterodactyl\Services\Servers\BuildModificationService $buildModificationService + * @param \Illuminate\Contracts\Config\Repository $config + * @param \Pterodactyl\Repositories\Wings\DaemonServerRepository $daemonServerRepository + * @param \Pterodactyl\Services\Databases\DatabaseManagementService $databaseManagementService + * @param \Pterodactyl\Services\Databases\DatabasePasswordService $databasePasswordService + * @param \Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface $databaseRepository + * @param \Pterodactyl\Repositories\Eloquent\DatabaseHostRepository $databaseHostRepository + * @param \Pterodactyl\Services\Servers\ServerDeletionService $deletionService + * @param \Pterodactyl\Services\Servers\DetailsModificationService $detailsModificationService + * @param \Pterodactyl\Services\Servers\ReinstallServerService $reinstallService + * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository + * @param \Pterodactyl\Repositories\Eloquent\MountRepository $mountRepository + * @param \Pterodactyl\Contracts\Repository\NestRepositoryInterface $nestRepository + * @param \Pterodactyl\Services\Servers\ServerConfigurationStructureService $serverConfigurationStructureService + * @param \Pterodactyl\Services\Servers\StartupModificationService $startupModificationService + * @param \Pterodactyl\Services\Servers\SuspensionService $suspensionService */ public function __construct( AlertsMessageBag $alert, AllocationRepositoryInterface $allocationRepository, BuildModificationService $buildModificationService, ConfigRepository $config, - ContainerRebuildService $containerRebuildService, - ServerCreationService $service, + DaemonServerRepository $daemonServerRepository, DatabaseManagementService $databaseManagementService, DatabasePasswordService $databasePasswordService, DatabaseRepositoryInterface $databaseRepository, DatabaseHostRepository $databaseHostRepository, ServerDeletionService $deletionService, DetailsModificationService $detailsModificationService, - LocationRepositoryInterface $locationRepository, - NodeRepositoryInterface $nodeRepository, ReinstallServerService $reinstallService, ServerRepositoryInterface $repository, + MountRepository $mountRepository, NestRepositoryInterface $nestRepository, + ServerConfigurationStructureService $serverConfigurationStructureService, StartupModificationService $startupModificationService, SuspensionService $suspensionService ) { @@ -182,231 +178,29 @@ class ServersController extends Controller $this->allocationRepository = $allocationRepository; $this->buildModificationService = $buildModificationService; $this->config = $config; - $this->containerRebuildService = $containerRebuildService; + $this->daemonServerRepository = $daemonServerRepository; $this->databaseHostRepository = $databaseHostRepository; $this->databaseManagementService = $databaseManagementService; $this->databasePasswordService = $databasePasswordService; $this->databaseRepository = $databaseRepository; $this->detailsModificationService = $detailsModificationService; $this->deletionService = $deletionService; - $this->locationRepository = $locationRepository; $this->nestRepository = $nestRepository; - $this->nodeRepository = $nodeRepository; $this->reinstallService = $reinstallService; $this->repository = $repository; - $this->service = $service; + $this->mountRepository = $mountRepository; + $this->serverConfigurationStructureService = $serverConfigurationStructureService; $this->startupModificationService = $startupModificationService; $this->suspensionService = $suspensionService; } - /** - * Display the index page with all servers currently on the system. - * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\View\View - */ - public function index(Request $request) - { - return view('admin.servers.index', [ - 'servers' => $this->repository->setSearchTerm($request->input('query'))->getAllServers( - $this->config->get('pterodactyl.paginate.admin.servers') - ), - ]); - } - - /** - * Display create new server page. - * - * @return \Illuminate\View\View - * - * @throws \Exception - */ - public function create() - { - $nodes = $this->nodeRepository->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 view('admin.servers.new', [ - 'locations' => $this->locationRepository->all(), - 'nests' => $nests, - ]); - } - - /** - * Handle POST of server creation form. - * - * @param \Pterodactyl\Http\Requests\Admin\ServerFormRequest $request - * @return \Illuminate\Http\RedirectResponse - * - * @throws \Illuminate\Validation\ValidationException - * @throws \Pterodactyl\Exceptions\DisplayException - * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException - * @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(ServerFormRequest $request) - { - $server = $this->service->handle($request->except('_token')); - $this->alert->success(trans('admin/server.alerts.server_created'))->flash(); - - return redirect()->route('admin.servers.view', $server->id); - } - - /** - * Display the index when viewing a specific server. - * - * @param \Pterodactyl\Models\Server $server - * @return \Illuminate\View\View - */ - public function viewIndex(Server $server) - { - return view('admin.servers.view.index', ['server' => $server]); - } - - /** - * Display the details page when viewing a specific server. - * - * @param int $server - * @return \Illuminate\View\View - * - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - */ - public function viewDetails($server) - { - return view('admin.servers.view.details', [ - 'server' => $this->repository->findFirstWhere([ - ['id', '=', $server], - ['installed', '=', 1], - ]), - ]); - } - - /** - * Display the build details page when viewing a specific server. - * - * @param int $server - * @return \Illuminate\View\View - * - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - */ - public function viewBuild($server) - { - $server = $this->repository->findFirstWhere([ - ['id', '=', $server], - ['installed', '=', 1], - ]); - - $allocations = $this->allocationRepository->getAllocationsForNode($server->node_id); - - return view('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'), - ]); - } - - /** - * Display startup configuration page for a server. - * - * @param \Pterodactyl\Models\Server $server - * @return \Illuminate\View\View - * - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - */ - public function viewStartup(Server $server) - { - $parameters = $this->repository->getVariablesWithValues($server->id, true); - if (! $parameters->server->installed) { - abort(404); - } - - $nests = $this->nestRepository->getWithEggs(); - - Javascript::put([ - 'server' => $server, - 'nests' => $nests->map(function ($item) { - return array_merge($item->toArray(), [ - 'eggs' => $item->eggs->keyBy('id')->toArray(), - ]); - })->keyBy('id'), - 'server_variables' => $parameters->data, - ]); - - return view('admin.servers.view.startup', [ - 'server' => $parameters->server, - 'nests' => $nests, - ]); - } - - /** - * Display the database management page for a specific server. - * - * @param \Pterodactyl\Models\Server $server - * @return \Illuminate\View\View - */ - public function viewDatabase(Server $server) - { - $this->repository->loadDatabaseRelations($server); - - return view('admin.servers.view.database', [ - 'hosts' => $this->databaseHostRepository->all(), - 'server' => $server, - ]); - } - - /** - * Display the management page when viewing a specific server. - * - * @param \Pterodactyl\Models\Server $server - * @return \Illuminate\View\View - * - * @throws \Pterodactyl\Exceptions\DisplayException - */ - public function viewManage(Server $server) - { - if ($server->installed > 1) { - throw new DisplayException('This server is in a failed installation state and must be deleted and recreated.'); - } - - return view('admin.servers.view.manage', ['server' => $server]); - } - - /** - * Display the deletion page for a server. - * - * @param \Pterodactyl\Models\Server $server - * @return \Illuminate\View\View - */ - public function viewDelete(Server $server) - { - return view('admin.servers.view.delete', ['server' => $server]); - } - /** * Update the details for a server. * - * @param \Illuminate\Http\Request $request + * @param \Illuminate\Http\Request $request * @param \Pterodactyl\Models\Server $server * @return \Illuminate\Http\RedirectResponse * - * @throws \Pterodactyl\Exceptions\DisplayException * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ @@ -447,7 +241,7 @@ class ServersController extends Controller } /** - * Reinstalls the server with the currently assigned pack and service. + * Reinstalls the server with the currently assigned service. * * @param \Pterodactyl\Models\Server $server * @return \Illuminate\Http\RedirectResponse @@ -458,31 +252,16 @@ class ServersController extends Controller */ public function reinstallServer(Server $server) { - $this->reinstallService->reinstall($server); + $this->reinstallService->handle($server); $this->alert->success(trans('admin/server.alerts.server_reinstalled'))->flash(); return redirect()->route('admin.servers.view.manage', $server->id); } - /** - * Setup a server to have a container rebuild. - * - * @param \Pterodactyl\Models\Server $server - * @return \Illuminate\Http\RedirectResponse - * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException - */ - public function rebuildContainer(Server $server) - { - $this->containerRebuildService->handle($server); - $this->alert->success(trans('admin/server.alerts.rebuild_on_boot'))->flash(); - - return redirect()->route('admin.servers.view.manage', $server->id); - } - /** * Manage the suspension status for a server. * - * @param \Illuminate\Http\Request $request + * @param \Illuminate\Http\Request $request * @param \Pterodactyl\Models\Server $server * @return \Illuminate\Http\RedirectResponse * @@ -503,21 +282,26 @@ class ServersController extends Controller /** * Update the build configuration for a server. * - * @param \Illuminate\Http\Request $request + * @param \Illuminate\Http\Request $request * @param \Pterodactyl\Models\Server $server * @return \Illuminate\Http\RedirectResponse * * @throws \Pterodactyl\Exceptions\DisplayException - * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Illuminate\Validation\ValidationException */ public function updateBuild(Request $request, Server $server) { - $this->buildModificationService->handle($server, $request->only([ - 'allocation_id', 'add_allocations', 'remove_allocations', - 'memory', 'swap', 'io', 'cpu', 'disk', - 'database_limit', 'allocation_limit', 'oom_disabled', - ])); + 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->validator); + } + $this->alert->success(trans('admin/server.alerts.build_updated'))->flash(); return redirect()->route('admin.servers.view.build', $server->id); @@ -526,12 +310,12 @@ class ServersController extends Controller /** * Start the server deletion process. * - * @param \Illuminate\Http\Request $request + * @param \Illuminate\Http\Request $request * @param \Pterodactyl\Models\Server $server * @return \Illuminate\Http\RedirectResponse * * @throws \Pterodactyl\Exceptions\DisplayException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Throwable */ public function delete(Request $request, Server $server) { @@ -544,19 +328,23 @@ class ServersController extends Controller /** * Update the startup command as well as variables. * - * @param \Illuminate\Http\Request $request + * @param \Illuminate\Http\Request $request * @param \Pterodactyl\Models\Server $server * @return \Illuminate\Http\RedirectResponse * * @throws \Illuminate\Validation\ValidationException - * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException - * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ public function saveStartup(Request $request, Server $server) { - $this->startupModificationService->setUserLevel(User::USER_LEVEL_ADMIN); - $this->startupModificationService->handle($server, $request->except('_token')); + try { + $this->startupModificationService + ->setUserLevel(User::USER_LEVEL_ADMIN) + ->handle($server, $request->except('_token')); + } catch (DataValidationException $exception) { + throw new ValidationException($exception->validator); + } + $this->alert->success(trans('admin/server.alerts.startup_changed'))->flash(); return redirect()->route('admin.servers.view.startup', $server->id); @@ -566,27 +354,28 @@ class ServersController extends Controller * Creates a new database assigned to a specific server. * * @param \Pterodactyl\Http\Requests\Admin\Servers\Databases\StoreServerDatabaseRequest $request - * @param int $server + * @param \Pterodactyl\Models\Server $server * @return \Illuminate\Http\RedirectResponse * - * @throws \Exception + * @throws \Throwable */ - public function newDatabase(StoreServerDatabaseRequest $request, $server) + public function newDatabase(StoreServerDatabaseRequest $request, Server $server) { $this->databaseManagementService->create($server, [ - 'database' => $request->input('database'), + '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)->withInput(); + return redirect()->route('admin.servers.view.database', $server->id)->withInput(); } /** * Resets the database password for a specific database on this server. * * @param \Illuminate\Http\Request $request - * @param int $server + * @param int $server * @return \Illuminate\Http\RedirectResponse * * @throws \Throwable @@ -620,8 +409,72 @@ class ServersController extends Controller ['id', '=', $database], ]); - $this->databaseManagementService->delete($database->id); + $this->databaseManagementService->delete($database); return response('', 204); } + + /** + * Add a mount to a server. + * + * @param Server $server + * @param \Pterodactyl\Models\Mount $mount + * @return \Illuminate\Http\RedirectResponse + * + * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function addMount(Server $server, Mount $mount) + { + $server->mounts()->updateOrCreate([ + 'mount_id' => $mount->id, + 'server_id' => $server->id, + ]); + + $data = $this->serverConfigurationStructureService->handle($server); + + try { + $this->daemonServerRepository + ->setServer($server) + ->update(Arr::only($data, ['mounts'])); + } catch (RequestException $exception) { + throw new DaemonConnectionException($exception); + } + + $this->alert->success('Mount was added successfully.')->flash(); + + return redirect()->route('admin.servers.view.mounts', $server->id); + } + + /** + * Remove a mount from a server. + * + * @param Server $server + * @param \Pterodactyl\Models\Mount $mount + * @return \Illuminate\Http\RedirectResponse + * + * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function deleteMount(Server $server, Mount $mount) + { + $server->mounts() + ->where('mount_id', $mount->id) + ->where('server_id', $server->id) + ->delete(); + + $data = $this->serverConfigurationStructureService->handle($server); + + try { + $this->daemonServerRepository + ->setServer($server) + ->update(Arr::only($data, ['mounts'])); + } catch (RequestException $exception) { + throw new DaemonConnectionException($exception); + } + + $this->alert->success('Mount was removed successfully.')->flash(); + + return redirect()->route('admin.servers.view.mounts', $server->id); + } } diff --git a/app/Http/Controllers/Admin/Settings/AdvancedController.php b/app/Http/Controllers/Admin/Settings/AdvancedController.php index f32517e7..54409d19 100644 --- a/app/Http/Controllers/Admin/Settings/AdvancedController.php +++ b/app/Http/Controllers/Admin/Settings/AdvancedController.php @@ -36,9 +36,9 @@ class AdvancedController extends Controller /** * AdvancedController constructor. * - * @param \Prologue\Alerts\AlertsMessageBag $alert - * @param \Illuminate\Contracts\Config\Repository $config - * @param \Illuminate\Contracts\Console\Kernel $kernel + * @param \Prologue\Alerts\AlertsMessageBag $alert + * @param \Illuminate\Contracts\Config\Repository $config + * @param \Illuminate\Contracts\Console\Kernel $kernel * @param \Pterodactyl\Contracts\Repository\SettingsRepositoryInterface $settings */ public function __construct( diff --git a/app/Http/Controllers/Admin/Settings/IndexController.php b/app/Http/Controllers/Admin/Settings/IndexController.php index bb9aef3e..1bf61d65 100644 --- a/app/Http/Controllers/Admin/Settings/IndexController.php +++ b/app/Http/Controllers/Admin/Settings/IndexController.php @@ -39,10 +39,10 @@ class IndexController extends Controller /** * IndexController constructor. * - * @param \Prologue\Alerts\AlertsMessageBag $alert - * @param \Illuminate\Contracts\Console\Kernel $kernel + * @param \Prologue\Alerts\AlertsMessageBag $alert + * @param \Illuminate\Contracts\Console\Kernel $kernel * @param \Pterodactyl\Contracts\Repository\SettingsRepositoryInterface $settings - * @param \Pterodactyl\Services\Helpers\SoftwareVersionService $versionService + * @param \Pterodactyl\Services\Helpers\SoftwareVersionService $versionService */ public function __construct( AlertsMessageBag $alert, diff --git a/app/Http/Controllers/Admin/Settings/MailController.php b/app/Http/Controllers/Admin/Settings/MailController.php index 7364a4ec..ccd84fa5 100644 --- a/app/Http/Controllers/Admin/Settings/MailController.php +++ b/app/Http/Controllers/Admin/Settings/MailController.php @@ -48,10 +48,10 @@ class MailController extends Controller /** * MailController constructor. * - * @param \Prologue\Alerts\AlertsMessageBag $alert - * @param \Illuminate\Contracts\Config\Repository $config - * @param \Illuminate\Contracts\Encryption\Encrypter $encrypter - * @param \Illuminate\Contracts\Console\Kernel $kernel + * @param \Prologue\Alerts\AlertsMessageBag $alert + * @param \Illuminate\Contracts\Config\Repository $config + * @param \Illuminate\Contracts\Encryption\Encrypter $encrypter + * @param \Illuminate\Contracts\Console\Kernel $kernel * @param \Pterodactyl\Contracts\Repository\SettingsRepositoryInterface $settings */ public function __construct( diff --git a/app/Http/Controllers/Admin/StatisticsController.php b/app/Http/Controllers/Admin/StatisticsController.php index 1ae80756..612f04b6 100644 --- a/app/Http/Controllers/Admin/StatisticsController.php +++ b/app/Http/Controllers/Admin/StatisticsController.php @@ -9,6 +9,7 @@ use Pterodactyl\Contracts\Repository\NodeRepositoryInterface; use Pterodactyl\Contracts\Repository\UserRepositoryInterface; use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; use Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Pterodactyl\Contracts\Repository\AllocationRepositoryInterface; class StatisticsController extends Controller @@ -45,6 +46,7 @@ class StatisticsController extends Controller public function index() { + throw new NotFoundHttpException; $servers = $this->serverRepository->all(); $nodes = $this->nodeRepository->all(); $usersCount = $this->userRepository->count(); @@ -67,7 +69,7 @@ class StatisticsController extends Controller $tokens = []; foreach ($nodes as $node) { - $tokens[$node->id] = $node->daemonSecret; + $tokens[$node->id] = decrypt($node->daemon_token); } $this->injectJavascript([ diff --git a/app/Http/Controllers/Admin/UserController.php b/app/Http/Controllers/Admin/UserController.php index e0474fa5..b5126c76 100644 --- a/app/Http/Controllers/Admin/UserController.php +++ b/app/Http/Controllers/Admin/UserController.php @@ -5,6 +5,7 @@ namespace Pterodactyl\Http\Controllers\Admin; use Illuminate\Http\Request; use Pterodactyl\Models\User; use Prologue\Alerts\AlertsMessageBag; +use Spatie\QueryBuilder\QueryBuilder; use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Http\Controllers\Controller; use Illuminate\Contracts\Translation\Translator; @@ -52,11 +53,11 @@ class UserController extends Controller /** * UserController constructor. * - * @param \Prologue\Alerts\AlertsMessageBag $alert - * @param \Pterodactyl\Services\Users\UserCreationService $creationService - * @param \Pterodactyl\Services\Users\UserDeletionService $deletionService - * @param \Illuminate\Contracts\Translation\Translator $translator - * @param \Pterodactyl\Services\Users\UserUpdateService $updateService + * @param \Prologue\Alerts\AlertsMessageBag $alert + * @param \Pterodactyl\Services\Users\UserCreationService $creationService + * @param \Pterodactyl\Services\Users\UserDeletionService $deletionService + * @param \Illuminate\Contracts\Translation\Translator $translator + * @param \Pterodactyl\Services\Users\UserUpdateService $updateService * @param \Pterodactyl\Contracts\Repository\UserRepositoryInterface $repository */ public function __construct( @@ -83,7 +84,17 @@ class UserController extends Controller */ public function index(Request $request) { - $users = $this->repository->setSearchTerm($request->input('query'))->getAllUsersWithCounts(); + $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 view('admin.users.index', ['users' => $users]); } @@ -147,7 +158,7 @@ class UserController extends Controller public function store(UserFormRequest $request) { $user = $this->creationService->handle($request->normalize()); - $this->alert->success($this->translator->trans('admin/user.notices.account_created'))->flash(); + $this->alert->success($this->translator->get('admin/user.notices.account_created'))->flash(); return redirect()->route('admin.users.view', $user->id); } @@ -156,7 +167,7 @@ class UserController extends Controller * Update a user on the system. * * @param \Pterodactyl\Http\Requests\Admin\UserFormRequest $request - * @param \Pterodactyl\Models\User $user + * @param \Pterodactyl\Models\User $user * @return \Illuminate\Http\RedirectResponse * * @throws \Pterodactyl\Exceptions\Model\DataValidationException @@ -164,27 +175,11 @@ class UserController extends Controller */ public function update(UserFormRequest $request, User $user) { - $this->updateService->setUserLevel(User::USER_LEVEL_ADMIN); - $data = $this->updateService->handle($user, $request->normalize()); + $this->updateService + ->setUserLevel(User::USER_LEVEL_ADMIN) + ->handle($user, $request->normalize()); - if (! empty($data->get('exceptions'))) { - foreach ($data->get('exceptions') as $node => $exception) { - /** @var \GuzzleHttp\Exception\RequestException $exception */ - /** @var \GuzzleHttp\Psr7\Response|null $response */ - $response = method_exists($exception, 'getResponse') ? $exception->getResponse() : null; - $message = trans('admin/server.exceptions.daemon_exception', [ - 'code' => is_null($response) ? 'E_CONN_REFUSED' : $response->getStatusCode(), - ]); - - $this->alert->danger(trans('exceptions.users.node_revocation_failed', [ - 'node' => $node, - 'error' => $message, - 'link' => route('admin.nodes.view', $node), - ]))->flash(); - } - } - - $this->alert->success($this->translator->trans('admin/user.notices.account_updated'))->flash(); + $this->alert->success(trans('admin/user.notices.account_updated'))->flash(); return redirect()->route('admin.users.view', $user->id); } @@ -193,10 +188,24 @@ class UserController extends Controller * Get a JSON response of users on the system. * * @param \Illuminate\Http\Request $request - * @return \Illuminate\Database\Eloquent\Collection + * @return \Illuminate\Support\Collection|\Pterodactyl\Models\Model */ public function json(Request $request) { - return $this->repository->filterUsersByQuery($request->input('q')); + $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; + }); } } diff --git a/app/Http/Controllers/Api/Application/ApplicationApiController.php b/app/Http/Controllers/Api/Application/ApplicationApiController.php index bdd5f9e7..903edeb6 100644 --- a/app/Http/Controllers/Api/Application/ApplicationApiController.php +++ b/app/Http/Controllers/Api/Application/ApplicationApiController.php @@ -5,6 +5,7 @@ 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; @@ -30,7 +31,10 @@ abstract class ApplicationApiController extends Controller Container::getInstance()->call([$this, 'loadDependencies']); // Parse all of the includes to use on this request. - $includes = collect(explode(',', $this->request->input('include', '')))->map(function ($value) { + $input = $this->request->input('include', []); + $input = is_array($input) ? $input : explode(',', $input); + + $includes = (new Collection($input))->map(function ($value) { return trim($value); })->filter()->toArray(); @@ -43,7 +47,7 @@ abstract class ApplicationApiController extends Controller * without littering the constructors of classes that extend this abstract. * * @param \Pterodactyl\Extensions\Spatie\Fractalistic\Fractal $fractal - * @param \Illuminate\Http\Request $request + * @param \Illuminate\Http\Request $request */ public function loadDependencies(Fractal $fractal, Request $request) { diff --git a/app/Http/Controllers/Api/Application/Locations/LocationController.php b/app/Http/Controllers/Api/Application/Locations/LocationController.php index 9220cf35..62ab4ea4 100644 --- a/app/Http/Controllers/Api/Application/Locations/LocationController.php +++ b/app/Http/Controllers/Api/Application/Locations/LocationController.php @@ -5,6 +5,7 @@ namespace Pterodactyl\Http\Controllers\Api\Application\Locations; use Illuminate\Http\Response; use Pterodactyl\Models\Location; use Illuminate\Http\JsonResponse; +use Spatie\QueryBuilder\QueryBuilder; use Pterodactyl\Services\Locations\LocationUpdateService; use Pterodactyl\Services\Locations\LocationCreationService; use Pterodactyl\Services\Locations\LocationDeletionService; @@ -42,10 +43,10 @@ class LocationController extends ApplicationApiController /** * LocationController constructor. * - * @param \Pterodactyl\Services\Locations\LocationCreationService $creationService - * @param \Pterodactyl\Services\Locations\LocationDeletionService $deletionService + * @param \Pterodactyl\Services\Locations\LocationCreationService $creationService + * @param \Pterodactyl\Services\Locations\LocationDeletionService $deletionService * @param \Pterodactyl\Contracts\Repository\LocationRepositoryInterface $repository - * @param \Pterodactyl\Services\Locations\LocationUpdateService $updateService + * @param \Pterodactyl\Services\Locations\LocationUpdateService $updateService */ public function __construct( LocationCreationService $creationService, @@ -69,7 +70,10 @@ class LocationController extends ApplicationApiController */ public function index(GetLocationsRequest $request): array { - $locations = $this->repository->setSearchTerm($request->input('search'))->paginated(50); + $locations = QueryBuilder::for(Location::query()) + ->allowedFilters(['short', 'long']) + ->allowedSorts(['id']) + ->paginate(100); return $this->fractal->collection($locations) ->transformWith($this->getTransformer(LocationTransformer::class)) diff --git a/app/Http/Controllers/Api/Application/Nodes/AllocationController.php b/app/Http/Controllers/Api/Application/Nodes/AllocationController.php index 094834fe..01ec37fe 100644 --- a/app/Http/Controllers/Api/Application/Nodes/AllocationController.php +++ b/app/Http/Controllers/Api/Application/Nodes/AllocationController.php @@ -3,11 +3,10 @@ namespace Pterodactyl\Http\Controllers\Api\Application\Nodes; use Pterodactyl\Models\Node; -use Illuminate\Http\Response; +use Illuminate\Http\JsonResponse; use Pterodactyl\Models\Allocation; use Pterodactyl\Services\Allocations\AssignmentService; use Pterodactyl\Services\Allocations\AllocationDeletionService; -use Pterodactyl\Contracts\Repository\AllocationRepositoryInterface; use Pterodactyl\Transformers\Api\Application\AllocationTransformer; use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController; use Pterodactyl\Http\Requests\Api\Application\Allocations\GetAllocationsRequest; @@ -26,41 +25,32 @@ class AllocationController extends ApplicationApiController */ private $deletionService; - /** - * @var \Pterodactyl\Contracts\Repository\AllocationRepositoryInterface - */ - private $repository; - /** * AllocationController constructor. * - * @param \Pterodactyl\Services\Allocations\AssignmentService $assignmentService - * @param \Pterodactyl\Services\Allocations\AllocationDeletionService $deletionService - * @param \Pterodactyl\Contracts\Repository\AllocationRepositoryInterface $repository + * @param \Pterodactyl\Services\Allocations\AssignmentService $assignmentService + * @param \Pterodactyl\Services\Allocations\AllocationDeletionService $deletionService */ public function __construct( AssignmentService $assignmentService, - AllocationDeletionService $deletionService, - AllocationRepositoryInterface $repository + AllocationDeletionService $deletionService ) { parent::__construct(); $this->assignmentService = $assignmentService; $this->deletionService = $deletionService; - $this->repository = $repository; } /** * Return all of the allocations that exist for a given node. * * @param \Pterodactyl\Http\Requests\Api\Application\Allocations\GetAllocationsRequest $request + * @param \Pterodactyl\Models\Node $node * @return array */ - public function index(GetAllocationsRequest $request): array + public function index(GetAllocationsRequest $request, Node $node): array { - $allocations = $this->repository->getPaginatedAllocationsForNode( - $request->getModel(Node::class)->id, 50 - ); + $allocations = $node->allocations()->paginate(50); return $this->fractal->collection($allocations) ->transformWith($this->getTransformer(AllocationTransformer::class)) @@ -71,32 +61,35 @@ class AllocationController extends ApplicationApiController * Store new allocations for a given node. * * @param \Pterodactyl\Http\Requests\Api\Application\Allocations\StoreAllocationRequest $request - * @return \Illuminate\Http\Response + * @param \Pterodactyl\Models\Node $node + * @return \Illuminate\Http\JsonResponse * * @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 store(StoreAllocationRequest $request): Response + public function store(StoreAllocationRequest $request, Node $node): JsonResponse { - $this->assignmentService->handle($request->getModel(Node::class), $request->validated()); + $this->assignmentService->handle($node, $request->validated()); - return response('', 204); + return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT); } /** * Delete a specific allocation from the Panel. * * @param \Pterodactyl\Http\Requests\Api\Application\Allocations\DeleteAllocationRequest $request - * @return \Illuminate\Http\Response + * @param \Pterodactyl\Models\Node $node + * @param \Pterodactyl\Models\Allocation $allocation + * @return \Illuminate\Http\JsonResponse * * @throws \Pterodactyl\Exceptions\Service\Allocation\ServerUsingAllocationException */ - public function delete(DeleteAllocationRequest $request): Response + public function delete(DeleteAllocationRequest $request, Node $node, Allocation $allocation): JsonResponse { - $this->deletionService->handle($request->getModel(Allocation::class)); + $this->deletionService->handle($allocation); - return response('', 204); + return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT); } } diff --git a/app/Http/Controllers/Api/Application/Nodes/NodeConfigurationController.php b/app/Http/Controllers/Api/Application/Nodes/NodeConfigurationController.php new file mode 100644 index 00000000..fc0c35f7 --- /dev/null +++ b/app/Http/Controllers/Api/Application/Nodes/NodeConfigurationController.php @@ -0,0 +1,25 @@ +getConfiguration()); + } +} diff --git a/app/Http/Controllers/Api/Application/Nodes/NodeController.php b/app/Http/Controllers/Api/Application/Nodes/NodeController.php index 2ae2b960..7198611b 100644 --- a/app/Http/Controllers/Api/Application/Nodes/NodeController.php +++ b/app/Http/Controllers/Api/Application/Nodes/NodeController.php @@ -3,8 +3,8 @@ 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; @@ -42,9 +42,9 @@ class NodeController extends ApplicationApiController /** * NodeController constructor. * - * @param \Pterodactyl\Services\Nodes\NodeCreationService $creationService - * @param \Pterodactyl\Services\Nodes\NodeDeletionService $deletionService - * @param \Pterodactyl\Services\Nodes\NodeUpdateService $updateService + * @param \Pterodactyl\Services\Nodes\NodeCreationService $creationService + * @param \Pterodactyl\Services\Nodes\NodeDeletionService $deletionService + * @param \Pterodactyl\Services\Nodes\NodeUpdateService $updateService * @param \Pterodactyl\Contracts\Repository\NodeRepositoryInterface $repository */ public function __construct( @@ -69,7 +69,10 @@ class NodeController extends ApplicationApiController */ public function index(GetNodesRequest $request): array { - $nodes = $this->repository->setSearchTerm($request->input('search'))->paginated(50); + $nodes = QueryBuilder::for(Node::query()) + ->allowedFilters(['uuid', 'name', 'fqdn', 'daemon_token_id']) + ->allowedSorts(['id', 'uuid', 'memory', 'disk']) + ->paginate(100); return $this->fractal->collection($nodes) ->transformWith($this->getTransformer(NodeTransformer::class)) @@ -80,11 +83,12 @@ class NodeController extends ApplicationApiController * Return data for a single instance of a node. * * @param \Pterodactyl\Http\Requests\Api\Application\Nodes\GetNodeRequest $request + * @param \Pterodactyl\Models\Node $node * @return array */ - public function view(GetNodeRequest $request): array + public function view(GetNodeRequest $request, Node $node): array { - return $this->fractal->item($request->getModel(Node::class)) + return $this->fractal->item($node) ->transformWith($this->getTransformer(NodeTransformer::class)) ->toArray(); } @@ -116,16 +120,15 @@ class NodeController extends ApplicationApiController * Update an existing node on the Panel. * * @param \Pterodactyl\Http\Requests\Api\Application\Nodes\UpdateNodeRequest $request + * @param \Pterodactyl\Models\Node $node * @return array * - * @throws \Pterodactyl\Exceptions\DisplayException - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Throwable */ - public function update(UpdateNodeRequest $request): array + public function update(UpdateNodeRequest $request, Node $node): array { $node = $this->updateService->handle( - $request->getModel(Node::class), $request->validated(), $request->input('reset_secret') === true + $node, $request->validated(), $request->input('reset_secret') === true ); return $this->fractal->item($node) @@ -138,14 +141,15 @@ class NodeController extends ApplicationApiController * currently attached to it. * * @param \Pterodactyl\Http\Requests\Api\Application\Nodes\DeleteNodeRequest $request - * @return \Illuminate\Http\Response + * @param \Pterodactyl\Models\Node $node + * @return \Illuminate\Http\JsonResponse * * @throws \Pterodactyl\Exceptions\Service\HasActiveServersException */ - public function delete(DeleteNodeRequest $request): Response + public function delete(DeleteNodeRequest $request, Node $node): JsonResponse { - $this->deletionService->handle($request->getModel(Node::class)); + $this->deletionService->handle($node); - return response('', 204); + return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT); } } diff --git a/app/Http/Controllers/Api/Application/Servers/DatabaseController.php b/app/Http/Controllers/Api/Application/Servers/DatabaseController.php index a2030bbc..829a6ca5 100644 --- a/app/Http/Controllers/Api/Application/Servers/DatabaseController.php +++ b/app/Http/Controllers/Api/Application/Servers/DatabaseController.php @@ -36,8 +36,8 @@ class DatabaseController extends ApplicationApiController /** * DatabaseController constructor. * - * @param \Pterodactyl\Services\Databases\DatabaseManagementService $databaseManagementService - * @param \Pterodactyl\Services\Databases\DatabasePasswordService $databasePasswordService + * @param \Pterodactyl\Services\Databases\DatabaseManagementService $databaseManagementService + * @param \Pterodactyl\Services\Databases\DatabasePasswordService $databasePasswordService * @param \Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface $repository */ public function __construct( @@ -57,13 +57,12 @@ class DatabaseController extends ApplicationApiController * server. * * @param \Pterodactyl\Http\Requests\Api\Application\Servers\Databases\GetServerDatabasesRequest $request + * @param \Pterodactyl\Models\Server $server * @return array */ - public function index(GetServerDatabasesRequest $request): array + public function index(GetServerDatabasesRequest $request, Server $server): array { - $databases = $this->repository->getDatabasesForServer($request->getModel(Server::class)->id); - - return $this->fractal->collection($databases) + return $this->fractal->collection($server->databases) ->transformWith($this->getTransformer(ServerDatabaseTransformer::class)) ->toArray(); } @@ -72,11 +71,13 @@ class DatabaseController extends ApplicationApiController * Return a single server database. * * @param \Pterodactyl\Http\Requests\Api\Application\Servers\Databases\GetServerDatabaseRequest $request + * @param \Pterodactyl\Models\Server $server + * @param \Pterodactyl\Models\Database $database * @return array */ - public function view(GetServerDatabaseRequest $request): array + public function view(GetServerDatabaseRequest $request, Server $server, Database $database): array { - return $this->fractal->item($request->getModel(Database::class)) + return $this->fractal->item($database) ->transformWith($this->getTransformer(ServerDatabaseTransformer::class)) ->toArray(); } @@ -85,29 +86,33 @@ class DatabaseController extends ApplicationApiController * Reset the password for a specific server database. * * @param \Pterodactyl\Http\Requests\Api\Application\Servers\Databases\ServerDatabaseWriteRequest $request - * @return \Illuminate\Http\Response + * @param \Pterodactyl\Models\Server $server + * @param \Pterodactyl\Models\Database $database + * @return \Illuminate\Http\JsonResponse * * @throws \Throwable */ - public function resetPassword(ServerDatabaseWriteRequest $request): Response + public function resetPassword(ServerDatabaseWriteRequest $request, Server $server, Database $database): JsonResponse { - $this->databasePasswordService->handle($request->getModel(Database::class)); + $this->databasePasswordService->handle($database); - return response('', 204); + return JsonResponse::create([], JsonResponse::HTTP_NO_CONTENT); } /** * Create a new database on the Panel for a given server. * * @param \Pterodactyl\Http\Requests\Api\Application\Servers\Databases\StoreServerDatabaseRequest $request + * @param \Pterodactyl\Models\Server $server * @return \Illuminate\Http\JsonResponse * - * @throws \Exception + * @throws \Throwable */ - public function store(StoreServerDatabaseRequest $request): JsonResponse + public function store(StoreServerDatabaseRequest $request, Server $server): JsonResponse { - $server = $request->getModel(Server::class); - $database = $this->databaseManagementService->create($server->id, $request->validated()); + $database = $this->databaseManagementService->create($server, array_merge($request->validated(), [ + 'database' => $request->databaseName(), + ])); return $this->fractal->item($database) ->transformWith($this->getTransformer(ServerDatabaseTransformer::class)) @@ -117,7 +122,7 @@ class DatabaseController extends ApplicationApiController 'database' => $database->id, ]), ]) - ->respond(201); + ->respond(Response::HTTP_CREATED); } /** @@ -130,7 +135,7 @@ class DatabaseController extends ApplicationApiController */ public function delete(ServerDatabaseWriteRequest $request): Response { - $this->databaseManagementService->delete($request->getModel(Database::class)->id); + $this->databaseManagementService->delete($request->getModel(Database::class)); return response('', 204); } diff --git a/app/Http/Controllers/Api/Application/Servers/ServerController.php b/app/Http/Controllers/Api/Application/Servers/ServerController.php index 1ef1e0b6..126c9192 100644 --- a/app/Http/Controllers/Api/Application/Servers/ServerController.php +++ b/app/Http/Controllers/Api/Application/Servers/ServerController.php @@ -5,6 +5,7 @@ namespace Pterodactyl\Http\Controllers\Api\Application\Servers; use Illuminate\Http\Response; use Pterodactyl\Models\Server; use Illuminate\Http\JsonResponse; +use Spatie\QueryBuilder\QueryBuilder; use Pterodactyl\Services\Servers\ServerCreationService; use Pterodactyl\Services\Servers\ServerDeletionService; use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; @@ -35,8 +36,8 @@ class ServerController extends ApplicationApiController /** * ServerController constructor. * - * @param \Pterodactyl\Services\Servers\ServerCreationService $creationService - * @param \Pterodactyl\Services\Servers\ServerDeletionService $deletionService + * @param \Pterodactyl\Services\Servers\ServerCreationService $creationService + * @param \Pterodactyl\Services\Servers\ServerDeletionService $deletionService * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository */ public function __construct( @@ -59,7 +60,10 @@ class ServerController extends ApplicationApiController */ public function index(GetServersRequest $request): array { - $servers = $this->repository->setSearchTerm($request->input('search'))->paginated(50); + $servers = QueryBuilder::for(Server::query()) + ->allowedFilters(['uuid', 'name', 'image', 'external_id']) + ->allowedSorts(['id', 'uuid']) + ->paginate(100); return $this->fractal->collection($servers) ->transformWith($this->getTransformer(ServerTransformer::class)) @@ -72,9 +76,9 @@ class ServerController extends ApplicationApiController * @param \Pterodactyl\Http\Requests\Api\Application\Servers\StoreServerRequest $request * @return \Illuminate\Http\JsonResponse * + * @throws \Throwable * @throws \Illuminate\Validation\ValidationException * @throws \Pterodactyl\Exceptions\DisplayException - * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException * @throws \Pterodactyl\Exceptions\Service\Deployment\NoViableAllocationException @@ -104,8 +108,8 @@ class ServerController extends ApplicationApiController /** * @param \Pterodactyl\Http\Requests\Api\Application\Servers\ServerWriteRequest $request - * @param \Pterodactyl\Models\Server $server - * @param string $force + * @param \Pterodactyl\Models\Server $server + * @param string $force * @return \Illuminate\Http\Response * * @throws \Pterodactyl\Exceptions\DisplayException diff --git a/app/Http/Controllers/Api/Application/Servers/ServerDetailsController.php b/app/Http/Controllers/Api/Application/Servers/ServerDetailsController.php index e544c138..c2c3fa1c 100644 --- a/app/Http/Controllers/Api/Application/Servers/ServerDetailsController.php +++ b/app/Http/Controllers/Api/Application/Servers/ServerDetailsController.php @@ -25,7 +25,7 @@ class ServerDetailsController extends ApplicationApiController /** * ServerDetailsController constructor. * - * @param \Pterodactyl\Services\Servers\BuildModificationService $buildModificationService + * @param \Pterodactyl\Services\Servers\BuildModificationService $buildModificationService * @param \Pterodactyl\Services\Servers\DetailsModificationService $detailsModificationService */ public function __construct( @@ -63,15 +63,16 @@ class ServerDetailsController extends ApplicationApiController * Update the build details for a specific server. * * @param \Pterodactyl\Http\Requests\Api\Application\Servers\UpdateServerBuildConfigurationRequest $request + * @param \Pterodactyl\Models\Server $server * @return array * * @throws \Pterodactyl\Exceptions\DisplayException * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function build(UpdateServerBuildConfigurationRequest $request): array + public function build(UpdateServerBuildConfigurationRequest $request, Server $server): array { - $server = $this->buildModificationService->handle($request->getModel(Server::class), $request->validated()); + $server = $this->buildModificationService->handle($server, $request->validated()); return $this->fractal->item($server) ->transformWith($this->getTransformer(ServerTransformer::class)) diff --git a/app/Http/Controllers/Api/Application/Servers/ServerManagementController.php b/app/Http/Controllers/Api/Application/Servers/ServerManagementController.php index 9ab324d7..5052c884 100644 --- a/app/Http/Controllers/Api/Application/Servers/ServerManagementController.php +++ b/app/Http/Controllers/Api/Application/Servers/ServerManagementController.php @@ -6,17 +6,11 @@ use Illuminate\Http\Response; use Pterodactyl\Models\Server; use Pterodactyl\Services\Servers\SuspensionService; use Pterodactyl\Services\Servers\ReinstallServerService; -use Pterodactyl\Services\Servers\ContainerRebuildService; use Pterodactyl\Http\Requests\Api\Application\Servers\ServerWriteRequest; use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController; class ServerManagementController extends ApplicationApiController { - /** - * @var \Pterodactyl\Services\Servers\ContainerRebuildService - */ - private $rebuildService; - /** * @var \Pterodactyl\Services\Servers\ReinstallServerService */ @@ -30,18 +24,15 @@ class ServerManagementController extends ApplicationApiController /** * SuspensionController constructor. * - * @param \Pterodactyl\Services\Servers\ContainerRebuildService $rebuildService - * @param \Pterodactyl\Services\Servers\ReinstallServerService $reinstallServerService - * @param \Pterodactyl\Services\Servers\SuspensionService $suspensionService + * @param \Pterodactyl\Services\Servers\ReinstallServerService $reinstallServerService + * @param \Pterodactyl\Services\Servers\SuspensionService $suspensionService */ public function __construct( - ContainerRebuildService $rebuildService, ReinstallServerService $reinstallServerService, SuspensionService $suspensionService ) { parent::__construct(); - $this->rebuildService = $rebuildService; $this->reinstallServerService = $reinstallServerService; $this->suspensionService = $suspensionService; } @@ -50,15 +41,14 @@ class ServerManagementController extends ApplicationApiController * Suspend a server on the Panel. * * @param \Pterodactyl\Http\Requests\Api\Application\Servers\ServerWriteRequest $request + * @param \Pterodactyl\Models\Server $server * @return \Illuminate\Http\Response * - * @throws \Pterodactyl\Exceptions\DisplayException - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Throwable */ - public function suspend(ServerWriteRequest $request): Response + public function suspend(ServerWriteRequest $request, Server $server): Response { - $this->suspensionService->toggle($request->getModel(Server::class), SuspensionService::ACTION_SUSPEND); + $this->suspensionService->toggle($server, SuspensionService::ACTION_SUSPEND); return $this->returnNoContent(); } @@ -67,15 +57,14 @@ class ServerManagementController extends ApplicationApiController * Unsuspend a server on the Panel. * * @param \Pterodactyl\Http\Requests\Api\Application\Servers\ServerWriteRequest $request + * @param \Pterodactyl\Models\Server $server * @return \Illuminate\Http\Response * - * @throws \Pterodactyl\Exceptions\DisplayException - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Throwable */ - public function unsuspend(ServerWriteRequest $request): Response + public function unsuspend(ServerWriteRequest $request, Server $server): Response { - $this->suspensionService->toggle($request->getModel(Server::class), SuspensionService::ACTION_UNSUSPEND); + $this->suspensionService->toggle($server, SuspensionService::ACTION_UNSUSPEND); return $this->returnNoContent(); } @@ -84,30 +73,16 @@ class ServerManagementController extends ApplicationApiController * Mark a server as needing to be reinstalled. * * @param \Pterodactyl\Http\Requests\Api\Application\Servers\ServerWriteRequest $request + * @param \Pterodactyl\Models\Server $server * @return \Illuminate\Http\Response * * @throws \Pterodactyl\Exceptions\DisplayException * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function reinstall(ServerWriteRequest $request): Response + public function reinstall(ServerWriteRequest $request, Server $server): Response { - $this->reinstallServerService->reinstall($request->getModel(Server::class)); - - return $this->returnNoContent(); - } - - /** - * Mark a server as needing its container rebuilt the next time it is started. - * - * @param \Pterodactyl\Http\Requests\Api\Application\Servers\ServerWriteRequest $request - * @return \Illuminate\Http\Response - * - * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException - */ - public function rebuild(ServerWriteRequest $request): Response - { - $this->rebuildService->handle($request->getModel(Server::class)); + $this->reinstallServerService->handle($server); return $this->returnNoContent(); } diff --git a/app/Http/Controllers/Api/Application/Users/UserController.php b/app/Http/Controllers/Api/Application/Users/UserController.php index d845c944..c8b309d8 100644 --- a/app/Http/Controllers/Api/Application/Users/UserController.php +++ b/app/Http/Controllers/Api/Application/Users/UserController.php @@ -5,6 +5,7 @@ namespace Pterodactyl\Http\Controllers\Api\Application\Users; use Pterodactyl\Models\User; use Illuminate\Http\Response; use Illuminate\Http\JsonResponse; +use Spatie\QueryBuilder\QueryBuilder; use Pterodactyl\Services\Users\UserUpdateService; use Pterodactyl\Services\Users\UserCreationService; use Pterodactyl\Services\Users\UserDeletionService; @@ -42,9 +43,9 @@ class UserController extends ApplicationApiController * UserController constructor. * * @param \Pterodactyl\Contracts\Repository\UserRepositoryInterface $repository - * @param \Pterodactyl\Services\Users\UserCreationService $creationService - * @param \Pterodactyl\Services\Users\UserDeletionService $deletionService - * @param \Pterodactyl\Services\Users\UserUpdateService $updateService + * @param \Pterodactyl\Services\Users\UserCreationService $creationService + * @param \Pterodactyl\Services\Users\UserDeletionService $deletionService + * @param \Pterodactyl\Services\Users\UserUpdateService $updateService */ public function __construct( UserRepositoryInterface $repository, @@ -70,7 +71,10 @@ class UserController extends ApplicationApiController */ public function index(GetUsersRequest $request): array { - $users = $this->repository->setSearchTerm($request->input('search'))->paginated(50); + $users = QueryBuilder::for(User::query()) + ->allowedFilters(['email', 'uuid', 'username', 'external_id']) + ->allowedSorts(['id', 'uuid']) + ->paginate(100); return $this->fractal->collection($users) ->transformWith($this->getTransformer(UserTransformer::class)) @@ -82,11 +86,12 @@ class UserController extends ApplicationApiController * were defined in the request. * * @param \Pterodactyl\Http\Requests\Api\Application\Users\GetUsersRequest $request + * @param \Pterodactyl\Models\User $user * @return array */ - public function view(GetUsersRequest $request): array + public function view(GetUsersRequest $request, User $user): array { - return $this->fractal->item($request->getModel(User::class)) + return $this->fractal->item($user) ->transformWith($this->getTransformer(UserTransformer::class)) ->toArray(); } @@ -100,39 +105,20 @@ class UserController extends ApplicationApiController * meta. If there are no errors this is an empty array. * * @param \Pterodactyl\Http\Requests\Api\Application\Users\UpdateUserRequest $request + * @param \Pterodactyl\Models\User $user * @return array * * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function update(UpdateUserRequest $request): array + public function update(UpdateUserRequest $request, User $user): array { $this->updateService->setUserLevel(User::USER_LEVEL_ADMIN); - $collection = $this->updateService->handle($request->getModel(User::class), $request->validated()); + $user = $this->updateService->handle($user, $request->validated()); - $errors = []; - if (! empty($collection->get('exceptions'))) { - foreach ($collection->get('exceptions') as $node => $exception) { - /** @var \GuzzleHttp\Exception\RequestException $exception */ - /** @var \GuzzleHttp\Psr7\Response|null $response */ - $response = method_exists($exception, 'getResponse') ? $exception->getResponse() : null; - $message = trans('admin/server.exceptions.daemon_exception', [ - 'code' => is_null($response) ? 'E_CONN_REFUSED' : $response->getStatusCode(), - ]); - - $errors[] = ['message' => $message, 'node' => $node]; - } - } - - $response = $this->fractal->item($collection->get('model')) + $response = $this->fractal->item($user) ->transformWith($this->getTransformer(UserTransformer::class)); - if (count($errors) > 0) { - $response->addMeta([ - 'revocation_errors' => $errors, - ]); - } - return $response->toArray(); } @@ -165,14 +151,15 @@ class UserController extends ApplicationApiController * on successful deletion. * * @param \Pterodactyl\Http\Requests\Api\Application\Users\DeleteUserRequest $request - * @return \Illuminate\Http\Response + * @param \Pterodactyl\Models\User $user + * @return \Illuminate\Http\JsonResponse * * @throws \Pterodactyl\Exceptions\DisplayException */ - public function delete(DeleteUserRequest $request): Response + public function delete(DeleteUserRequest $request, User $user): JsonResponse { - $this->deletionService->handle($request->getModel(User::class)); + $this->deletionService->handle($user); - return response('', 204); + return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT); } } diff --git a/app/Http/Controllers/Api/Client/AccountController.php b/app/Http/Controllers/Api/Client/AccountController.php new file mode 100644 index 00000000..25900f05 --- /dev/null +++ b/app/Http/Controllers/Api/Client/AccountController.php @@ -0,0 +1,85 @@ +updateService = $updateService; + $this->sessionGuard = $sessionGuard; + } + + /** + * @param Request $request + * @return array + */ + public function index(Request $request): array + { + return $this->fractal->item($request->user()) + ->transformWith($this->getTransformer(AccountTransformer::class)) + ->toArray(); + } + + /** + * Update the authenticated user's email address. + * + * @param \Pterodactyl\Http\Requests\Api\Client\Account\UpdateEmailRequest $request + * @return \Illuminate\Http\JsonResponse + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function updateEmail(UpdateEmailRequest $request): JsonResponse + { + $this->updateService->handle($request->user(), $request->validated()); + + return new JsonResponse([], Response::HTTP_NO_CONTENT); + } + + /** + * Update the authenticated user's password. All existing sessions will be logged + * out immediately. + * + * @param \Pterodactyl\Http\Requests\Api\Client\Account\UpdatePasswordRequest $request + * @return \Illuminate\Http\JsonResponse + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function updatePassword(UpdatePasswordRequest $request): JsonResponse + { + $this->updateService->handle($request->user(), $request->validated()); + + $this->sessionGuard->logoutOtherDevices($request->input('password')); + + return new JsonResponse([], Response::HTTP_NO_CONTENT); + } +} diff --git a/app/Http/Controllers/Api/Client/ApiKeyController.php b/app/Http/Controllers/Api/Client/ApiKeyController.php new file mode 100644 index 00000000..5662e19c --- /dev/null +++ b/app/Http/Controllers/Api/Client/ApiKeyController.php @@ -0,0 +1,117 @@ +encrypter = $encrypter; + $this->keyCreationService = $keyCreationService; + $this->repository = $repository; + } + + /** + * Returns all of the API keys that exist for the given client. + * + * @param \Pterodactyl\Http\Requests\Api\Client\ClientApiRequest $request + * @return array + */ + public function index(ClientApiRequest $request) + { + return $this->fractal->collection($request->user()->apiKeys) + ->transformWith($this->getTransformer(ApiKeyTransformer::class)) + ->toArray(); + } + + /** + * Store a new API key for a user's account. + * + * @param \Pterodactyl\Http\Requests\Api\Client\Account\StoreApiKeyRequest $request + * @return array + * + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + */ + public function store(StoreApiKeyRequest $request) + { + if ($request->user()->apiKeys->count() >= 5) { + throw new DisplayException( + 'You have reached the account limit for number of API keys.' + ); + } + + $key = $this->keyCreationService->setKeyType(ApiKey::TYPE_ACCOUNT)->handle([ + 'user_id' => $request->user()->id, + 'memo' => $request->input('description'), + 'allowed_ips' => $request->input('allowed_ips') ?? [], + ]); + + return $this->fractal->item($key) + ->transformWith($this->getTransformer(ApiKeyTransformer::class)) + ->addMeta([ + 'secret_token' => $this->encrypter->decrypt($key->token), + ]) + ->toArray(); + } + + /** + * Deletes a given API key. + * + * @param \Pterodactyl\Http\Requests\Api\Client\ClientApiRequest $request + * @param string $identifier + * @return \Illuminate\Http\JsonResponse + */ + public function delete(ClientApiRequest $request, string $identifier) + { + $response = $this->repository->deleteWhere([ + 'key_type' => ApiKey::TYPE_ACCOUNT, + 'user_id' => $request->user()->id, + 'identifier' => $identifier, + ]); + + if (! $response) { + throw new NotFoundHttpException; + } + + return JsonResponse::create([], JsonResponse::HTTP_NO_CONTENT); + } +} diff --git a/app/Http/Controllers/Api/Client/ClientApiController.php b/app/Http/Controllers/Api/Client/ClientApiController.php index e2d4b3f8..56c3db1f 100644 --- a/app/Http/Controllers/Api/Client/ClientApiController.php +++ b/app/Http/Controllers/Api/Client/ClientApiController.php @@ -4,11 +4,46 @@ namespace Pterodactyl\Http\Controllers\Api\Client; use Webmozart\Assert\Assert; use Illuminate\Container\Container; +use Pterodactyl\Transformers\Daemon\BaseDaemonTransformer; use Pterodactyl\Transformers\Api\Client\BaseClientTransformer; use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController; abstract class ClientApiController extends ApplicationApiController { + /** + * Returns only the includes which are valid for the given transformer. + * + * @param \Pterodactyl\Transformers\Api\Client\BaseClientTransformer $transformer + * @param array $merge + * @return string[] + */ + protected function getIncludesForTransformer(BaseClientTransformer $transformer, array $merge = []) + { + $filtered = array_filter($this->parseIncludes(), function ($datum) use ($transformer) { + return in_array($datum, $transformer->getAvailableIncludes()); + }); + + return array_merge($filtered, $merge); + } + + /** + * Returns the parsed includes for this request. + * + * @return string[] + */ + protected function parseIncludes() + { + $includes = $this->request->query('include') ?? []; + + if (! is_string($includes)) { + return $includes; + } + + return array_map(function ($item) { + return trim($item); + }, explode(',', $includes)); + } + /** * Return an instance of an application transformer. * @@ -19,10 +54,15 @@ abstract class ClientApiController extends ApplicationApiController { /** @var \Pterodactyl\Transformers\Api\Client\BaseClientTransformer $transformer */ $transformer = Container::getInstance()->make($abstract); - Assert::isInstanceOf($transformer, BaseClientTransformer::class); + Assert::isInstanceOfAny($transformer, [ + BaseClientTransformer::class, + BaseDaemonTransformer::class, + ]); - $transformer->setKey($this->request->attributes->get('api_key')); - $transformer->setUser($this->request->user()); + if ($transformer instanceof BaseClientTransformer) { + $transformer->setKey($this->request->attributes->get('api_key')); + $transformer->setUser($this->request->user()); + } return $transformer; } diff --git a/app/Http/Controllers/Api/Client/ClientController.php b/app/Http/Controllers/Api/Client/ClientController.php index 7b8f4b23..5eec40b5 100644 --- a/app/Http/Controllers/Api/Client/ClientController.php +++ b/app/Http/Controllers/Api/Client/ClientController.php @@ -2,24 +2,26 @@ namespace Pterodactyl\Http\Controllers\Api\Client; -use Pterodactyl\Models\User; +use Pterodactyl\Models\Server; +use Pterodactyl\Models\Permission; +use Spatie\QueryBuilder\QueryBuilder; +use Pterodactyl\Repositories\Eloquent\ServerRepository; use Pterodactyl\Transformers\Api\Client\ServerTransformer; use Pterodactyl\Http\Requests\Api\Client\GetServersRequest; -use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; class ClientController extends ClientApiController { /** - * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface + * @var \Pterodactyl\Repositories\Eloquent\ServerRepository */ private $repository; /** * ClientController constructor. * - * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository + * @param \Pterodactyl\Repositories\Eloquent\ServerRepository $repository */ - public function __construct(ServerRepositoryInterface $repository) + public function __construct(ServerRepository $repository) { parent::__construct(); @@ -35,29 +37,46 @@ class ClientController extends ClientApiController */ public function index(GetServersRequest $request): array { - // Check for the filter parameter on the request. - switch ($request->input('filter')) { - case 'all': - $filter = User::FILTER_LEVEL_ALL; - break; - case 'admin': - $filter = User::FILTER_LEVEL_ADMIN; - break; - case 'owner': - $filter = User::FILTER_LEVEL_OWNER; - break; - case 'subuser-of': - default: - $filter = User::FILTER_LEVEL_SUBUSER; - break; + $user = $request->user(); + $transformer = $this->getTransformer(ServerTransformer::class); + + // Start the query builder and ensure we eager load any requested relationships from the request. + $builder = QueryBuilder::for( + Server::query()->with($this->getIncludesForTransformer($transformer, ['node'])) + )->allowedFilters('uuid', 'name', 'external_id'); + + // Either return all of the servers the user has access to because they are an admin `?type=admin` or + // just return all of the servers the user has access to because they are the owner or a subuser of the + // server. + if ($request->input('type') === 'admin') { + $builder = $user->root_admin + ? $builder->whereNotIn('id', $user->accessibleServers()->pluck('id')->all()) + // If they aren't an admin but want all the admin servers don't fail the request, just + // make it a query that will never return any results back. + : $builder->whereRaw('1 = 2'); + } elseif ($request->input('type') === 'owner') { + $builder = $builder->where('owner_id', $user->id); + } else { + $builder = $builder->whereIn('id', $user->accessibleServers()->pluck('id')->all()); } - $servers = $this->repository->filterUserAccessServers( - $request->user(), $filter, config('pterodactyl.paginate.frontend.servers') - ); + $servers = $builder->paginate(min($request->query('per_page', 50), 100))->appends($request->query()); - return $this->fractal->collection($servers) - ->transformWith($this->getTransformer(ServerTransformer::class)) - ->toArray(); + return $this->fractal->transformWith($transformer)->collection($servers)->toArray(); + } + + /** + * Returns all of the subuser permissions available on the system. + * + * @return array + */ + public function permissions() + { + return [ + 'object' => 'system_permissions', + 'attributes' => [ + 'permissions' => Permission::permissions(), + ], + ]; } } diff --git a/app/Http/Controllers/Api/Client/Servers/BackupController.php b/app/Http/Controllers/Api/Client/Servers/BackupController.php new file mode 100644 index 00000000..cc8ba091 --- /dev/null +++ b/app/Http/Controllers/Api/Client/Servers/BackupController.php @@ -0,0 +1,122 @@ +initiateBackupService = $initiateBackupService; + $this->deleteBackupService = $deleteBackupService; + $this->repository = $repository; + } + + /** + * Returns all of the backups for a given server instance in a paginated + * result set. + * + * @param \Pterodactyl\Http\Requests\Api\Client\Servers\Backups\GetBackupsRequest $request + * @param \Pterodactyl\Models\Server $server + * @return array + */ + public function index(GetBackupsRequest $request, Server $server) + { + return $this->fractal->collection($server->backups()->paginate(20)) + ->transformWith($this->getTransformer(BackupTransformer::class)) + ->toArray(); + } + + /** + * Starts the backup process for a server. + * + * @param \Pterodactyl\Http\Requests\Api\Client\Servers\Backups\StoreBackupRequest $request + * @param \Pterodactyl\Models\Server $server + * @return array + * + * @throws \Exception|\Throwable + */ + public function store(StoreBackupRequest $request, Server $server) + { + $backup = $this->initiateBackupService + ->setIgnoredFiles( + explode(PHP_EOL, $request->input('ignored') ?? '') + ) + ->handle($server, $request->input('name')); + + return $this->fractal->item($backup) + ->transformWith($this->getTransformer(BackupTransformer::class)) + ->toArray(); + } + + /** + * Returns information about a single backup. + * + * @param \Pterodactyl\Http\Requests\Api\Client\Servers\Backups\GetBackupsRequest $request + * @param \Pterodactyl\Models\Server $server + * @param \Pterodactyl\Models\Backup $backup + * @return array + */ + public function view(GetBackupsRequest $request, Server $server, Backup $backup) + { + return $this->fractal->item($backup) + ->transformWith($this->getTransformer(BackupTransformer::class)) + ->toArray(); + } + + /** + * Deletes a backup from the panel as well as the remote source where it is currently + * being stored. + * + * @param \Pterodactyl\Http\Requests\Api\Client\Servers\Backups\DeleteBackupRequest $request + * @param \Pterodactyl\Models\Server $server + * @param \Pterodactyl\Models\Backup $backup + * @return \Illuminate\Http\JsonResponse + * + * @throws \Throwable + */ + public function delete(DeleteBackupRequest $request, Server $server, Backup $backup) + { + $this->deleteBackupService->handle($backup); + + return JsonResponse::create([], JsonResponse::HTTP_NO_CONTENT); + } +} diff --git a/app/Http/Controllers/Api/Client/Servers/CommandController.php b/app/Http/Controllers/Api/Client/Servers/CommandController.php index 881548fa..b8390673 100644 --- a/app/Http/Controllers/Api/Client/Servers/CommandController.php +++ b/app/Http/Controllers/Api/Client/Servers/CommandController.php @@ -5,27 +5,26 @@ namespace Pterodactyl\Http\Controllers\Api\Client\Servers; use Illuminate\Http\Response; use Pterodactyl\Models\Server; use Psr\Http\Message\ResponseInterface; -use GuzzleHttp\Exception\ClientException; -use GuzzleHttp\Exception\RequestException; +use GuzzleHttp\Exception\BadResponseException; +use Symfony\Component\HttpKernel\Exception\HttpException; +use Pterodactyl\Repositories\Wings\DaemonCommandRepository; use Pterodactyl\Http\Controllers\Api\Client\ClientApiController; use Pterodactyl\Http\Requests\Api\Client\Servers\SendCommandRequest; use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException; -use Pterodactyl\Contracts\Repository\Daemon\CommandRepositoryInterface; -use Symfony\Component\HttpKernel\Exception\PreconditionFailedHttpException; class CommandController extends ClientApiController { /** - * @var \Pterodactyl\Contracts\Repository\Daemon\CommandRepositoryInterface + * @var \Pterodactyl\Repositories\Wings\DaemonCommandRepository */ private $repository; /** * CommandController constructor. * - * @param \Pterodactyl\Contracts\Repository\Daemon\CommandRepositoryInterface $repository + * @param \Pterodactyl\Repositories\Wings\DaemonCommandRepository $repository */ - public function __construct(CommandRepositoryInterface $repository) + public function __construct(DaemonCommandRepository $repository) { parent::__construct(); @@ -36,27 +35,30 @@ class CommandController extends ClientApiController * Send a command to a running server. * * @param \Pterodactyl\Http\Requests\Api\Client\Servers\SendCommandRequest $request + * @param \Pterodactyl\Models\Server $server * @return \Illuminate\Http\Response * * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException */ - public function index(SendCommandRequest $request): Response + public function index(SendCommandRequest $request, Server $server): Response { - $server = $request->getModel(Server::class); - $token = $request->attributes->get('server_token'); - try { - $this->repository->setServer($server) - ->setToken($token) - ->send($request->input('command')); - } catch (RequestException $exception) { - if ($exception instanceof ClientException) { - if ($exception->getResponse() instanceof ResponseInterface && $exception->getResponse()->getStatusCode() === 412) { - throw new PreconditionFailedHttpException('Server is not online.'); + $this->repository->setServer($server)->send($request->input('command')); + } catch (DaemonConnectionException $exception) { + $previous = $exception->getPrevious(); + + if ($previous instanceof BadResponseException) { + if ( + $previous->getResponse() instanceof ResponseInterface + && $previous->getResponse()->getStatusCode() === Response::HTTP_BAD_GATEWAY + ) { + throw new HttpException( + Response::HTTP_BAD_GATEWAY, 'Server must be online in order to send commands.', $exception + ); } } - throw new DaemonConnectionException($exception); + throw $exception; } return $this->returnNoContent(); diff --git a/app/Http/Controllers/Api/Client/Servers/DatabaseController.php b/app/Http/Controllers/Api/Client/Servers/DatabaseController.php new file mode 100644 index 00000000..2eacfb9e --- /dev/null +++ b/app/Http/Controllers/Api/Client/Servers/DatabaseController.php @@ -0,0 +1,136 @@ +deployDatabaseService = $deployDatabaseService; + $this->repository = $repository; + $this->managementService = $managementService; + $this->passwordService = $passwordService; + } + + /** + * Return all of the databases that belong to the given server. + * + * @param \Pterodactyl\Http\Requests\Api\Client\Servers\Databases\GetDatabasesRequest $request + * @param \Pterodactyl\Models\Server $server + * @return array + */ + public function index(GetDatabasesRequest $request, Server $server): array + { + return $this->fractal->collection($server->databases) + ->transformWith($this->getTransformer(DatabaseTransformer::class)) + ->toArray(); + } + + /** + * Create a new database for the given server and return it. + * + * @param \Pterodactyl\Http\Requests\Api\Client\Servers\Databases\StoreDatabaseRequest $request + * @param \Pterodactyl\Models\Server $server + * @return array + * + * @throws \Throwable + * @throws \Pterodactyl\Exceptions\Service\Database\TooManyDatabasesException + * @throws \Pterodactyl\Exceptions\Service\Database\DatabaseClientFeatureNotEnabledException + */ + public function store(StoreDatabaseRequest $request, Server $server): array + { + $database = $this->deployDatabaseService->handle($server, $request->validated()); + + return $this->fractal->item($database) + ->parseIncludes(['password']) + ->transformWith($this->getTransformer(DatabaseTransformer::class)) + ->toArray(); + } + + /** + * Rotates the password for the given server model and returns a fresh instance to + * the caller. + * + * @param \Pterodactyl\Http\Requests\Api\Client\Servers\Databases\RotatePasswordRequest $request + * @param \Pterodactyl\Models\Server $server + * @param \Pterodactyl\Models\Database $database + * @return array + * + * @throws \Throwable + */ + public function rotatePassword(RotatePasswordRequest $request, Server $server, Database $database) + { + $this->passwordService->handle($database); + $database->refresh(); + + return $this->fractal->item($database) + ->parseIncludes(['password']) + ->transformWith($this->getTransformer(DatabaseTransformer::class)) + ->toArray(); + } + + /** + * Removes a database from the server. + * + * @param \Pterodactyl\Http\Requests\Api\Client\Servers\Databases\DeleteDatabaseRequest $request + * @param \Pterodactyl\Models\Server $server + * @param \Pterodactyl\Models\Database $database + * @return \Illuminate\Http\Response + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function delete(DeleteDatabaseRequest $request, Server $server, Database $database): Response + { + $this->managementService->delete($database); + + return Response::create('', Response::HTTP_NO_CONTENT); + } +} diff --git a/app/Http/Controllers/Api/Client/Servers/DownloadBackupController.php b/app/Http/Controllers/Api/Client/Servers/DownloadBackupController.php new file mode 100644 index 00000000..4c8b16a2 --- /dev/null +++ b/app/Http/Controllers/Api/Client/Servers/DownloadBackupController.php @@ -0,0 +1,144 @@ +daemonBackupRepository = $daemonBackupRepository; + $this->responseFactory = $responseFactory; + $this->jwtService = $jwtService; + $this->backupManager = $backupManager; + } + + /** + * Download the backup for a given server instance. For daemon local files, the file + * will be streamed back through the Panel. For AWS S3 files, a signed URL will be generated + * which the user is redirected to. + * + * @param \Pterodactyl\Http\Requests\Api\Client\Servers\Backups\DownloadBackupRequest $request + * @param \Pterodactyl\Models\Server $server + * @param \Pterodactyl\Models\Backup $backup + * @return \Illuminate\Http\JsonResponse + */ + public function __invoke(DownloadBackupRequest $request, Server $server, Backup $backup) + { + switch ($backup->disk) { + case Backup::ADAPTER_WINGS: + $url = $this->getLocalBackupUrl($backup, $server, $request->user()); + break; + case Backup::ADAPTER_AWS_S3: + $url = $this->getS3BackupUrl($backup, $server); + break; + default: + throw new BadRequestHttpException; + } + + return new JsonResponse([ + 'object' => 'signed_url', + 'attributes' => [ + 'url' => $url, + ], + ]); + } + + /** + * Returns a signed URL that allows us to download a file directly out of a non-public + * S3 bucket by using a signed URL. + * + * @param \Pterodactyl\Models\Backup $backup + * @param \Pterodactyl\Models\Server $server + * @return string + */ + protected function getS3BackupUrl(Backup $backup, Server $server) + { + /** @var \League\Flysystem\AwsS3v3\AwsS3Adapter $adapter */ + $adapter = $this->backupManager->adapter(Backup::ADAPTER_AWS_S3); + + $client = $adapter->getClient(); + + $request = $client->createPresignedRequest( + $client->getCommand('GetObject', [ + 'Bucket' => $adapter->getBucket(), + 'Key' => sprintf('%s/%s.tar.gz', $server->uuid, $backup->uuid), + 'ContentType' => 'application/x-gzip', + ]), + CarbonImmutable::now()->addMinutes(5) + ); + + return $request->getUri()->__toString(); + } + + /** + * Returns a download link a backup stored on a wings instance. + * + * @param \Pterodactyl\Models\Backup $backup + * @param \Pterodactyl\Models\Server $server + * @param \Pterodactyl\Models\User $user + * @return string + */ + protected function getLocalBackupUrl(Backup $backup, Server $server, User $user) + { + $token = $this->jwtService + ->setExpiresAt(CarbonImmutable::now()->addMinutes(15)) + ->setClaims([ + 'backup_uuid' => $backup->uuid, + 'server_uuid' => $server->uuid, + ]) + ->handle($server->node, $user->id . $server->uuid); + + return sprintf( + '%s/download/backup?token=%s', + $server->node->getConnectionAddress(), + $token->__toString() + ); + } +} diff --git a/app/Http/Controllers/Api/Client/Servers/FileController.php b/app/Http/Controllers/Api/Client/Servers/FileController.php new file mode 100644 index 00000000..a175f390 --- /dev/null +++ b/app/Http/Controllers/Api/Client/Servers/FileController.php @@ -0,0 +1,264 @@ +fileRepository = $fileRepository; + $this->responseFactory = $responseFactory; + $this->jwtService = $jwtService; + } + + /** + * Returns a listing of files in a given directory. + * + * @param \Pterodactyl\Http\Requests\Api\Client\Servers\Files\ListFilesRequest $request + * @param \Pterodactyl\Models\Server $server + * @return array + * + * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException + */ + public function directory(ListFilesRequest $request, Server $server): array + { + $contents = $this->fileRepository + ->setServer($server) + ->getDirectory(urlencode(urldecode($request->get('directory') ?? '/'))); + + return $this->fractal->collection($contents) + ->transformWith($this->getTransformer(FileObjectTransformer::class)) + ->toArray(); + } + + /** + * Return the contents of a specified file for the user. + * + * @param \Pterodactyl\Http\Requests\Api\Client\Servers\Files\GetFileContentsRequest $request + * @param \Pterodactyl\Models\Server $server + * @return \Illuminate\Http\Response + * + * @throws \Pterodactyl\Exceptions\Http\Server\FileSizeTooLargeException + * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException + */ + public function contents(GetFileContentsRequest $request, Server $server): Response + { + return new Response( + $this->fileRepository->setServer($server)->getContent( + urlencode(urldecode($request->get('file'))), config('pterodactyl.files.max_edit_size') + ), + Response::HTTP_OK, + ['Content-Type' => 'text/plain'] + ); + } + + /** + * Generates a one-time token with a link that the user can use to + * download a given file. + * + * @param \Pterodactyl\Http\Requests\Api\Client\Servers\Files\GetFileContentsRequest $request + * @param \Pterodactyl\Models\Server $server + * @return array + * + * @throws \Exception + */ + public function download(GetFileContentsRequest $request, Server $server) + { + $token = $this->jwtService + ->setExpiresAt(CarbonImmutable::now()->addMinutes(15)) + ->setClaims([ + 'file_path' => $request->get('file'), + 'server_uuid' => $server->uuid, + ]) + ->handle($server->node, $request->user()->id . $server->uuid); + + return [ + 'object' => 'signed_url', + 'attributes' => [ + 'url' => sprintf( + '%s/download/file?token=%s', + $server->node->getConnectionAddress(), + $token->__toString() + ), + ], + ]; + } + + /** + * Writes the contents of the specified file to the server. + * + * @param \Pterodactyl\Http\Requests\Api\Client\Servers\Files\WriteFileContentRequest $request + * @param \Pterodactyl\Models\Server $server + * @return \Illuminate\Http\JsonResponse + * + * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException + */ + public function write(WriteFileContentRequest $request, Server $server): JsonResponse + { + $this->fileRepository->setServer($server)->putContent( + $request->get('file'), + $request->getContent() + ); + + return new JsonResponse([], Response::HTTP_NO_CONTENT); + } + + /** + * Creates a new folder on the server. + * + * @param \Pterodactyl\Http\Requests\Api\Client\Servers\Files\CreateFolderRequest $request + * @param \Pterodactyl\Models\Server $server + * @return \Illuminate\Http\JsonResponse + * + * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException + */ + public function create(CreateFolderRequest $request, Server $server): JsonResponse + { + $this->fileRepository + ->setServer($server) + ->createDirectory($request->input('name'), $request->input('root', '/')); + + return new JsonResponse([], Response::HTTP_NO_CONTENT); + } + + /** + * Renames a file on the remote machine. + * + * @param \Pterodactyl\Http\Requests\Api\Client\Servers\Files\RenameFileRequest $request + * @param \Pterodactyl\Models\Server $server + * @return \Illuminate\Http\JsonResponse + * + * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException + */ + public function rename(RenameFileRequest $request, Server $server): JsonResponse + { + $this->fileRepository + ->setServer($server) + ->renameFiles($request->input('root'), $request->input('files')); + + return new JsonResponse([], Response::HTTP_NO_CONTENT); + } + + /** + * Copies a file on the server. + * + * @param \Pterodactyl\Http\Requests\Api\Client\Servers\Files\CopyFileRequest $request + * @param \Pterodactyl\Models\Server $server + * @return \Illuminate\Http\JsonResponse + * + * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException + */ + public function copy(CopyFileRequest $request, Server $server): JsonResponse + { + $this->fileRepository + ->setServer($server) + ->copyFile($request->input('location')); + + return new JsonResponse([], Response::HTTP_NO_CONTENT); + } + + /** + * @param \Pterodactyl\Http\Requests\Api\Client\Servers\Files\CompressFilesRequest $request + * @param \Pterodactyl\Models\Server $server + * @return array + * + * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException + */ + public function compress(CompressFilesRequest $request, Server $server): array + { + // Allow up to five minutes for this request to process before timing out. + set_time_limit(300); + + $file = $this->fileRepository->setServer($server) + ->compressFiles( + $request->input('root'), $request->input('files') + ); + + return $this->fractal->item($file) + ->transformWith($this->getTransformer(FileObjectTransformer::class)) + ->toArray(); + } + + /** + * @param \Pterodactyl\Http\Requests\Api\Client\Servers\Files\DecompressFilesRequest $request + * @param \Pterodactyl\Models\Server $server + * @return \Illuminate\Http\JsonResponse + * + * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException + */ + public function decompress(DecompressFilesRequest $request, Server $server): JsonResponse + { + // Allow up to five minutes for this request to process before timing out. + set_time_limit(300); + + $this->fileRepository->setServer($server) + ->decompressFile($request->input('root'), $request->input('file')); + + return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT); + } + + /** + * Deletes files or folders for the server in the given root directory. + * + * @param \Pterodactyl\Http\Requests\Api\Client\Servers\Files\DeleteFileRequest $request + * @param \Pterodactyl\Models\Server $server + * @return \Illuminate\Http\JsonResponse + * + * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException + */ + public function delete(DeleteFileRequest $request, Server $server): JsonResponse + { + $this->fileRepository->setServer($server) + ->deleteFiles( + $request->input('root'), $request->input('files') + ); + + return new JsonResponse([], Response::HTTP_NO_CONTENT); + } +} diff --git a/app/Http/Controllers/Api/Client/Servers/FileUploadController.php b/app/Http/Controllers/Api/Client/Servers/FileUploadController.php new file mode 100644 index 00000000..e8f5ad08 --- /dev/null +++ b/app/Http/Controllers/Api/Client/Servers/FileUploadController.php @@ -0,0 +1,73 @@ +jwtService = $jwtService; + } + + /** + * Returns a url where files can be uploaded to. + * + * @param \Pterodactyl\Http\Requests\Api\Client\Servers\Files\UploadFileRequest $request + * @param \Pterodactyl\Models\Server $server + * + * @return \Illuminate\Http\JsonResponse + */ + public function __invoke(UploadFileRequest $request, Server $server) + { + return new JsonResponse([ + 'object' => 'signed_url', + 'attributes' => [ + 'url' => $this->getUploadUrl($server, $request->user()), + ], + ]); + } + + /** + * Returns a url where files can be uploaded to. + * + * @param \Pterodactyl\Models\Server $server + * @param \Pterodactyl\Models\User $user + * @return string + */ + protected function getUploadUrl(Server $server, User $user) + { + $token = $this->jwtService + ->setExpiresAt(CarbonImmutable::now()->addMinutes(15)) + ->setClaims([ + 'server_uuid' => $server->uuid, + ]) + ->handle($server->node, $user->id . $server->uuid); + + return sprintf( + '%s/upload/file?token=%s', + $server->node->getConnectionAddress(), + $token->__toString() + ); + } +} diff --git a/app/Http/Controllers/Api/Client/Servers/NetworkAllocationController.php b/app/Http/Controllers/Api/Client/Servers/NetworkAllocationController.php new file mode 100644 index 00000000..162d815d --- /dev/null +++ b/app/Http/Controllers/Api/Client/Servers/NetworkAllocationController.php @@ -0,0 +1,127 @@ +repository = $repository; + $this->serverRepository = $serverRepository; + } + + /** + * Lists all of the allocations available to a server and wether or + * not they are currently assigned as the primary for this server. + * + * @param \Pterodactyl\Http\Requests\Api\Client\Servers\Network\GetNetworkRequest $request + * @param \Pterodactyl\Models\Server $server + * @return array + */ + public function index(GetNetworkRequest $request, Server $server): array + { + return $this->fractal->collection($server->allocations) + ->transformWith($this->getTransformer(AllocationTransformer::class)) + ->toArray(); + } + + /** + * Set the primary allocation for a server. + * + * @param \Pterodactyl\Http\Requests\Api\Client\Servers\Network\UpdateAllocationRequest $request + * @param \Pterodactyl\Models\Server $server + * @param \Pterodactyl\Models\Allocation $allocation + * @return array + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function update(UpdateAllocationRequest $request, Server $server, Allocation $allocation): array + { + $allocation = $this->repository->update($allocation->id, [ + 'notes' => $request->input('notes'), + ]); + + return $this->fractal->item($allocation) + ->transformWith($this->getTransformer(AllocationTransformer::class)) + ->toArray(); + } + + /** + * Set the primary allocation for a server. + * + * @param \Pterodactyl\Http\Requests\Api\Client\Servers\Network\SetPrimaryAllocationRequest $request + * @param \Pterodactyl\Models\Server $server + * @param \Pterodactyl\Models\Allocation $allocation + * @return array + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function setPrimary(SetPrimaryAllocationRequest $request, Server $server, Allocation $allocation): array + { + $this->serverRepository->update($server->id, ['allocation_id' => $allocation->id]); + + return $this->fractal->item($allocation) + ->transformWith($this->getTransformer(AllocationTransformer::class)) + ->toArray(); + } + + /** + * Delete an allocation from a server. + * + * @param \Pterodactyl\Http\Requests\Api\Client\Servers\Network\DeleteAllocationRequest $request + * @param \Pterodactyl\Models\Server $server + * @param \Pterodactyl\Models\Allocation $allocation + * @return \Illuminate\Http\JsonResponse + * + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function delete(DeleteAllocationRequest $request, Server $server, Allocation $allocation) + { + if ($allocation->id === $server->allocation_id) { + throw new DisplayException( + 'Cannot delete the primary allocation for a server.' + ); + } + + $this->repository->update($allocation->id, ['server_id' => null, 'notes' => null]); + + return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT); + } +} diff --git a/app/Http/Controllers/Api/Client/Servers/PowerController.php b/app/Http/Controllers/Api/Client/Servers/PowerController.php index 7a96a2a4..12e2d75b 100644 --- a/app/Http/Controllers/Api/Client/Servers/PowerController.php +++ b/app/Http/Controllers/Api/Client/Servers/PowerController.php @@ -4,23 +4,23 @@ namespace Pterodactyl\Http\Controllers\Api\Client\Servers; use Illuminate\Http\Response; use Pterodactyl\Models\Server; +use Pterodactyl\Repositories\Wings\DaemonPowerRepository; use Pterodactyl\Http\Controllers\Api\Client\ClientApiController; use Pterodactyl\Http\Requests\Api\Client\Servers\SendPowerRequest; -use Pterodactyl\Contracts\Repository\Daemon\PowerRepositoryInterface; class PowerController extends ClientApiController { /** - * @var \Pterodactyl\Contracts\Repository\Daemon\PowerRepositoryInterface + * @var \Pterodactyl\Repositories\Wings\DaemonPowerRepository */ private $repository; /** * PowerController constructor. * - * @param \Pterodactyl\Contracts\Repository\Daemon\PowerRepositoryInterface $repository + * @param \Pterodactyl\Repositories\Wings\DaemonPowerRepository $repository */ - public function __construct(PowerRepositoryInterface $repository) + public function __construct(DaemonPowerRepository $repository) { parent::__construct(); @@ -31,16 +31,16 @@ class PowerController extends ClientApiController * Send a power action to a server. * * @param \Pterodactyl\Http\Requests\Api\Client\Servers\SendPowerRequest $request + * @param \Pterodactyl\Models\Server $server * @return \Illuminate\Http\Response * - * @throws \Pterodactyl\Exceptions\Repository\Daemon\InvalidPowerSignalException + * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException */ - public function index(SendPowerRequest $request): Response + public function index(SendPowerRequest $request, Server $server): Response { - $server = $request->getModel(Server::class); - $token = $request->attributes->get('server_token'); - - $this->repository->setServer($server)->setToken($token)->sendSignal($request->input('signal')); + $this->repository->setServer($server)->send( + $request->input('signal') + ); return $this->returnNoContent(); } diff --git a/app/Http/Controllers/Api/Client/Servers/ResourceUtilizationController.php b/app/Http/Controllers/Api/Client/Servers/ResourceUtilizationController.php index 75645e9a..0d56c21b 100644 --- a/app/Http/Controllers/Api/Client/Servers/ResourceUtilizationController.php +++ b/app/Http/Controllers/Api/Client/Servers/ResourceUtilizationController.php @@ -4,20 +4,43 @@ namespace Pterodactyl\Http\Controllers\Api\Client\Servers; use Pterodactyl\Models\Server; use Pterodactyl\Transformers\Api\Client\StatsTransformer; +use Pterodactyl\Repositories\Wings\DaemonServerRepository; use Pterodactyl\Http\Controllers\Api\Client\ClientApiController; use Pterodactyl\Http\Requests\Api\Client\Servers\GetServerRequest; class ResourceUtilizationController extends ClientApiController { + /** + * @var \Pterodactyl\Repositories\Wings\DaemonServerRepository + */ + private $repository; + + /** + * ResourceUtilizationController constructor. + * + * @param \Pterodactyl\Repositories\Wings\DaemonServerRepository $repository + */ + public function __construct(DaemonServerRepository $repository) + { + parent::__construct(); + + $this->repository = $repository; + } + /** * Return the current resource utilization for a server. * * @param \Pterodactyl\Http\Requests\Api\Client\Servers\GetServerRequest $request + * @param \Pterodactyl\Models\Server $server * @return array + * + * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException */ - public function index(GetServerRequest $request): array + public function __invoke(GetServerRequest $request, Server $server): array { - return $this->fractal->item($request->getModel(Server::class)) + $stats = $this->repository->setServer($server)->getDetails(); + + return $this->fractal->item($stats) ->transformWith($this->getTransformer(StatsTransformer::class)) ->toArray(); } diff --git a/app/Http/Controllers/Api/Client/Servers/ScheduleController.php b/app/Http/Controllers/Api/Client/Servers/ScheduleController.php new file mode 100644 index 00000000..d35b597e --- /dev/null +++ b/app/Http/Controllers/Api/Client/Servers/ScheduleController.php @@ -0,0 +1,187 @@ +repository = $repository; + } + + /** + * Returns all of the schedules belonging to a given server. + * + * @param \Pterodactyl\Http\Requests\Api\Client\Servers\Schedules\ViewScheduleRequest $request + * @param \Pterodactyl\Models\Server $server + * @return array + */ + public function index(ViewScheduleRequest $request, Server $server) + { + $schedules = $server->schedule; + $schedules->loadMissing('tasks'); + + return $this->fractal->collection($schedules) + ->transformWith($this->getTransformer(ScheduleTransformer::class)) + ->toArray(); + } + + /** + * Store a new schedule for a server. + * + * @param \Pterodactyl\Http\Requests\Api\Client\Servers\Schedules\StoreScheduleRequest $request + * @param \Pterodactyl\Models\Server $server + * @return array + * + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + */ + public function store(StoreScheduleRequest $request, Server $server) + { + /** @var \Pterodactyl\Models\Schedule $model */ + $model = $this->repository->create([ + 'server_id' => $server->id, + 'name' => $request->input('name'), + 'cron_day_of_week' => $request->input('day_of_week'), + 'cron_day_of_month' => $request->input('day_of_month'), + 'cron_hour' => $request->input('hour'), + 'cron_minute' => $request->input('minute'), + 'is_active' => (bool) $request->input('is_active'), + 'next_run_at' => $this->getNextRunAt($request), + ]); + + return $this->fractal->item($model) + ->transformWith($this->getTransformer(ScheduleTransformer::class)) + ->toArray(); + } + + /** + * Returns a specific schedule for the server. + * + * @param \Pterodactyl\Http\Requests\Api\Client\Servers\Schedules\ViewScheduleRequest $request + * @param \Pterodactyl\Models\Server $server + * @param \Pterodactyl\Models\Schedule $schedule + * @return array + */ + public function view(ViewScheduleRequest $request, Server $server, Schedule $schedule) + { + if ($schedule->server_id !== $server->id) { + throw new NotFoundHttpException; + } + + $schedule->loadMissing('tasks'); + + return $this->fractal->item($schedule) + ->transformWith($this->getTransformer(ScheduleTransformer::class)) + ->toArray(); + } + + /** + * Updates a given schedule with the new data provided. + * + * @param \Pterodactyl\Http\Requests\Api\Client\Servers\Schedules\UpdateScheduleRequest $request + * @param \Pterodactyl\Models\Server $server + * @param \Pterodactyl\Models\Schedule $schedule + * @return array + * + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function update(UpdateScheduleRequest $request, Server $server, Schedule $schedule) + { + $active = (bool) $request->input('is_active'); + + $data = [ + 'name' => $request->input('name'), + 'cron_day_of_week' => $request->input('day_of_week'), + 'cron_day_of_month' => $request->input('day_of_month'), + 'cron_hour' => $request->input('hour'), + 'cron_minute' => $request->input('minute'), + 'is_active' => $active, + 'next_run_at' => $this->getNextRunAt($request), + ]; + + // Toggle the processing state of the scheduled task when it is enabled or disabled so that an + // invalid state can be reset without manual database intervention. + // + // @see https://github.com/pterodactyl/panel/issues/2425 + if ($schedule->is_active !== $active) { + $data['is_processing'] = false; + } + + $this->repository->update($schedule->id, $data); + + return $this->fractal->item($schedule->refresh()) + ->transformWith($this->getTransformer(ScheduleTransformer::class)) + ->toArray(); + } + + /** + * Deletes a schedule and it's associated tasks. + * + * @param \Pterodactyl\Http\Requests\Api\Client\Servers\Schedules\DeleteScheduleRequest $request + * @param \Pterodactyl\Models\Server $server + * @param \Pterodactyl\Models\Schedule $schedule + * @return \Illuminate\Http\JsonResponse + */ + public function delete(DeleteScheduleRequest $request, Server $server, Schedule $schedule) + { + $this->repository->delete($schedule->id); + + return new JsonResponse([], Response::HTTP_NO_CONTENT); + } + + /** + * Get the next run timestamp based on the cron data provided. + * + * @param \Illuminate\Http\Request $request + * @return \Carbon\Carbon + * @throws \Pterodactyl\Exceptions\DisplayException + */ + protected function getNextRunAt(Request $request): Carbon + { + try { + return Utilities::getScheduleNextRunDate( + $request->input('minute'), + $request->input('hour'), + $request->input('day_of_month'), + $request->input('day_of_week') + ); + } catch (Exception $exception) { + throw new DisplayException( + 'The cron data provided does not evaluate to a valid expression.' + ); + } + } +} diff --git a/app/Http/Controllers/Api/Client/Servers/ScheduleTaskController.php b/app/Http/Controllers/Api/Client/Servers/ScheduleTaskController.php new file mode 100644 index 00000000..0d613b6b --- /dev/null +++ b/app/Http/Controllers/Api/Client/Servers/ScheduleTaskController.php @@ -0,0 +1,127 @@ +repository = $repository; + } + + /** + * Create a new task for a given schedule and store it in the database. + * + * @param \Pterodactyl\Http\Requests\Api\Client\Servers\Schedules\StoreTaskRequest $request + * @param \Pterodactyl\Models\Server $server + * @param \Pterodactyl\Models\Schedule $schedule + * @return array + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Service\ServiceLimitExceededException + */ + public function store(StoreTaskRequest $request, Server $server, Schedule $schedule) + { + $limit = config('pterodactyl.client_features.schedules.per_schedule_task_limit', 10); + if ($schedule->tasks()->count() >= $limit) { + throw new ServiceLimitExceededException( + "Schedules may not have more than {$limit} tasks associated with them. Creating this task would put this schedule over the limit." + ); + } + + $lastTask = $schedule->tasks->last(); + + /** @var \Pterodactyl\Models\Task $task */ + $task = $this->repository->create([ + 'schedule_id' => $schedule->id, + 'sequence_id' => ($lastTask->sequence_id ?? 0) + 1, + 'action' => $request->input('action'), + 'payload' => $request->input('payload') ?? '', + 'time_offset' => $request->input('time_offset'), + ]); + + return $this->fractal->item($task) + ->transformWith($this->getTransformer(TaskTransformer::class)) + ->toArray(); + } + + /** + * Updates a given task for a server. + * + * @param \Pterodactyl\Http\Requests\Api\Client\Servers\Schedules\StoreTaskRequest $request + * @param \Pterodactyl\Models\Server $server + * @param \Pterodactyl\Models\Schedule $schedule + * @param \Pterodactyl\Models\Task $task + * @return array + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function update(StoreTaskRequest $request, Server $server, Schedule $schedule, Task $task) + { + if ($schedule->id !== $task->schedule_id || $server->id !== $schedule->server_id) { + throw new NotFoundHttpException; + } + + $this->repository->update($task->id, [ + 'action' => $request->input('action'), + 'payload' => $request->input('payload') ?? '', + 'time_offset' => $request->input('time_offset'), + ]); + + return $this->fractal->item($task->refresh()) + ->transformWith($this->getTransformer(TaskTransformer::class)) + ->toArray(); + } + + /** + * Determines if a user can delete the task for a given server. + * + * @param \Pterodactyl\Http\Requests\Api\Client\ClientApiRequest $request + * @param \Pterodactyl\Models\Server $server + * @param \Pterodactyl\Models\Schedule $schedule + * @param \Pterodactyl\Models\Task $task + * @return \Illuminate\Http\JsonResponse + */ + public function delete(ClientApiRequest $request, Server $server, Schedule $schedule, Task $task) + { + if ($task->schedule_id !== $schedule->id || $schedule->server_id !== $server->id) { + throw new NotFoundHttpException; + } + + if (! $request->user()->can(Permission::ACTION_SCHEDULE_UPDATE, $server)) { + throw new HttpForbiddenException('You do not have permission to perform this action.'); + } + + $this->repository->delete($task->id); + + return JsonResponse::create(null, Response::HTTP_NO_CONTENT); + } +} diff --git a/app/Http/Controllers/Api/Client/Servers/ServerController.php b/app/Http/Controllers/Api/Client/Servers/ServerController.php index ce4502e3..96e27632 100644 --- a/app/Http/Controllers/Api/Client/Servers/ServerController.php +++ b/app/Http/Controllers/Api/Client/Servers/ServerController.php @@ -3,23 +3,54 @@ namespace Pterodactyl\Http\Controllers\Api\Client\Servers; use Pterodactyl\Models\Server; +use Pterodactyl\Repositories\Eloquent\SubuserRepository; use Pterodactyl\Transformers\Api\Client\ServerTransformer; +use Pterodactyl\Services\Servers\GetUserPermissionsService; use Pterodactyl\Http\Controllers\Api\Client\ClientApiController; use Pterodactyl\Http\Requests\Api\Client\Servers\GetServerRequest; class ServerController extends ClientApiController { + /** + * @var \Pterodactyl\Repositories\Eloquent\SubuserRepository + */ + private $repository; + + /** + * @var \Pterodactyl\Services\Servers\GetUserPermissionsService + */ + private $permissionsService; + + /** + * ServerController constructor. + * + * @param \Pterodactyl\Services\Servers\GetUserPermissionsService $permissionsService + * @param \Pterodactyl\Repositories\Eloquent\SubuserRepository $repository + */ + public function __construct(GetUserPermissionsService $permissionsService, SubuserRepository $repository) + { + parent::__construct(); + + $this->repository = $repository; + $this->permissionsService = $permissionsService; + } + /** * Transform an individual server into a response that can be consumed by a * client using the API. * * @param \Pterodactyl\Http\Requests\Api\Client\Servers\GetServerRequest $request + * @param \Pterodactyl\Models\Server $server * @return array */ - public function index(GetServerRequest $request): array + public function index(GetServerRequest $request, Server $server): array { - return $this->fractal->item($request->getModel(Server::class)) + return $this->fractal->item($server) ->transformWith($this->getTransformer(ServerTransformer::class)) + ->addMeta([ + 'is_server_owner' => $request->user()->id === $server->owner_id, + 'user_permissions' => $this->permissionsService->handle($server, $request->user()), + ]) ->toArray(); } } diff --git a/app/Http/Controllers/Api/Client/Servers/SettingsController.php b/app/Http/Controllers/Api/Client/Servers/SettingsController.php new file mode 100644 index 00000000..7dfbf7b4 --- /dev/null +++ b/app/Http/Controllers/Api/Client/Servers/SettingsController.php @@ -0,0 +1,76 @@ +repository = $repository; + $this->reinstallServerService = $reinstallServerService; + } + + /** + * Renames a server. + * + * @param \Pterodactyl\Http\Requests\Api\Client\Servers\Settings\RenameServerRequest $request + * @param \Pterodactyl\Models\Server $server + * @return \Illuminate\Http\JsonResponse + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function rename(RenameServerRequest $request, Server $server) + { + $this->repository->update($server->id, [ + 'name' => $request->input('name'), + ]); + + return new JsonResponse([], Response::HTTP_NO_CONTENT); + } + + /** + * Reinstalls the server on the daemon. + * + * @param \Pterodactyl\Http\Requests\Api\Client\Servers\Settings\ReinstallServerRequest $request + * @param \Pterodactyl\Models\Server $server + * @return \Illuminate\Http\JsonResponse + * + * @throws \Throwable + */ + public function reinstall(ReinstallServerRequest $request, Server $server) + { + $this->reinstallServerService->handle($server); + + return new JsonResponse([], Response::HTTP_ACCEPTED); + } +} diff --git a/app/Http/Controllers/Api/Client/Servers/StartupController.php b/app/Http/Controllers/Api/Client/Servers/StartupController.php new file mode 100644 index 00000000..e0c58027 --- /dev/null +++ b/app/Http/Controllers/Api/Client/Servers/StartupController.php @@ -0,0 +1,119 @@ +service = $service; + $this->repository = $repository; + $this->startupCommandService = $startupCommandService; + } + + /** + * Returns the startup information for the server including all of the variables. + * + * @param \Pterodactyl\Http\Requests\Api\Client\Servers\Startup\GetStartupRequest $request + * @param \Pterodactyl\Models\Server $server + * @return array + */ + public function index(GetStartupRequest $request, Server $server) + { + $startup = $this->startupCommandService->handle($server, false); + + return $this->fractal->collection( + $server->variables()->where('user_viewable', true)->get() + ) + ->transformWith($this->getTransformer(EggVariableTransformer::class)) + ->addMeta([ + 'startup_command' => $startup, + 'raw_startup_command' => $server->startup, + ]) + ->toArray(); + } + + /** + * Updates a single variable for a server. + * + * @param \Pterodactyl\Http\Requests\Api\Client\Servers\Startup\UpdateStartupVariableRequest $request + * @param \Pterodactyl\Models\Server $server + * @return array + * + * @throws \Illuminate\Validation\ValidationException + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function update(UpdateStartupVariableRequest $request, Server $server) + { + /** @var \Pterodactyl\Models\EggVariable $variable */ + $variable = $server->variables()->where('env_variable', $request->input('key'))->first(); + + if (is_null($variable) || ! $variable->user_viewable) { + throw new BadRequestHttpException( + "The environment variable you are trying to edit does not exist." + ); + } else if (! $variable->user_editable) { + throw new BadRequestHttpException( + "The environment variable you are trying to edit is read-only." + ); + } + + // Revalidate the variable value using the egg variable specific validation rules for it. + $this->validate($request, ['value' => $variable->rules]); + + $this->repository->updateOrCreate([ + 'server_id' => $server->id, + 'variable_id' => $variable->id, + ], [ + 'variable_value' => $request->input('value') ?? '', + ]); + + $variable = $variable->refresh(); + $variable->server_value = $request->input('value'); + + $startup = $this->startupCommandService->handle($server, false); + + return $this->fractal->item($variable) + ->transformWith($this->getTransformer(EggVariableTransformer::class)) + ->addMeta([ + 'startup_command' => $startup, + 'raw_startup_command' => $server->startup, + ]) + ->toArray(); + } +} diff --git a/app/Http/Controllers/Api/Client/Servers/SubuserController.php b/app/Http/Controllers/Api/Client/Servers/SubuserController.php new file mode 100644 index 00000000..d8bdcc40 --- /dev/null +++ b/app/Http/Controllers/Api/Client/Servers/SubuserController.php @@ -0,0 +1,149 @@ +repository = $repository; + $this->creationService = $creationService; + } + + /** + * Return the users associated with this server instance. + * + * @param \Pterodactyl\Http\Requests\Api\Client\Servers\Subusers\GetSubuserRequest $request + * @param \Pterodactyl\Models\Server $server + * @return array + */ + public function index(GetSubuserRequest $request, Server $server) + { + return $this->fractal->collection($server->subusers) + ->transformWith($this->getTransformer(SubuserTransformer::class)) + ->toArray(); + } + + /** + * Returns a single subuser associated with this server instance. + * + * @param \Pterodactyl\Http\Requests\Api\Client\Servers\Subusers\GetSubuserRequest $request + * @return array + */ + public function view(GetSubuserRequest $request) + { + $subuser = $request->attributes->get('subuser'); + + return $this->fractal->item($subuser) + ->transformWith($this->getTransformer(SubuserTransformer::class)) + ->toArray(); + } + + /** + * Create a new subuser for the given server. + * + * @param \Pterodactyl\Http\Requests\Api\Client\Servers\Subusers\StoreSubuserRequest $request + * @param \Pterodactyl\Models\Server $server + * @return array + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Service\Subuser\ServerSubuserExistsException + * @throws \Pterodactyl\Exceptions\Service\Subuser\UserIsServerOwnerException + * @throws \Throwable + */ + public function store(StoreSubuserRequest $request, Server $server) + { + $response = $this->creationService->handle( + $server, $request->input('email'), $this->getDefaultPermissions($request) + ); + + return $this->fractal->item($response) + ->transformWith($this->getTransformer(SubuserTransformer::class)) + ->toArray(); + } + + /** + * Update a given subuser in the system for the server. + * + * @param \Pterodactyl\Http\Requests\Api\Client\Servers\Subusers\UpdateSubuserRequest $request + * @return array + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function update(UpdateSubuserRequest $request): array + { + /** @var \Pterodactyl\Models\Subuser $subuser */ + $subuser = $request->attributes->get('subuser'); + + $this->repository->update($subuser->id, [ + 'permissions' => $this->getDefaultPermissions($request), + ]); + + return $this->fractal->item($subuser->refresh()) + ->transformWith($this->getTransformer(SubuserTransformer::class)) + ->toArray(); + } + + /** + * Removes a subusers from a server's assignment. + * + * @param \Pterodactyl\Http\Requests\Api\Client\Servers\Subusers\DeleteSubuserRequest $request + * @return \Illuminate\Http\JsonResponse + */ + public function delete(DeleteSubuserRequest $request) + { + /** @var \Pterodactyl\Models\Subuser $subuser */ + $subuser = $request->attributes->get('subuser'); + + $this->repository->delete($subuser->id); + + return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT); + } + + /** + * Returns the default permissions for all subusers to ensure none are ever removed wrongly. + * + * @param \Illuminate\Http\Request $request + * @return array + */ + protected function getDefaultPermissions(Request $request): array + { + return array_unique(array_merge($request->input('permissions') ?? [], [Permission::ACTION_WEBSOCKET_CONNECT])); + } +} diff --git a/app/Http/Controllers/Api/Client/Servers/WebsocketController.php b/app/Http/Controllers/Api/Client/Servers/WebsocketController.php new file mode 100644 index 00000000..a176f66f --- /dev/null +++ b/app/Http/Controllers/Api/Client/Servers/WebsocketController.php @@ -0,0 +1,79 @@ +jwtService = $jwtService; + $this->permissionsService = $permissionsService; + } + + /** + * Generates a one-time token that is sent along in every websocket call to the Daemon. + * This is a signed JWT that the Daemon then uses the verify the user's identity, and + * allows us to continually renew this token and avoid users mainitaining sessions wrongly, + * as well as ensure that user's only perform actions they're allowed to. + * + * @param \Pterodactyl\Http\Requests\Api\Client\ClientApiRequest $request + * @param \Pterodactyl\Models\Server $server + * @return \Illuminate\Http\JsonResponse + */ + public function __invoke(ClientApiRequest $request, Server $server) + { + $user = $request->user(); + if ($user->cannot(Permission::ACTION_WEBSOCKET_CONNECT, $server)) { + throw new HttpException(Response::HTTP_FORBIDDEN, 'You do not have permission to connect to this server\'s websocket.'); + } + + $token = $this->jwtService + ->setExpiresAt(CarbonImmutable::now()->addMinutes(15)) + ->setClaims([ + 'user_id' => $request->user()->id, + 'server_uuid' => $server->uuid, + 'permissions' => $this->permissionsService->handle($server, $user), + ]) + ->handle($server->node, $user->id . $server->uuid); + + $socket = str_replace(['https://', 'http://'], ['wss://', 'ws://'], $server->node->getConnectionAddress()); + + return new JsonResponse([ + 'data' => [ + 'token' => $token->__toString(), + 'socket' => $socket . sprintf('/api/servers/%s/ws', $server->uuid), + ], + ]); + } +} diff --git a/app/Http/Controllers/Api/Client/TwoFactorController.php b/app/Http/Controllers/Api/Client/TwoFactorController.php new file mode 100644 index 00000000..93be78fb --- /dev/null +++ b/app/Http/Controllers/Api/Client/TwoFactorController.php @@ -0,0 +1,134 @@ +setupService = $setupService; + $this->validation = $validation; + $this->toggleTwoFactorService = $toggleTwoFactorService; + } + + /** + * Returns two-factor token credentials that allow a user to configure + * it on their account. If two-factor is already enabled this endpoint + * will return a 400 error. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\JsonResponse + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function index(Request $request) + { + if ($request->user()->use_totp) { + throw new BadRequestHttpException('Two-factor authentication is already enabled on this account.'); + } + + return new JsonResponse([ + 'data' => [ + 'image_url_data' => $this->setupService->handle($request->user()), + ], + ]); + } + + /** + * Updates a user's account to have two-factor enabled. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\JsonResponse + * + * @throws \Illuminate\Validation\ValidationException + * @throws \PragmaRX\Google2FA\Exceptions\IncompatibleWithGoogleAuthenticatorException + * @throws \PragmaRX\Google2FA\Exceptions\InvalidCharactersException + * @throws \PragmaRX\Google2FA\Exceptions\SecretKeyTooShortException + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Pterodactyl\Exceptions\Service\User\TwoFactorAuthenticationTokenInvalid + */ + public function store(Request $request) + { + $validator = $this->validation->make($request->all(), [ + 'code' => 'required|string', + ]); + + if ($validator->fails()) { + throw new ValidationException($validator); + } + + $tokens = $this->toggleTwoFactorService->handle($request->user(), $request->input('code'), true); + + return new JsonResponse([ + 'object' => 'recovery_tokens', + 'attributes' => [ + 'tokens' => $tokens, + ], + ]); + } + + /** + * Disables two-factor authentication on an account if the password provided + * is valid. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\JsonResponse + */ + public function delete(Request $request) + { + if (! password_verify($request->input('password') ?? '', $request->user()->password)) { + throw new BadRequestHttpException( + 'The password provided was not valid.' + ); + } + + /** @var \Pterodactyl\Models\User $user */ + $user = $request->user(); + + $user->update([ + 'totp_authenticated_at' => Carbon::now(), + 'use_totp' => false, + ]); + + return new JsonResponse([], Response::HTTP_NO_CONTENT); + } +} diff --git a/app/Http/Controllers/Api/Remote/Backups/BackupStatusController.php b/app/Http/Controllers/Api/Remote/Backups/BackupStatusController.php new file mode 100644 index 00000000..3f568882 --- /dev/null +++ b/app/Http/Controllers/Api/Remote/Backups/BackupStatusController.php @@ -0,0 +1,61 @@ +repository = $repository; + } + + /** + * Handles updating the state of a backup. + * + * @param \Pterodactyl\Http\Requests\Api\Remote\ReportBackupCompleteRequest $request + * @param string $backup + * @return \Illuminate\Http\JsonResponse + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function __invoke(ReportBackupCompleteRequest $request, string $backup) + { + /** @var \Pterodactyl\Models\Backup $model */ + $model = $this->repository->findFirstWhere([[ 'uuid', '=', $backup ]]); + + if (!is_null($model->completed_at)) { + throw new BadRequestHttpException( + 'Cannot update the status of a backup that is already marked as completed.' + ); + } + + $successful = $request->input('successful') ? true : false; + $model->forceFill([ + 'is_successful' => $successful, + 'checksum' => $successful ? ($request->input('checksum_type') . ':' . $request->input('checksum')) : null, + 'bytes' => $successful ? $request->input('size') : 0, + 'completed_at' => CarbonImmutable::now(), + ])->save(); + + return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT); + } +} diff --git a/app/Http/Controllers/Api/Remote/EggInstallController.php b/app/Http/Controllers/Api/Remote/EggInstallController.php index 65aedfbf..d26da5c8 100644 --- a/app/Http/Controllers/Api/Remote/EggInstallController.php +++ b/app/Http/Controllers/Api/Remote/EggInstallController.php @@ -23,7 +23,7 @@ class EggInstallController extends Controller /** * EggInstallController constructor. * - * @param \Pterodactyl\Services\Servers\EnvironmentService $environment + * @param \Pterodactyl\Services\Servers\EnvironmentService $environment * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository */ public function __construct(EnvironmentService $environment, ServerRepositoryInterface $repository) @@ -37,7 +37,7 @@ class EggInstallController extends Controller * that is being created on the node. * * @param \Illuminate\Http\Request $request - * @param string $uuid + * @param string $uuid * @return \Illuminate\Http\JsonResponse * * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException diff --git a/app/Http/Controllers/Api/Remote/EggRetrievalController.php b/app/Http/Controllers/Api/Remote/EggRetrievalController.php deleted file mode 100644 index 2f006b64..00000000 --- a/app/Http/Controllers/Api/Remote/EggRetrievalController.php +++ /dev/null @@ -1,74 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -namespace Pterodactyl\Http\Controllers\Api\Remote; - -use Illuminate\Http\JsonResponse; -use Pterodactyl\Http\Controllers\Controller; -use Pterodactyl\Services\Eggs\EggConfigurationService; -use Pterodactyl\Contracts\Repository\EggRepositoryInterface; - -class EggRetrievalController extends Controller -{ - /** - * @var \Pterodactyl\Services\Eggs\EggConfigurationService - */ - protected $configurationFileService; - - /** - * @var \Pterodactyl\Contracts\Repository\EggRepositoryInterface - */ - protected $repository; - - /** - * OptionUpdateController constructor. - * - * @param \Pterodactyl\Contracts\Repository\EggRepositoryInterface $repository - * @param \Pterodactyl\Services\Eggs\EggConfigurationService $configurationFileService - */ - public function __construct( - EggRepositoryInterface $repository, - EggConfigurationService $configurationFileService - ) { - $this->configurationFileService = $configurationFileService; - $this->repository = $repository; - } - - /** - * Return a JSON array of Eggs and the SHA1 hash of their configuration file. - * - * @return \Illuminate\Http\JsonResponse - */ - public function index(): JsonResponse - { - $eggs = $this->repository->getAllWithCopyAttributes(); - - $response = []; - $eggs->each(function ($egg) use (&$response) { - $response[$egg->uuid] = sha1(json_encode($this->configurationFileService->handle($egg))); - }); - - return response()->json($response); - } - - /** - * Return the configuration file for a single Egg for the Daemon. - * - * @param string $uuid - * @return \Illuminate\Http\JsonResponse - * - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - */ - public function download(string $uuid): JsonResponse - { - $option = $this->repository->getWithCopyAttributes($uuid, 'uuid'); - - return response()->json($this->configurationFileService->handle($option)); - } -} diff --git a/app/Http/Controllers/Api/Remote/FileDownloadController.php b/app/Http/Controllers/Api/Remote/FileDownloadController.php deleted file mode 100644 index fa4818fc..00000000 --- a/app/Http/Controllers/Api/Remote/FileDownloadController.php +++ /dev/null @@ -1,50 +0,0 @@ -cache = $cache; - } - - /** - * Handle a request to authenticate a download using a token and return - * the path of the file to the daemon. - * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\Http\JsonResponse - * - * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException - */ - public function index(Request $request): JsonResponse - { - $download = $this->cache->pull('Server:Downloads:' . $request->input('token', '')); - - if (is_null($download)) { - throw new NotFoundHttpException('No file was found using the token provided.'); - } - - return response()->json([ - 'path' => array_get($download, 'path'), - 'server' => array_get($download, 'server'), - ]); - } -} diff --git a/app/Http/Controllers/Api/Remote/Servers/ServerDetailsController.php b/app/Http/Controllers/Api/Remote/Servers/ServerDetailsController.php new file mode 100644 index 00000000..2e4aa8ab --- /dev/null +++ b/app/Http/Controllers/Api/Remote/Servers/ServerDetailsController.php @@ -0,0 +1,100 @@ +eggConfigurationService = $eggConfigurationService; + $this->repository = $repository; + $this->configurationStructureService = $configurationStructureService; + $this->nodeRepository = $nodeRepository; + } + + /** + * Returns details about the server that allows Wings to self-recover and ensure + * that the state of the server matches the Panel at all times. + * + * @param \Illuminate\Http\Request $request + * @param string $uuid + * @return \Illuminate\Http\JsonResponse + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function __invoke(Request $request, $uuid) + { + $server = $this->repository->getByUuid($uuid); + + return JsonResponse::create([ + 'settings' => $this->configurationStructureService->handle($server), + 'process_configuration' => $this->eggConfigurationService->handle($server), + ]); + } + + /** + * Lists all servers with their configurations that are assigned to the requesting node. + * + * @param \Illuminate\Http\Request $request + * + * @return \Illuminate\Http\JsonResponse + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function list(Request $request) + { + $node = $request->attributes->get('node'); + $servers = $this->repository->loadEveryServerForNode($node->id); + + $configurations = []; + + foreach ($servers as $server) { + $configurations[$server->uuid] = [ + 'settings' => $this->configurationStructureService->handle($server), + 'process_configuration' => $this->eggConfigurationService->handle($server), + ]; + } + + return JsonResponse::create($configurations); + } +} diff --git a/app/Http/Controllers/Api/Remote/Servers/ServerInstallController.php b/app/Http/Controllers/Api/Remote/Servers/ServerInstallController.php new file mode 100644 index 00000000..530832d4 --- /dev/null +++ b/app/Http/Controllers/Api/Remote/Servers/ServerInstallController.php @@ -0,0 +1,70 @@ +repository = $repository; + } + + /** + * Returns installation information for a server. + * + * @param \Illuminate\Http\Request $request + * @param string $uuid + * @return \Illuminate\Http\JsonResponse + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function index(Request $request, string $uuid) + { + $server = $this->repository->getByUuid($uuid); + $egg = $server->egg; + + return JsonResponse::create([ + 'container_image' => $egg->copy_script_container, + 'entrypoint' => $egg->copy_script_entry, + 'script' => $egg->copy_script_install, + ]); + } + + /** + * Updates the installation state of a server. + * + * @param \Pterodactyl\Http\Requests\Api\Remote\InstallationDataRequest $request + * @param string $uuid + * @return \Illuminate\Http\JsonResponse + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + */ + public function store(InstallationDataRequest $request, string $uuid) + { + $server = $this->repository->getByUuid($uuid); + + $this->repository->update($server->id, [ + 'installed' => (string) $request->input('successful') === '1' ? 1 : 2, + ], true, true); + + return JsonResponse::create([], Response::HTTP_NO_CONTENT); + } +} diff --git a/app/Http/Controllers/Api/Remote/Servers/ServerTransferController.php b/app/Http/Controllers/Api/Remote/Servers/ServerTransferController.php new file mode 100644 index 00000000..bd8827a6 --- /dev/null +++ b/app/Http/Controllers/Api/Remote/Servers/ServerTransferController.php @@ -0,0 +1,238 @@ +connection = $connection; + $this->repository = $repository; + $this->allocationRepository = $allocationRepository; + $this->nodeRepository = $nodeRepository; + $this->daemonServerRepository = $daemonServerRepository; + $this->daemonTransferRepository = $daemonTransferRepository; + $this->configurationStructureService = $configurationStructureService; + $this->suspensionService = $suspensionService; + $this->writer = $writer; + } + + /** + * The daemon notifies us about the archive status. + * + * @param \Illuminate\Http\Request $request + * @param string $uuid + * @return \Illuminate\Http\JsonResponse + * + * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Throwable + */ + public function archive(Request $request, string $uuid) + { + $server = $this->repository->getByUuid($uuid); + + // Unsuspend the server and don't continue the transfer. + if (! $request->input('successful')) { + $this->suspensionService->toggle($server, 'unsuspend'); + + return JsonResponse::create([], Response::HTTP_NO_CONTENT); + } + + $server->node_id = $server->transfer->new_node; + + $data = $this->configurationStructureService->handle($server); + $data['suspended'] = false; + $data['service']['skip_scripts'] = true; + + $allocations = $server->getAllocationMappings(); + $data['allocations']['default']['ip'] = array_key_first($allocations); + $data['allocations']['default']['port'] = $allocations[$data['allocations']['default']['ip']][0]; + + $now = Chronos::now(); + $signer = new Sha256; + + $token = (new Builder)->issuedBy(config('app.url')) + ->permittedFor($server->node->getConnectionAddress()) + ->identifiedBy(hash('sha256', $server->uuid), true) + ->issuedAt($now->getTimestamp()) + ->canOnlyBeUsedAfter($now->getTimestamp()) + ->expiresAt($now->addMinutes(15)->getTimestamp()) + ->relatedTo($server->uuid, true) + ->getToken($signer, new Key($server->node->getDecryptedKey())); + + // On the daemon transfer repository, make sure to set the node after the server + // because setServer() tells the repository to use the server's node and not the one + // we want to specify. + try { + $this->daemonTransferRepository + ->setServer($server) + ->setNode($this->nodeRepository->find($server->transfer->new_node)) + ->notify($server, $data, $server->node, $token->__toString()); + } catch (DaemonConnectionException $exception) { + throw $exception; + } + + return JsonResponse::create([], Response::HTTP_NO_CONTENT); + } + + /** + * The daemon notifies us about a transfer failure. + * + * @param \Illuminate\Http\Request $request + * @param string $uuid + * @return \Illuminate\Http\JsonResponse + * + * @throws \Throwable + */ + public function failure(string $uuid) + { + $server = $this->repository->getByUuid($uuid); + $transfer = $server->transfer; + + $allocationIds = json_decode($transfer->new_additional_allocations); + array_push($allocationIds, $transfer->new_allocation); + + // Remove the new allocations. + $this->allocationRepository->updateWhereIn('id', $allocationIds, ['server_id' => null]); + + // Unsuspend the server. + $this->suspensionService->toggle($server, 'unsuspend'); + + return JsonResponse::create([], Response::HTTP_NO_CONTENT); + } + + /** + * The daemon notifies us about a transfer success. + * + * @param string $uuid + * @return \Illuminate\Http\JsonResponse + * + * @throws \Throwable + */ + public function success(string $uuid) + { + $server = $this->repository->getByUuid($uuid); + $transfer = $server->transfer; + + $allocationIds = json_decode($transfer->old_additional_allocations); + array_push($allocationIds, $transfer->old_allocation); + + // Begin a transaction. + $this->connection->beginTransaction(); + + // Remove the old allocations. + $this->allocationRepository->updateWhereIn('id', $allocationIds, ['server_id' => null]); + + // Update the server's allocation_id and node_id. + $server->allocation_id = $transfer->new_allocation; + $server->node_id = $transfer->new_node; + $server->save(); + + // Mark the transfer as successful. + $transfer->successful = true; + $transfer->save(); + + // Commit the transaction. + $this->connection->commit(); + + // Delete the server from the old node + try { + $this->daemonServerRepository->setServer($server)->delete(); + } catch (DaemonConnectionException $exception) { + $this->writer->warning($exception); + } + + // Unsuspend the server + $server->load('node'); + $this->suspensionService->toggle($server, $this->suspensionService::ACTION_UNSUSPEND); + + return JsonResponse::create([], Response::HTTP_NO_CONTENT); + } +} diff --git a/app/Http/Controllers/Api/Remote/SftpAuthenticationController.php b/app/Http/Controllers/Api/Remote/SftpAuthenticationController.php new file mode 100644 index 00000000..b62d6e5c --- /dev/null +++ b/app/Http/Controllers/Api/Remote/SftpAuthenticationController.php @@ -0,0 +1,141 @@ +userRepository = $userRepository; + $this->serverRepository = $serverRepository; + $this->permissionsService = $permissionsService; + } + + /** + * Authenticate a set of credentials and return the associated server details + * for a SFTP connection on the daemon. + * + * @param \Pterodactyl\Http\Requests\Api\Remote\SftpAuthenticationFormRequest $request + * @return \Illuminate\Http\JsonResponse + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function __invoke(SftpAuthenticationFormRequest $request): JsonResponse + { + // Reverse the string to avoid issues with usernames that contain periods. + $parts = explode('.', strrev($request->input('username')), 2); + + // Unreverse the strings after parsing them apart. + $connection = [ + 'username' => strrev(array_get($parts, 1)), + 'server' => strrev(array_get($parts, 0)), + ]; + + if ($this->hasTooManyLoginAttempts($request)) { + $seconds = $this->limiter()->availableIn($this->throttleKey($request)); + + throw new TooManyRequestsHttpException( + $seconds, "Too many login attempts for this account, please try again in {$seconds} seconds." + ); + } + + /** @var \Pterodactyl\Models\Node $node */ + $node = $request->attributes->get('node'); + if (empty($connection['server'])) { + throw new NotFoundHttpException; + } + + /** @var \Pterodactyl\Models\User $user */ + $user = $this->userRepository->findFirstWhere([ + ['username', '=', $connection['username']], + ]); + + $server = $this->serverRepository->getByUuid($connection['server'] ?? ''); + if (! password_verify($request->input('password'), $user->password) || $server->node_id !== $node->id) { + $this->incrementLoginAttempts($request); + + throw new HttpForbiddenException( + 'Authorization credentials were not correct, please try again.' + ); + } + + if (! $user->root_admin && $server->owner_id !== $user->id) { + $permissions = $this->permissionsService->handle($server, $user); + + if (! in_array(Permission::ACTION_FILE_SFTP, $permissions)) { + throw new HttpForbiddenException( + 'You do not have permission to access SFTP for this server.' + ); + } + } + + // Remeber, for security purposes, only reveal the existence of the server to people that + // have provided valid credentials, and have permissions to know about it. + if ($server->installed !== 1 || $server->suspended) { + throw new BadRequestHttpException( + 'Server is not installed or is currently suspended.' + ); + } + + return JsonResponse::create([ + 'server' => $server->uuid, + // Deprecated, but still needed at the moment for Wings. + 'token' => '', + 'permissions' => $permissions ?? ['*'], + ]); + } + + /** + * Get the throttle key for the given request. + * + * @param \Illuminate\Http\Request $request + * @return string + */ + protected function throttleKey(Request $request) + { + $username = explode('.', strrev($request->input('username', ''))); + + return strtolower(strrev($username[0] ?? '') . '|' . $request->ip()); + } +} diff --git a/app/Http/Controllers/Api/Remote/SftpController.php b/app/Http/Controllers/Api/Remote/SftpController.php deleted file mode 100644 index 08354423..00000000 --- a/app/Http/Controllers/Api/Remote/SftpController.php +++ /dev/null @@ -1,91 +0,0 @@ -authenticationService = $authenticationService; - } - - /** - * Authenticate a set of credentials and return the associated server details - * for a SFTP connection on the daemon. - * - * @param \Pterodactyl\Http\Requests\Api\Remote\SftpAuthenticationFormRequest $request - * @return \Illuminate\Http\JsonResponse - * - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - */ - public function index(SftpAuthenticationFormRequest $request): JsonResponse - { - $parts = explode('.', strrev($request->input('username')), 2); - $connection = [ - 'username' => strrev(array_get($parts, 1)), - 'server' => strrev(array_get($parts, 0)), - ]; - - $this->incrementLoginAttempts($request); - - if ($this->hasTooManyLoginAttempts($request)) { - return response()->json([ - 'error' => 'Logins throttled.', - ], Response::HTTP_TOO_MANY_REQUESTS); - } - - try { - $data = $this->authenticationService->handle( - $connection['username'], - $request->input('password'), - object_get($request->attributes->get('node'), 'id', 0), - empty($connection['server']) ? null : $connection['server'] - ); - - $this->clearLoginAttempts($request); - } catch (BadRequestHttpException $exception) { - return response()->json([ - 'error' => 'The server you are trying to access is not installed or is suspended.', - ], Response::HTTP_BAD_REQUEST); - } catch (RecordNotFoundException $exception) { - return response()->json([ - 'error' => 'Unable to locate a resource using the username and password provided.', - ], Response::HTTP_NOT_FOUND); - } - - return response()->json($data); - } - - /** - * Get the throttle key for the given request. - * - * @param \Illuminate\Http\Request $request - * @return string - */ - protected function throttleKey(Request $request) - { - return strtolower(array_get(explode('.', $request->input('username')), 0) . '|' . $request->ip()); - } -} diff --git a/app/Http/Controllers/Api/Remote/ValidateKeyController.php b/app/Http/Controllers/Api/Remote/ValidateKeyController.php deleted file mode 100644 index 86a02cbc..00000000 --- a/app/Http/Controllers/Api/Remote/ValidateKeyController.php +++ /dev/null @@ -1,100 +0,0 @@ -. - * - * 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. - */ - -namespace Pterodactyl\Http\Controllers\Api\Remote; - -use Spatie\Fractal\Fractal; -use Illuminate\Http\Response; -use Pterodactyl\Http\Controllers\Controller; -use Illuminate\Contracts\Foundation\Application; -use Illuminate\Foundation\Testing\HttpException; -use League\Fractal\Serializer\JsonApiSerializer; -use Pterodactyl\Transformers\Daemon\ApiKeyTransformer; -use Pterodactyl\Exceptions\Repository\RecordNotFoundException; -use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; -use Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface; - -class ValidateKeyController extends Controller -{ - /** - * @var \Illuminate\Contracts\Foundation\Application - */ - protected $app; - - /** - * @var \Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface - */ - protected $daemonKeyRepository; - - /** - * @var \Spatie\Fractal\Fractal - */ - protected $fractal; - - /** - * ValidateKeyController constructor. - * - * @param \Illuminate\Contracts\Foundation\Application $app - * @param \Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface $daemonKeyRepository - * @param \Spatie\Fractal\Fractal $fractal - */ - public function __construct( - Application $app, - DaemonKeyRepositoryInterface $daemonKeyRepository, - Fractal $fractal - ) { - $this->app = $app; - $this->daemonKeyRepository = $daemonKeyRepository; - $this->fractal = $fractal; - } - - /** - * Return the server(s) and permissions associated with an API key. - * - * @param string $token - * @return array - * - * @throws \Illuminate\Foundation\Testing\HttpException - */ - public function index($token) - { - if (! starts_with($token, DaemonKeyRepositoryInterface::INTERNAL_KEY_IDENTIFIER)) { - throw new HttpException(Response::HTTP_NOT_IMPLEMENTED); - } - - try { - $key = $this->daemonKeyRepository->getKeyWithServer($token); - } catch (RecordNotFoundException $exception) { - throw new NotFoundHttpException; - } - - if ($key->getRelation('server')->suspended || $key->getRelation('server')->installed !== 1) { - throw new NotFoundHttpException; - } - - return $this->fractal->item($key, $this->app->make(ApiKeyTransformer::class), 'server') - ->serializeWith(JsonApiSerializer::class) - ->toArray(); - } -} diff --git a/app/Http/Controllers/Auth/AbstractLoginController.php b/app/Http/Controllers/Auth/AbstractLoginController.php new file mode 100644 index 00000000..b24a1a62 --- /dev/null +++ b/app/Http/Controllers/Auth/AbstractLoginController.php @@ -0,0 +1,135 @@ +lockoutTime = $config->get('auth.lockout.time'); + $this->maxLoginAttempts = $config->get('auth.lockout.attempts'); + + $this->auth = $auth; + $this->config = $config; + } + + /** + * Get the failed login response instance. + * + * @param \Illuminate\Http\Request $request + * @param \Illuminate\Contracts\Auth\Authenticatable|null $user + * @param string|null $message + * + * @throws \Pterodactyl\Exceptions\DisplayException + */ + protected function sendFailedLoginResponse(Request $request, Authenticatable $user = null, string $message = null) + { + $this->incrementLoginAttempts($request); + $this->fireFailedLoginEvent($user, [ + $this->getField($request->input('user')) => $request->input('user'), + ]); + + if ($request->route()->named('auth.login-checkpoint')) { + throw new DisplayException( + $message ?? trans('auth.two_factor.checkpoint_failed') + ); + } + + throw new DisplayException(trans('auth.failed')); + } + + /** + * Send the response after the user was authenticated. + * + * @param \Pterodactyl\Models\User $user + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\JsonResponse + */ + protected function sendLoginResponse(User $user, Request $request): JsonResponse + { + $request->session()->regenerate(); + $this->clearLoginAttempts($request); + + $this->auth->guard()->login($user, true); + + return JsonResponse::create([ + 'data' => [ + 'complete' => true, + 'intended' => $this->redirectPath(), + 'user' => $user->toVueObject(), + ], + ]); + } + + /** + * Determine if the user is logging in using an email or username,. + * + * @param string $input + * @return string + */ + protected function getField(string $input = null): string + { + return ($input && str_contains($input, '@')) ? 'email' : 'username'; + } + + /** + * Fire a failed login event. + * + * @param \Illuminate\Contracts\Auth\Authenticatable|null $user + * @param array $credentials + */ + protected function fireFailedLoginEvent(Authenticatable $user = null, array $credentials = []) + { + event(new Failed('auth', $user, $credentials)); + } +} diff --git a/app/Http/Controllers/Auth/ForgotPasswordController.php b/app/Http/Controllers/Auth/ForgotPasswordController.php index 896489f9..8f78f8c0 100644 --- a/app/Http/Controllers/Auth/ForgotPasswordController.php +++ b/app/Http/Controllers/Auth/ForgotPasswordController.php @@ -3,7 +3,7 @@ namespace Pterodactyl\Http\Controllers\Auth; use Illuminate\Http\Request; -use Illuminate\Http\RedirectResponse; +use Illuminate\Http\JsonResponse; use Illuminate\Support\Facades\Password; use Pterodactyl\Http\Controllers\Controller; use Pterodactyl\Events\Auth\FailedPasswordReset; @@ -16,11 +16,11 @@ class ForgotPasswordController extends Controller /** * Get the response for a failed password reset link. * - * @param \Illuminate\Http\Request + * @param \Illuminate\Http\Request * @param string $response - * @return \Illuminate\Http\RedirectResponse + * @return \Illuminate\Http\JsonResponse */ - protected function sendResetLinkFailedResponse(Request $request, $response): RedirectResponse + protected function sendResetLinkFailedResponse(Request $request, $response): JsonResponse { // As noted in #358 we will return success even if it failed // to avoid pointing out that an account does or does not @@ -29,4 +29,18 @@ class ForgotPasswordController extends Controller return $this->sendResetLinkResponse($request, Password::RESET_LINK_SENT); } + + /** + * Get the response for a successful password reset link. + * + * @param \Illuminate\Http\Request $request + * @param string $response + * @return \Illuminate\Http\JsonResponse + */ + protected function sendResetLinkResponse(Request $request, $response): JsonResponse + { + return response()->json([ + 'status' => trans($response), + ]); + } } diff --git a/app/Http/Controllers/Auth/LoginCheckpointController.php b/app/Http/Controllers/Auth/LoginCheckpointController.php new file mode 100644 index 00000000..c44f18a8 --- /dev/null +++ b/app/Http/Controllers/Auth/LoginCheckpointController.php @@ -0,0 +1,119 @@ +google2FA = $google2FA; + $this->cache = $cache; + $this->repository = $repository; + $this->encrypter = $encrypter; + $this->recoveryTokenRepository = $recoveryTokenRepository; + } + + /** + * Handle a login where the user is required to provide a TOTP authentication + * token. Once a user has reached this stage it is assumed that they have already + * provided a valid username and password. + * + * @param \Pterodactyl\Http\Requests\Auth\LoginCheckpointRequest $request + * @return \Illuminate\Http\JsonResponse|void + * + * @throws \PragmaRX\Google2FA\Exceptions\IncompatibleWithGoogleAuthenticatorException + * @throws \PragmaRX\Google2FA\Exceptions\InvalidCharactersException + * @throws \PragmaRX\Google2FA\Exceptions\SecretKeyTooShortException + * @throws \Pterodactyl\Exceptions\DisplayException + */ + public function __invoke(LoginCheckpointRequest $request): JsonResponse + { + $token = $request->input('confirmation_token'); + $recoveryToken = $request->input('recovery_token'); + + try { + /** @var \Pterodactyl\Models\User $user */ + $user = $this->repository->find($this->cache->get($token, 0)); + } catch (RecordNotFoundException $exception) { + return $this->sendFailedLoginResponse($request, null, 'The authentication token provided has expired, please refresh the page and try again.'); + } + + // If we got a recovery token try to find one that matches for the user and then continue + // through the process (and delete the token). + if (! is_null($recoveryToken)) { + foreach ($user->recoveryTokens as $token) { + if (password_verify($recoveryToken, $token->token)) { + $this->recoveryTokenRepository->delete($token->id); + + return $this->sendLoginResponse($user, $request); + } + } + } else { + $decrypted = $this->encrypter->decrypt($user->totp_secret); + + if ($this->google2FA->verifyKey($decrypted, (string) $request->input('authentication_code') ?? '', config('pterodactyl.auth.2fa.window'))) { + $this->cache->delete($token); + + return $this->sendLoginResponse($user, $request); + } + } + + return $this->sendFailedLoginResponse($request, $user, ! empty($recoveryToken) ? 'The recovery token provided is not valid.' : null); + } +} diff --git a/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php index d5141c8d..593189db 100644 --- a/app/Http/Controllers/Auth/LoginController.php +++ b/app/Http/Controllers/Auth/LoginController.php @@ -2,115 +2,82 @@ namespace Pterodactyl\Http\Controllers\Auth; +use Cake\Chronos\Chronos; +use Illuminate\Support\Str; use Illuminate\Http\Request; use Illuminate\Auth\AuthManager; -use PragmaRX\Google2FA\Google2FA; -use Illuminate\Auth\Events\Failed; -use Illuminate\Http\RedirectResponse; -use Pterodactyl\Http\Controllers\Controller; -use Illuminate\Contracts\Auth\Authenticatable; -use Illuminate\Contracts\Encryption\Encrypter; -use Illuminate\Foundation\Auth\AuthenticatesUsers; +use Illuminate\Http\JsonResponse; +use Illuminate\Contracts\View\View; +use Illuminate\Contracts\Config\Repository; +use Illuminate\Contracts\View\Factory as ViewFactory; use Illuminate\Contracts\Cache\Repository as CacheRepository; use Pterodactyl\Contracts\Repository\UserRepositoryInterface; use Pterodactyl\Exceptions\Repository\RecordNotFoundException; -use Illuminate\Contracts\Config\Repository as ConfigRepository; -class LoginController extends Controller +class LoginController extends AbstractLoginController { - use AuthenticatesUsers; - /** - * @var \Illuminate\Auth\AuthManager + * @var \Illuminate\Contracts\View\Factory */ - private $auth; + private $view; /** * @var \Illuminate\Contracts\Cache\Repository */ private $cache; - /** - * @var \Illuminate\Contracts\Config\Repository - */ - private $config; - - /** - * @var \Illuminate\Contracts\Encryption\Encrypter - */ - private $encrypter; - /** * @var \Pterodactyl\Contracts\Repository\UserRepositoryInterface */ private $repository; - /** - * @var \PragmaRX\Google2FA\Google2FA - */ - private $google2FA; - - /** - * Where to redirect users after login / registration. - * - * @var string - */ - protected $redirectTo = '/'; - - /** - * Lockout time for failed login requests. - * - * @var int - */ - protected $decayMinutes; - - /** - * After how many attempts should logins be throttled and locked. - * - * @var int - */ - protected $maxAttempts; - /** * LoginController constructor. * - * @param \Illuminate\Auth\AuthManager $auth - * @param \Illuminate\Contracts\Cache\Repository $cache - * @param \Illuminate\Contracts\Config\Repository $config - * @param \Illuminate\Contracts\Encryption\Encrypter $encrypter - * @param \PragmaRX\Google2FA\Google2FA $google2FA + * @param \Illuminate\Auth\AuthManager $auth + * @param \Illuminate\Contracts\Config\Repository $config + * @param \Illuminate\Contracts\Cache\Repository $cache * @param \Pterodactyl\Contracts\Repository\UserRepositoryInterface $repository + * @param \Illuminate\Contracts\View\Factory $view */ public function __construct( AuthManager $auth, + Repository $config, CacheRepository $cache, - ConfigRepository $config, - Encrypter $encrypter, - Google2FA $google2FA, - UserRepositoryInterface $repository + UserRepositoryInterface $repository, + ViewFactory $view ) { - $this->auth = $auth; - $this->cache = $cache; - $this->config = $config; - $this->encrypter = $encrypter; - $this->google2FA = $google2FA; - $this->repository = $repository; + parent::__construct($auth, $config); - $this->decayMinutes = $this->config->get('auth.lockout.time'); - $this->maxAttempts = $this->config->get('auth.lockout.attempts'); + $this->view = $view; + $this->cache = $cache; + $this->repository = $repository; + } + + /** + * Handle all incoming requests for the authentication routes and render the + * base authentication view component. Vuejs will take over at this point and + * turn the login area into a SPA. + * + * @return \Illuminate\Contracts\View\View + */ + public function index(): View + { + return $this->view->make('templates/auth.core'); } /** * Handle a login request to the application. * * @param \Illuminate\Http\Request $request - * @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\Response + * @return \Illuminate\Http\JsonResponse|void * + * @throws \Pterodactyl\Exceptions\DisplayException * @throws \Illuminate\Validation\ValidationException */ - public function login(Request $request) + public function login(Request $request): JsonResponse { - $username = $request->input($this->username()); + $username = $request->input('user'); $useColumn = $this->getField($username); if ($this->hasTooManyLoginAttempts($request)) { @@ -124,132 +91,28 @@ class LoginController extends Controller return $this->sendFailedLoginResponse($request); } + // Ensure that the account is using a valid username and password before trying to + // continue. Previously this was handled in the 2FA checkpoint, however that has + // a flaw in which you can discover if an account exists simply by seeing if you + // can proceede to the next step in the login process. if (! password_verify($request->input('password'), $user->password)) { return $this->sendFailedLoginResponse($request, $user); } if ($user->use_totp) { - $token = str_random(64); - $this->cache->put($token, ['user_id' => $user->id, 'valid_credentials' => true], 5); + $token = Str::random(64); + $this->cache->put($token, $user->id, Chronos::now()->addMinutes(5)); - return redirect()->route('auth.totp')->with('authentication_token', $token); + return new JsonResponse([ + 'data' => [ + 'complete' => false, + 'confirmation_token' => $token, + ], + ]); } $this->auth->guard()->login($user, true); - return $this->sendLoginResponse($request); - } - - /** - * Handle a TOTP implementation page. - * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\Http\RedirectResponse|\Illuminate\View\View - */ - public function totp(Request $request) - { - $token = $request->session()->get('authentication_token'); - if (is_null($token) || $this->auth->guard()->user()) { - return redirect()->route('auth.login'); - } - - return view('auth.totp', ['verify_key' => $token]); - } - - /** - * Handle a login where the user is required to provide a TOTP authentication - * token. - * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\Response - * @throws \PragmaRX\Google2FA\Exceptions\IncompatibleWithGoogleAuthenticatorException - * @throws \PragmaRX\Google2FA\Exceptions\InvalidCharactersException - * @throws \PragmaRX\Google2FA\Exceptions\SecretKeyTooShortException - */ - public function loginUsingTotp(Request $request) - { - if (is_null($request->input('verify_token'))) { - return $this->sendFailedLoginResponse($request); - } - - try { - $cache = $this->cache->pull($request->input('verify_token'), []); - $user = $this->repository->find(array_get($cache, 'user_id', 0)); - } catch (RecordNotFoundException $exception) { - return $this->sendFailedLoginResponse($request); - } - - if (is_null($request->input('2fa_token'))) { - return $this->sendFailedLoginResponse($request, $user); - } - - if (! $this->google2FA->verifyKey( - $this->encrypter->decrypt($user->totp_secret), - $request->input('2fa_token'), - $this->config->get('pterodactyl.auth.2fa.window') - )) { - return $this->sendFailedLoginResponse($request, $user); - } - - $this->auth->guard()->login($user, true); - - return $this->sendLoginResponse($request); - } - - /** - * Get the failed login response instance. - * - * @param \Illuminate\Http\Request $request - * @param \Illuminate\Contracts\Auth\Authenticatable|null $user - * @return \Illuminate\Http\RedirectResponse - */ - protected function sendFailedLoginResponse(Request $request, Authenticatable $user = null): RedirectResponse - { - $this->incrementLoginAttempts($request); - $this->fireFailedLoginEvent($user, [ - $this->getField($request->input($this->username())) => $request->input($this->username()), - ]); - - $errors = [$this->username() => trans('auth.failed')]; - - if ($request->expectsJson()) { - return response()->json($errors, 422); - } - - return redirect()->route('auth.login') - ->withInput($request->only($this->username())) - ->withErrors($errors); - } - - /** - * Get the login username to be used by the controller. - * - * @return string - */ - public function username() - { - return 'user'; - } - - /** - * Determine if the user is logging in using an email or username,. - * - * @param string $input - * @return string - */ - private function getField(string $input = null): string - { - return str_contains($input, '@') ? 'email' : 'username'; - } - - /** - * Fire a failed login event. - * - * @param \Illuminate\Contracts\Auth\Authenticatable|null $user - * @param array $credentials - */ - private function fireFailedLoginEvent(Authenticatable $user = null, array $credentials = []) - { - event(new Failed(config('auth.defaults.guard'), $user, $credentials)); + return $this->sendLoginResponse($user, $request); } } diff --git a/app/Http/Controllers/Auth/ResetPasswordController.php b/app/Http/Controllers/Auth/ResetPasswordController.php index f13511d9..fe559151 100644 --- a/app/Http/Controllers/Auth/ResetPasswordController.php +++ b/app/Http/Controllers/Auth/ResetPasswordController.php @@ -3,13 +3,15 @@ namespace Pterodactyl\Http\Controllers\Auth; use Illuminate\Support\Str; -use Illuminate\Http\Request; -use Prologue\Alerts\AlertsMessageBag; +use Illuminate\Http\JsonResponse; use Illuminate\Contracts\Hashing\Hasher; +use Illuminate\Support\Facades\Password; use Illuminate\Auth\Events\PasswordReset; use Illuminate\Contracts\Events\Dispatcher; +use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Http\Controllers\Controller; use Illuminate\Foundation\Auth\ResetsPasswords; +use Pterodactyl\Http\Requests\Auth\ResetPasswordRequest; use Pterodactyl\Contracts\Repository\UserRepositoryInterface; class ResetPasswordController extends Controller @@ -28,11 +30,6 @@ class ResetPasswordController extends Controller */ protected $hasTwoFactor = false; - /** - * @var \Prologue\Alerts\AlertsMessageBag - */ - private $alerts; - /** * @var \Illuminate\Contracts\Events\Dispatcher */ @@ -51,31 +48,44 @@ class ResetPasswordController extends Controller /** * ResetPasswordController constructor. * - * @param \Prologue\Alerts\AlertsMessageBag $alerts - * @param \Illuminate\Contracts\Events\Dispatcher $dispatcher - * @param \Illuminate\Contracts\Hashing\Hasher $hasher + * @param \Illuminate\Contracts\Events\Dispatcher $dispatcher + * @param \Illuminate\Contracts\Hashing\Hasher $hasher * @param \Pterodactyl\Contracts\Repository\UserRepositoryInterface $userRepository */ - public function __construct(AlertsMessageBag $alerts, Dispatcher $dispatcher, Hasher $hasher, UserRepositoryInterface $userRepository) + public function __construct(Dispatcher $dispatcher, Hasher $hasher, UserRepositoryInterface $userRepository) { - $this->alerts = $alerts; $this->dispatcher = $dispatcher; $this->hasher = $hasher; $this->userRepository = $userRepository; } /** - * Return the rules used when validating password reset. + * Reset the given user's password. * - * @return array + * @param \Pterodactyl\Http\Requests\Auth\ResetPasswordRequest $request + * @return \Illuminate\Http\JsonResponse + * + * @throws \Pterodactyl\Exceptions\DisplayException */ - protected function rules(): array + public function __invoke(ResetPasswordRequest $request): JsonResponse { - return [ - 'token' => 'required', - 'email' => 'required|email', - 'password' => 'required|confirmed|min:8', - ]; + // Here we will attempt to reset the user's password. If it is successful we + // will update the password on an actual user model and persist it to the + // database. Otherwise we will parse the error and return the response. + $response = $this->broker()->reset( + $this->credentials($request), function ($user, $password) { + $this->resetPassword($user, $password); + } + ); + + // If the password was successfully reset, we will redirect the user back to + // the application's home authenticated view. If there is an error we can + // redirect them back to where they came from with their error message. + if ($response === Password::PASSWORD_RESET) { + return $this->sendResetResponse(); + } + + throw new DisplayException(trans($response)); } /** @@ -84,7 +94,7 @@ class ResetPasswordController extends Controller * form with a note telling them their password was changed and to log back in. * * @param \Illuminate\Contracts\Auth\CanResetPassword|\Pterodactyl\Models\User $user - * @param string $password + * @param string $password * * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException @@ -108,19 +118,16 @@ class ResetPasswordController extends Controller } /** - * Get the response for a successful password reset. + * Send a successful password reset response back to the callee. * - * @param \Illuminate\Http\Request $request - * @param string $response - * @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\JsonResponse + * @return \Illuminate\Http\JsonResponse */ - protected function sendResetResponse(Request $request, $response) + protected function sendResetResponse(): JsonResponse { - if ($this->hasTwoFactor) { - $this->alerts->success('Your password was successfully updated. Please log in to continue.')->flash(); - } - - return redirect($this->hasTwoFactor ? route('auth.login') : $this->redirectPath()) - ->with('status', trans($response)); + return response()->json([ + 'success' => true, + 'redirect_to' => $this->redirectTo, + 'send_to_login' => $this->hasTwoFactor, + ]); } } diff --git a/app/Http/Controllers/Base/AccountController.php b/app/Http/Controllers/Base/AccountController.php deleted file mode 100644 index 80811750..00000000 --- a/app/Http/Controllers/Base/AccountController.php +++ /dev/null @@ -1,91 +0,0 @@ -alert = $alert; - $this->updateService = $updateService; - $this->sessionGuard = $authManager->guard(); - } - - /** - * Display base account information page. - * - * @return \Illuminate\View\View - */ - public function index() - { - return view('base.account', [ - 'languages' => $this->getAvailableLanguages(true), - ]); - } - - /** - * Update details for a user's account. - * - * @param \Pterodactyl\Http\Requests\Base\AccountDataFormRequest $request - * @return \Illuminate\Http\RedirectResponse - * - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - */ - public function update(AccountDataFormRequest $request) - { - // Prevent logging this specific session out when the password is changed. This will - // automatically update the user's password anyways, so no need to do anything else here. - if ($request->input('do_action') === 'password') { - $this->sessionGuard->logoutOtherDevices($request->input('new_password')); - } else { - if ($request->input('do_action') === 'email') { - $data = ['email' => $request->input('new_email')]; - } elseif ($request->input('do_action') === 'identity') { - $data = $request->only(['name_first', 'name_last', 'username', 'language']); - } else { - $data = []; - } - - $this->updateService->setUserLevel(User::USER_LEVEL_USER); - $this->updateService->handle($request->user(), $data); - } - - $this->alert->success(trans('base.account.details_updated'))->flash(); - - return redirect()->route('account'); - } -} diff --git a/app/Http/Controllers/Base/AccountKeyController.php b/app/Http/Controllers/Base/AccountKeyController.php deleted file mode 100644 index 7161b4ab..00000000 --- a/app/Http/Controllers/Base/AccountKeyController.php +++ /dev/null @@ -1,115 +0,0 @@ -alert = $alert; - $this->keyService = $keyService; - $this->repository = $repository; - } - - /** - * Display a listing of all account API keys. - * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\View\View - */ - public function index(Request $request): View - { - return view('base.api.index', [ - 'keys' => $this->repository->getAccountKeys($request->user()), - ]); - } - - /** - * Display account API key creation page. - * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\View\View - */ - public function create(Request $request): View - { - return view('base.api.new'); - } - - /** - * Handle saving new account API key. - * - * @param \Pterodactyl\Http\Requests\Base\StoreAccountKeyRequest $request - * @return \Illuminate\Http\RedirectResponse - * - * @throws \Pterodactyl\Exceptions\DisplayException - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - */ - public function store(StoreAccountKeyRequest $request) - { - if ($this->repository->findCountWhere(['user_id' => $request->user()->id]) >= 5) { - throw new DisplayException( - 'Cannot assign more than 5 API keys to an account.' - ); - } - - $this->keyService->setKeyType(ApiKey::TYPE_ACCOUNT)->handle([ - 'user_id' => $request->user()->id, - 'allowed_ips' => $request->input('allowed_ips'), - 'memo' => $request->input('memo'), - ]); - - $this->alert->success(trans('base.api.index.keypair_created'))->flash(); - - return redirect()->route('account.api'); - } - - /** - * Delete an account API key from the Panel via an AJAX request. - * - * @param \Illuminate\Http\Request $request - * @param string $identifier - * @return \Illuminate\Http\Response - */ - public function revoke(Request $request, string $identifier): Response - { - $this->repository->deleteAccountKey($request->user(), $identifier); - - return response('', 204); - } -} diff --git a/app/Http/Controllers/Base/ClientApiController.php b/app/Http/Controllers/Base/ClientApiController.php deleted file mode 100644 index a74c28db..00000000 --- a/app/Http/Controllers/Base/ClientApiController.php +++ /dev/null @@ -1,109 +0,0 @@ -alert = $alert; - $this->creationService = $creationService; - $this->repository = $repository; - } - - /** - * Return all of the API keys available to this user. - * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\View\View - */ - public function index(Request $request): View - { - return view('base.api.index', [ - 'keys' => $this->repository->getAccountKeys($request->user()), - ]); - } - - /** - * Render UI to allow creation of an API key. - * - * @return \Illuminate\View\View - */ - public function create(): View - { - return view('base.api.new'); - } - - /** - * Create the API key and return the user to the key listing page. - * - * @param \Pterodactyl\Http\Requests\Base\CreateClientApiKeyRequest $request - * @return \Illuminate\Http\RedirectResponse - * - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - */ - public function store(CreateClientApiKeyRequest $request): RedirectResponse - { - $allowedIps = null; - if (! is_null($request->input('allowed_ips'))) { - $allowedIps = json_encode(explode(PHP_EOL, $request->input('allowed_ips'))); - } - - $this->creationService->setKeyType(ApiKey::TYPE_ACCOUNT)->handle([ - 'memo' => $request->input('memo'), - 'allowed_ips' => $allowedIps, - 'user_id' => $request->user()->id, - ]); - - $this->alert->success('A new client API key has been generated for your account.')->flash(); - - return redirect()->route('account.api'); - } - - /** - * Delete a client's API key from the panel. - * - * @param \Illuminate\Http\Request $request - * @param $identifier - * @return \Illuminate\Http\Response - */ - public function delete(Request $request, $identifier): Response - { - $this->repository->deleteAccountKey($request->user(), $identifier); - - return response('', 204); - } -} diff --git a/app/Http/Controllers/Base/IndexController.php b/app/Http/Controllers/Base/IndexController.php index 20ef370e..41ff988f 100644 --- a/app/Http/Controllers/Base/IndexController.php +++ b/app/Http/Controllers/Base/IndexController.php @@ -2,29 +2,11 @@ namespace Pterodactyl\Http\Controllers\Base; -use Illuminate\Http\Request; -use Pterodactyl\Models\User; -use Illuminate\Http\Response; -use GuzzleHttp\Exception\ConnectException; -use GuzzleHttp\Exception\RequestException; use Pterodactyl\Http\Controllers\Controller; -use Symfony\Component\HttpKernel\Exception\HttpException; -use Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService; use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; -use Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface as DaemonServerRepositoryInterface; class IndexController extends Controller { - /** - * @var \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface - */ - protected $daemonRepository; - - /** - * @var \Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService - */ - protected $keyProviderService; - /** * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface */ @@ -33,62 +15,20 @@ class IndexController extends Controller /** * IndexController constructor. * - * @param \Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService $keyProviderService - * @param \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface $daemonRepository - * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository + * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository */ - public function __construct( - DaemonKeyProviderService $keyProviderService, - DaemonServerRepositoryInterface $daemonRepository, - ServerRepositoryInterface $repository - ) { - $this->daemonRepository = $daemonRepository; - $this->keyProviderService = $keyProviderService; + public function __construct(ServerRepositoryInterface $repository) + { $this->repository = $repository; } /** * Returns listing of user's servers. * - * @param \Illuminate\Http\Request $request * @return \Illuminate\View\View */ - public function getIndex(Request $request) + public function index() { - $servers = $this->repository->setSearchTerm($request->input('query'))->filterUserAccessServers( - $request->user(), User::FILTER_LEVEL_ALL, config('pterodactyl.paginate.frontend.servers') - ); - - return view('base.index', ['servers' => $servers]); - } - - /** - * Returns status of the server in a JSON response used for populating active status list. - * - * @param \Illuminate\Http\Request $request - * @param string $uuid - * @return \Illuminate\Http\JsonResponse - * @throws \Exception - */ - public function status(Request $request, $uuid) - { - $server = $this->repository->findFirstWhere([['uuidShort', '=', $uuid]]); - $token = $this->keyProviderService->handle($server, $request->user()); - - if (! $server->installed) { - return response()->json(['status' => 20]); - } elseif ($server->suspended) { - return response()->json(['status' => 30]); - } - - try { - $response = $this->daemonRepository->setServer($server)->setToken($token)->details(); - } catch (ConnectException $exception) { - throw new HttpException(Response::HTTP_GATEWAY_TIMEOUT, $exception->getMessage()); - } catch (RequestException $exception) { - throw new HttpException(500, $exception->getMessage()); - } - - return response()->json(json_decode($response->getBody())); + return view('templates/base.core'); } } diff --git a/app/Http/Controllers/Base/LocaleController.php b/app/Http/Controllers/Base/LocaleController.php new file mode 100644 index 00000000..f29ba23d --- /dev/null +++ b/app/Http/Controllers/Base/LocaleController.php @@ -0,0 +1,43 @@ +translator = $translator; + } + + /** + * Returns translation data given a specific locale and namespace. + * + * @param \Illuminate\Http\Request $request + * @param string $locale + * @param string $namespace + * @return \Illuminate\Http\JsonResponse + */ + public function __invoke(Request $request, string $locale, string $namespace) + { + $data = $this->translator->getLoader()->load($locale, str_replace('.', '/', $namespace)); + + return JsonResponse::create($data, 200, [ + 'E-Tag' => md5(json_encode($data)), + ]); + } +} diff --git a/app/Http/Controllers/Base/SecurityController.php b/app/Http/Controllers/Base/SecurityController.php deleted file mode 100644 index 2aa9ac12..00000000 --- a/app/Http/Controllers/Base/SecurityController.php +++ /dev/null @@ -1,153 +0,0 @@ -alert = $alert; - $this->config = $config; - $this->repository = $repository; - $this->toggleTwoFactorService = $toggleTwoFactorService; - $this->twoFactorSetupService = $twoFactorSetupService; - } - - /** - * Returns Security Management Page. - * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\View\View - */ - public function index(Request $request) - { - if ($this->config->get('session.driver') === 'database') { - $activeSessions = $this->repository->getUserSessions($request->user()->id); - } - - return view('base.security', [ - 'sessions' => $activeSessions ?? null, - ]); - } - - /** - * Generates TOTP Secret and returns popup data for user to verify - * that they can generate a valid response. - * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\Http\JsonResponse - * - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - */ - public function generateTotp(Request $request) - { - $totpData = $this->twoFactorSetupService->handle($request->user()); - - return response()->json([ - 'qrImage' => 'https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=' . $totpData, - ]); - } - - /** - * Verifies that 2FA token received is valid and will work on the account. - * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\Http\Response - * - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - */ - public function setTotp(Request $request) - { - try { - $this->toggleTwoFactorService->handle($request->user(), $request->input('token') ?? ''); - - return response('true'); - } catch (TwoFactorAuthenticationTokenInvalid $exception) { - return response('false'); - } - } - - /** - * Disables TOTP on an account. - * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\Http\RedirectResponse - * - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - */ - public function disableTotp(Request $request) - { - try { - $this->toggleTwoFactorService->handle($request->user(), $request->input('token') ?? '', false); - } catch (TwoFactorAuthenticationTokenInvalid $exception) { - $this->alert->danger(trans('base.security.2fa_disable_error'))->flash(); - } - - return redirect()->route('account.security'); - } - - /** - * Revokes a user session. - * - * @param \Illuminate\Http\Request $request - * @param string $id - * @return \Illuminate\Http\RedirectResponse - */ - public function revoke(Request $request, string $id) - { - $this->repository->deleteUserSession($request->user()->id, $id); - - return redirect()->route('account.security'); - } -} diff --git a/app/Http/Controllers/Daemon/ActionController.php b/app/Http/Controllers/Daemon/ActionController.php deleted file mode 100644 index 257d8775..00000000 --- a/app/Http/Controllers/Daemon/ActionController.php +++ /dev/null @@ -1,107 +0,0 @@ -eventDispatcher = $eventDispatcher; - $this->repository = $repository; - } - - /** - * Handles install toggle request from daemon. - * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\Http\JsonResponse - * - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - */ - public function markInstall(Request $request): JsonResponse - { - try { - /** @var \Pterodactyl\Models\Server $server */ - $server = $this->repository->findFirstWhere([ - 'uuid' => $request->input('server'), - ]); - } catch (RecordNotFoundException $exception) { - return JsonResponse::create([ - 'error' => 'No server by that ID was found on the system.', - ], Response::HTTP_UNPROCESSABLE_ENTITY); - } - - if (! $server->relationLoaded('node')) { - $server->load('node'); - } - - $hmac = $request->input('signed'); - $status = $request->input('installed'); - - if (! hash_equals(base64_decode($hmac), hash_hmac('sha256', $server->uuid, $server->getRelation('node')->daemonSecret, true))) { - return JsonResponse::create([ - 'error' => 'Signed HMAC was invalid.', - ], Response::HTTP_FORBIDDEN); - } - - $this->repository->update($server->id, [ - 'installed' => ($status === 'installed') ? 1 : 2, - ], true, true); - - // Only fire event if server installed successfully. - if ($status === 'installed') { - $this->eventDispatcher->dispatch(new ServerInstalled($server)); - } - - // Don't use a 204 here, the daemon is hard-checking for a 200 code. - return JsonResponse::create([]); - } - - /** - * Handles configuration data request from daemon. - * - * @param \Illuminate\Http\Request $request - * @param string $token - * @return \Illuminate\Http\JsonResponse|\Illuminate\Http\Response - */ - public function configuration(Request $request, $token) - { - $nodeId = Cache::pull('Node:Configuration:' . $token); - if (is_null($nodeId)) { - return response()->json(['error' => 'token_invalid'], 403); - } - - $node = Node::findOrFail($nodeId); - - // Manually as getConfigurationAsJson() returns it in correct format already - return response($node->getConfigurationAsJson())->header('Content-Type', 'text/json'); - } -} diff --git a/app/Http/Controllers/Daemon/PackController.php b/app/Http/Controllers/Daemon/PackController.php deleted file mode 100644 index ce482013..00000000 --- a/app/Http/Controllers/Daemon/PackController.php +++ /dev/null @@ -1,73 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -namespace Pterodactyl\Http\Controllers\Daemon; - -use Storage; -use Pterodactyl\Models; -use Illuminate\Http\Request; -use Pterodactyl\Http\Controllers\Controller; - -class PackController extends Controller -{ - /** - * Pulls an install pack archive from the system. - * - * @param \Illuminate\Http\Request $request - * @param string $uuid - * @return \Illuminate\Http\JsonResponse|\Symfony\Component\HttpFoundation\BinaryFileResponse - */ - public function pull(Request $request, $uuid) - { - $pack = Models\Pack::where('uuid', $uuid)->first(); - - if (! $pack) { - return response()->json(['error' => 'No such pack.'], 404); - } - - if (! Storage::exists('packs/' . $pack->uuid . '/archive.tar.gz')) { - return response()->json(['error' => 'There is no archive available for this pack.'], 503); - } - - return response()->download(storage_path('app/packs/' . $pack->uuid . '/archive.tar.gz')); - } - - /** - * Returns the hash information for a pack. - * - * @param \Illuminate\Http\Request $request - * @param string $uuid - * @return \Illuminate\Http\JsonResponse - */ - public function hash(Request $request, $uuid) - { - $pack = Models\Pack::where('uuid', $uuid)->first(); - - if (! $pack) { - return response()->json(['error' => 'No such pack.'], 404); - } - - if (! Storage::exists('packs/' . $pack->uuid . '/archive.tar.gz')) { - return response()->json(['error' => 'There is no archive available for this pack.'], 503); - } - - return response()->json([ - 'archive.tar.gz' => sha1_file(storage_path('app/packs/' . $pack->uuid . '/archive.tar.gz')), - ]); - } - - /** - * Pulls an update pack archive from the system. - * - * @param \Illuminate\Http\Request $request - */ - public function pullUpdate(Request $request) - { - } -} diff --git a/app/Http/Controllers/Server/ConsoleController.php b/app/Http/Controllers/Server/ConsoleController.php deleted file mode 100644 index 0c195925..00000000 --- a/app/Http/Controllers/Server/ConsoleController.php +++ /dev/null @@ -1,72 +0,0 @@ -config = $config; - } - - /** - * Render server index page with the console and power options. - * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\View\View - */ - public function index(Request $request): View - { - $server = $request->attributes->get('server'); - - $this->setRequest($request)->injectJavascript([ - 'server' => [ - 'cpu' => $server->cpu, - ], - 'meta' => [ - 'saveFile' => route('server.files.save', $server->uuidShort), - 'csrfToken' => csrf_token(), - ], - 'config' => [ - 'console_count' => $this->config->get('pterodactyl.console.count'), - 'console_freq' => $this->config->get('pterodactyl.console.frequency'), - ], - ]); - - return view('server.index'); - } - - /** - * Render a stand-alone console in the browser. - * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\View\View - */ - public function console(Request $request): View - { - $this->setRequest($request)->injectJavascript(['config' => [ - 'console_count' => $this->config->get('pterodactyl.console.count'), - 'console_freq' => $this->config->get('pterodactyl.console.frequency'), - ]]); - - return view('server.console'); - } -} diff --git a/app/Http/Controllers/Server/DatabaseController.php b/app/Http/Controllers/Server/DatabaseController.php deleted file mode 100644 index d897ca0c..00000000 --- a/app/Http/Controllers/Server/DatabaseController.php +++ /dev/null @@ -1,163 +0,0 @@ -alert = $alert; - $this->databaseHostRepository = $databaseHostRepository; - $this->deployServerDatabaseService = $deployServerDatabaseService; - $this->managementService = $managementService; - $this->passwordService = $passwordService; - $this->repository = $repository; - } - - /** - * Render the database listing for a server. - * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\View\View - * - * @throws \Illuminate\Auth\Access\AuthorizationException - */ - public function index(Request $request): View - { - $server = $request->attributes->get('server'); - $this->authorize('view-databases', $server); - $this->setRequest($request)->injectJavascript(); - - $canCreateDatabase = config('pterodactyl.client_features.databases.enabled'); - $allowRandom = config('pterodactyl.client_features.databases.allow_random'); - - if ($this->databaseHostRepository->findCountWhere([['node_id', '=', $server->node_id]]) === 0) { - if ($canCreateDatabase && ! $allowRandom) { - $canCreateDatabase = false; - } - } - - $databases = $this->repository->getDatabasesForServer($server->id); - - return view('server.databases.index', [ - 'allowCreation' => $canCreateDatabase, - 'overLimit' => ! is_null($server->database_limit) && count($databases) >= $server->database_limit, - 'databases' => $databases, - ]); - } - - /** - * Handle a request from a user to create a new database for the server. - * - * @param \Pterodactyl\Http\Requests\Server\Database\StoreServerDatabaseRequest $request - * @return \Illuminate\Http\RedirectResponse - * - * @throws \Exception - * @throws \Pterodactyl\Exceptions\Service\Database\DatabaseClientFeatureNotEnabledException - */ - public function store(StoreServerDatabaseRequest $request): RedirectResponse - { - $this->deployServerDatabaseService->handle($request->getServer(), $request->validated()); - - $this->alert->success('Successfully created a new database.')->flash(); - - return redirect()->route('server.databases.index', $request->getServer()->uuidShort); - } - - /** - * Handle a request to update the password for a specific database. - * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\Http\JsonResponse - * - * @throws \Illuminate\Auth\Access\AuthorizationException - * @throws \Throwable - */ - public function update(Request $request): JsonResponse - { - $this->authorize('reset-db-password', $request->attributes->get('server')); - - $password = $this->passwordService->handle($request->attributes->get('database')); - - return response()->json(['password' => $password]); - } - - /** - * Delete a database for this server from the SQL server and Panel database. - * - * @param \Pterodactyl\Http\Requests\Server\Database\DeleteServerDatabaseRequest $request - * @return \Illuminate\Http\Response - * - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - */ - public function delete(DeleteServerDatabaseRequest $request): Response - { - $this->managementService->delete($request->attributes->get('database')->id); - - return response('', Response::HTTP_NO_CONTENT); - } -} diff --git a/app/Http/Controllers/Server/Files/DownloadController.php b/app/Http/Controllers/Server/Files/DownloadController.php deleted file mode 100644 index 04b16d08..00000000 --- a/app/Http/Controllers/Server/Files/DownloadController.php +++ /dev/null @@ -1,57 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -namespace Pterodactyl\Http\Controllers\Server\Files; - -use Ramsey\Uuid\Uuid; -use Illuminate\Http\Request; -use Illuminate\Cache\Repository; -use Illuminate\Http\RedirectResponse; -use Pterodactyl\Http\Controllers\Controller; - -class DownloadController extends Controller -{ - /** - * @var \Illuminate\Cache\Repository - */ - protected $cache; - - /** - * DownloadController constructor. - * - * @param \Illuminate\Cache\Repository $cache - */ - public function __construct(Repository $cache) - { - $this->cache = $cache; - } - - /** - * Setup a unique download link for a user to download a file from. - * - * @param \Illuminate\Http\Request $request - * @param string $uuid - * @param string $file - * @return \Illuminate\Http\RedirectResponse - * - * @throws \Illuminate\Auth\Access\AuthorizationException - */ - public function index(Request $request, string $uuid, string $file): RedirectResponse - { - $server = $request->attributes->get('server'); - $this->authorize('download-files', $server); - - $token = Uuid::uuid4()->toString(); - $node = $server->getRelation('node'); - - $this->cache->put('Server:Downloads:' . $token, ['server' => $server->uuid, 'path' => $file], 5); - - return redirect(sprintf('%s://%s:%s/v1/server/file/download/%s', $node->scheme, $node->fqdn, $node->daemonListen, $token)); - } -} diff --git a/app/Http/Controllers/Server/Files/FileActionsController.php b/app/Http/Controllers/Server/Files/FileActionsController.php deleted file mode 100644 index bd63009a..00000000 --- a/app/Http/Controllers/Server/Files/FileActionsController.php +++ /dev/null @@ -1,120 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -namespace Pterodactyl\Http\Controllers\Server\Files; - -use Illuminate\View\View; -use Illuminate\Http\Request; -use GuzzleHttp\Exception\RequestException; -use Pterodactyl\Http\Controllers\Controller; -use Pterodactyl\Traits\Controllers\JavascriptInjection; -use Pterodactyl\Http\Requests\Server\UpdateFileContentsFormRequest; -use Pterodactyl\Contracts\Repository\Daemon\FileRepositoryInterface; -use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException; - -class FileActionsController extends Controller -{ - use JavascriptInjection; - - /** - * @var \Pterodactyl\Contracts\Repository\Daemon\FileRepositoryInterface - */ - protected $repository; - - /** - * FileActionsController constructor. - * - * @param \Pterodactyl\Contracts\Repository\Daemon\FileRepositoryInterface $repository - */ - public function __construct(FileRepositoryInterface $repository) - { - $this->repository = $repository; - } - - /** - * Display server file index list. - * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\View\View - * - * @throws \Illuminate\Auth\Access\AuthorizationException - */ - public function index(Request $request): View - { - $server = $request->attributes->get('server'); - $this->authorize('list-files', $server); - - $this->setRequest($request)->injectJavascript([ - 'meta' => [ - 'directoryList' => route('server.files.directory-list', $server->uuidShort), - 'csrftoken' => csrf_token(), - ], - 'permissions' => [ - 'moveFiles' => $request->user()->can('move-files', $server), - 'copyFiles' => $request->user()->can('copy-files', $server), - 'compressFiles' => $request->user()->can('compress-files', $server), - 'decompressFiles' => $request->user()->can('decompress-files', $server), - 'createFiles' => $request->user()->can('create-files', $server), - 'downloadFiles' => $request->user()->can('download-files', $server), - 'deleteFiles' => $request->user()->can('delete-files', $server), - ], - ]); - - return view('server.files.index'); - } - - /** - * Render page to manually create a file in the panel. - * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\View\View - * - * @throws \Illuminate\Auth\Access\AuthorizationException - */ - public function create(Request $request): View - { - $this->authorize('create-files', $request->attributes->get('server')); - $this->setRequest($request)->injectJavascript(); - - return view('server.files.add', [ - 'directory' => (in_array($request->get('dir'), [null, '/', ''])) ? '' : trim($request->get('dir'), '/') . '/', - ]); - } - - /** - * Display a form to allow for editing of a file. - * - * @param \Pterodactyl\Http\Requests\Server\UpdateFileContentsFormRequest $request - * @param string $uuid - * @param string $file - * @return \Illuminate\View\View - * - * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException - */ - public function view(UpdateFileContentsFormRequest $request, string $uuid, string $file): View - { - $server = $request->attributes->get('server'); - - $dirname = str_replace('\\', '/', pathinfo($file, PATHINFO_DIRNAME)); - try { - $content = $this->repository->setServer($server)->setToken($request->attributes->get('server_token'))->getContent($file); - } catch (RequestException $exception) { - throw new DaemonConnectionException($exception); - } - - $this->setRequest($request)->injectJavascript(['stat' => $request->attributes->get('file_stats')]); - - return view('server.files.edit', [ - 'file' => $file, - 'stat' => $request->attributes->get('file_stats'), - 'contents' => $content, - 'directory' => (in_array($dirname, ['.', './', '/'])) ? '/' : trim($dirname, '/') . '/', - ]); - } -} diff --git a/app/Http/Controllers/Server/Files/RemoteRequestController.php b/app/Http/Controllers/Server/Files/RemoteRequestController.php deleted file mode 100644 index ab58037d..00000000 --- a/app/Http/Controllers/Server/Files/RemoteRequestController.php +++ /dev/null @@ -1,105 +0,0 @@ -config = $config; - $this->repository = $repository; - } - - /** - * Return a listing of a servers file directory. - * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\View\View - * - * @throws \Illuminate\Auth\Access\AuthorizationException - * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - */ - public function directory(Request $request): View - { - $server = $request->attributes->get('server'); - $this->authorize('list-files', $server); - - $requestDirectory = '/' . trim(urldecode($request->input('directory', '/')), '/'); - $directory = [ - 'header' => $requestDirectory !== '/' ? $requestDirectory : '', - 'first' => $requestDirectory !== '/', - ]; - - $goBack = explode('/', trim($requestDirectory, '/')); - if (! empty(array_filter($goBack)) && count($goBack) >= 2) { - array_pop($goBack); - - $directory['show'] = true; - $directory['link'] = '/' . implode('/', $goBack); - $directory['link_show'] = implode('/', $goBack) . '/'; - } - - try { - $listing = $this->repository->setServer($server)->setToken($request->attributes->get('server_token'))->getDirectory($requestDirectory); - } catch (RequestException $exception) { - throw new DaemonConnectionException($exception, true); - } - - return view('server.files.list', [ - 'files' => $listing['files'], - 'folders' => $listing['folders'], - 'editableMime' => $this->config->get('pterodactyl.files.editable'), - 'directory' => $directory, - ]); - } - - /** - * Put the contents of a file onto the daemon. - * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\Http\Response - * - * @throws \Illuminate\Auth\Access\AuthorizationException - * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException - */ - public function store(Request $request): Response - { - $server = $request->attributes->get('server'); - $this->authorize('save-files', $server); - - try { - $this->repository->setServer($server)->setToken($request->attributes->get('server_token')) - ->putContent($request->input('file'), $request->input('contents') ?? ''); - - return response('', 204); - } catch (RequestException $exception) { - throw new DaemonConnectionException($exception); - } - } -} diff --git a/app/Http/Controllers/Server/Settings/AllocationController.php b/app/Http/Controllers/Server/Settings/AllocationController.php deleted file mode 100644 index 21baf7c0..00000000 --- a/app/Http/Controllers/Server/Settings/AllocationController.php +++ /dev/null @@ -1,96 +0,0 @@ -defaultAllocationService = $defaultAllocationService; - $this->hashids = $hashids; - $this->repository = $repository; - } - - /** - * Render the allocation management overview page for a server. - * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\View\View - * - * @throws \Illuminate\Auth\Access\AuthorizationException - */ - public function index(Request $request): View - { - $server = $request->attributes->get('server'); - $this->authorize('view-allocations', $server); - $this->setRequest($request)->injectJavascript(); - - return view('server.settings.allocation', [ - 'allocations' => $this->repository->findWhere([['server_id', '=', $server->id]]), - ]); - } - - /** - * Update the default allocation for a server. - * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\Http\JsonResponse - * - * @throws \Illuminate\Auth\Access\AuthorizationException - * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - */ - public function update(Request $request): JsonResponse - { - $server = $request->attributes->get('server'); - $this->authorize('edit-allocation', $server); - - $allocation = $this->hashids->decodeFirst($request->input('allocation'), 0); - - try { - $this->defaultAllocationService->handle($server->id, $allocation); - } catch (AllocationDoesNotBelongToServerException $exception) { - return response()->json(['error' => 'No matching allocation was located for this server.'], 404); - } - - return response()->json(); - } -} diff --git a/app/Http/Controllers/Server/Settings/NameController.php b/app/Http/Controllers/Server/Settings/NameController.php deleted file mode 100644 index 29cdb9ed..00000000 --- a/app/Http/Controllers/Server/Settings/NameController.php +++ /dev/null @@ -1,59 +0,0 @@ -repository = $repository; - } - - /** - * @param \Illuminate\Http\Request $request - * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View - * @throws \Illuminate\Auth\Access\AuthorizationException - */ - public function index(Request $request) - { - $this->authorize('view-name', $request->attributes->get('server')); - $this->setRequest($request)->injectJavascript(); - - return view('server.settings.name'); - } - - /** - * Update the stored name for a specific server. - * - * @param \Pterodactyl\Http\Requests\Server\Settings\ChangeServerNameRequest $request - * @return \Illuminate\Http\RedirectResponse - * - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - */ - public function update(ChangeServerNameRequest $request): RedirectResponse - { - $this->repository->update($request->getServer()->id, $request->validated()); - - return redirect()->route('server.settings.name', $request->getServer()->uuidShort); - } -} diff --git a/app/Http/Controllers/Server/Settings/SftpController.php b/app/Http/Controllers/Server/Settings/SftpController.php deleted file mode 100644 index 58b110bd..00000000 --- a/app/Http/Controllers/Server/Settings/SftpController.php +++ /dev/null @@ -1,29 +0,0 @@ -authorize('access-sftp', $request->attributes->get('server')); - $this->setRequest($request)->injectJavascript(); - - return view('server.settings.sftp'); - } -} diff --git a/app/Http/Controllers/Server/Settings/StartupController.php b/app/Http/Controllers/Server/Settings/StartupController.php deleted file mode 100644 index 8f17022b..00000000 --- a/app/Http/Controllers/Server/Settings/StartupController.php +++ /dev/null @@ -1,96 +0,0 @@ -alert = $alert; - $this->commandViewService = $commandViewService; - $this->modificationService = $modificationService; - } - - /** - * Render the server startup page. - * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\View\View - * - * @throws \Illuminate\Auth\Access\AuthorizationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - */ - public function index(Request $request): View - { - $server = $request->attributes->get('server'); - $this->authorize('view-startup', $server); - $this->setRequest($request)->injectJavascript(); - - $data = $this->commandViewService->handle($server->id); - - return view('server.settings.startup', [ - 'variables' => $data->get('variables'), - 'server_values' => $data->get('server_values'), - 'startup' => $data->get('startup'), - ]); - } - - /** - * Handle request to update the startup variables for a server. Authorization - * is handled in the form request. - * - * @param \Pterodactyl\Http\Requests\Server\UpdateStartupParametersFormRequest $request - * @return \Illuminate\Http\RedirectResponse - * - * @throws \Pterodactyl\Exceptions\DisplayException - * @throws \Illuminate\Validation\ValidationException - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - */ - public function update(UpdateStartupParametersFormRequest $request): RedirectResponse - { - $this->modificationService->setUserLevel(User::USER_LEVEL_USER); - $this->modificationService->handle($request->attributes->get('server'), $request->normalize()); - $this->alert->success(trans('server.config.startup.edited'))->flash(); - - return redirect()->route('server.settings.startup', ['server' => $request->attributes->get('server')->uuidShort]); - } -} diff --git a/app/Http/Controllers/Server/SubuserController.php b/app/Http/Controllers/Server/SubuserController.php deleted file mode 100644 index 008cfeaa..00000000 --- a/app/Http/Controllers/Server/SubuserController.php +++ /dev/null @@ -1,197 +0,0 @@ -alert = $alert; - $this->repository = $repository; - $this->subuserCreationService = $subuserCreationService; - $this->subuserDeletionService = $subuserDeletionService; - $this->subuserUpdateService = $subuserUpdateService; - } - - /** - * Displays the subuser overview index. - * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\View\View - * - * @throws \Illuminate\Auth\Access\AuthorizationException - */ - public function index(Request $request): View - { - $server = $request->attributes->get('server'); - $this->authorize('list-subusers', $server); - $this->setRequest($request)->injectJavascript(); - - return view('server.users.index', [ - 'subusers' => $this->repository->findWhere([['server_id', '=', $server->id]]), - ]); - } - - /** - * Displays a single subuser overview. - * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\View\View - * - * @throws \Illuminate\Auth\Access\AuthorizationException - */ - public function view(Request $request): View - { - $server = $request->attributes->get('server'); - $this->authorize('view-subuser', $server); - - $subuser = $this->repository->getWithPermissions($request->attributes->get('subuser')); - $this->setRequest($request)->injectJavascript(); - - return view('server.users.view', [ - 'subuser' => $subuser, - 'permlist' => Permission::getPermissions(), - 'permissions' => $subuser->getRelation('permissions')->mapWithKeys(function ($item) { - return [$item->permission => true]; - }), - ]); - } - - /** - * Handles editing a subuser. - * - * @param \Pterodactyl\Http\Requests\Server\Subuser\SubuserUpdateFormRequest $request - * @param string $uuid - * @param string $hash - * @return \Illuminate\Http\RedirectResponse - * - * @throws \Illuminate\Auth\Access\AuthorizationException - * @throws \Pterodactyl\Exceptions\DisplayException - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - */ - public function update(SubuserUpdateFormRequest $request, string $uuid, string $hash): RedirectResponse - { - $this->subuserUpdateService->handle($request->attributes->get('subuser'), $request->input('permissions', [])); - $this->alert->success(trans('server.users.user_updated'))->flash(); - - return redirect()->route('server.subusers.view', ['uuid' => $uuid, 'subuser' => $hash]); - } - - /** - * Display new subuser creation page. - * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\View\View - * @throws \Illuminate\Auth\Access\AuthorizationException - */ - public function create(Request $request): View - { - $server = $request->attributes->get('server'); - $this->authorize('create-subuser', $server); - $this->setRequest($request)->injectJavascript(); - - return view('server.users.new', ['permissions' => Permission::getPermissions()]); - } - - /** - * Handles creating a new subuser. - * - * @param \Pterodactyl\Http\Requests\Server\Subuser\SubuserStoreFormRequest $request - * @return \Illuminate\Http\RedirectResponse - * - * @throws \Exception - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - * @throws \Pterodactyl\Exceptions\Service\Subuser\ServerSubuserExistsException - * @throws \Pterodactyl\Exceptions\Service\Subuser\UserIsServerOwnerException - */ - public function store(SubuserStoreFormRequest $request): RedirectResponse - { - $server = $request->attributes->get('server'); - - $subuser = $this->subuserCreationService->handle($server, $request->input('email'), $request->input('permissions', [])); - $this->alert->success(trans('server.users.user_assigned'))->flash(); - - return redirect()->route('server.subusers.view', [ - 'uuid' => $server->uuidShort, - 'id' => $subuser->hashid, - ]); - } - - /** - * Handles deleting a subuser. - * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\Http\Response - * - * @throws \Illuminate\Auth\Access\AuthorizationException - * @throws \Pterodactyl\Exceptions\DisplayException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - */ - public function delete(Request $request): Response - { - $server = $request->attributes->get('server'); - $this->authorize('delete-subuser', $server); - - $this->subuserDeletionService->handle($request->attributes->get('subuser')); - - return response('', 204); - } -} diff --git a/app/Http/Controllers/Server/Tasks/ActionController.php b/app/Http/Controllers/Server/Tasks/ActionController.php deleted file mode 100644 index 498db853..00000000 --- a/app/Http/Controllers/Server/Tasks/ActionController.php +++ /dev/null @@ -1,79 +0,0 @@ -processScheduleService = $processScheduleService; - $this->repository = $repository; - } - - /** - * Toggle a task to be active or inactive for a given server. - * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\Http\Response - * - * @throws \Illuminate\Auth\Access\AuthorizationException - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - */ - public function toggle(Request $request): Response - { - $server = $request->attributes->get('server'); - $schedule = $request->attributes->get('schedule'); - $this->authorize('toggle-schedule', $server); - - $this->repository->update($schedule->id, [ - 'is_active' => ! $schedule->is_active, - ]); - - return response('', 204); - } - - /** - * Trigger a schedule to run now. - * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\Http\Response - * - * @throws \Illuminate\Auth\Access\AuthorizationException - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - */ - public function trigger(Request $request): Response - { - $server = $request->attributes->get('server'); - $this->authorize('toggle-schedule', $server); - - $this->processScheduleService->handle( - $request->attributes->get('schedule') - ); - - return response('', 204); - } -} diff --git a/app/Http/Controllers/Server/Tasks/TaskManagementController.php b/app/Http/Controllers/Server/Tasks/TaskManagementController.php deleted file mode 100644 index 9805b5cf..00000000 --- a/app/Http/Controllers/Server/Tasks/TaskManagementController.php +++ /dev/null @@ -1,198 +0,0 @@ -alert = $alert; - $this->creationService = $creationService; - $this->hashids = $hashids; - $this->repository = $repository; - $this->updateService = $updateService; - } - - /** - * Display the task page listing. - * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\View\View - * - * @throws \Illuminate\Auth\Access\AuthorizationException - */ - public function index(Request $request): View - { - $server = $request->attributes->get('server'); - $this->authorize('list-schedules', $server); - $this->setRequest($request)->injectJavascript(); - - return view('server.schedules.index', [ - 'schedules' => $this->repository->findServerSchedules($server->id), - 'actions' => [ - 'command' => trans('server.schedule.actions.command'), - 'power' => trans('server.schedule.actions.power'), - ], - ]); - } - - /** - * Display the task creation page. - * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\View\View - * - * @throws \Illuminate\Auth\Access\AuthorizationException - */ - public function create(Request $request): View - { - $server = $request->attributes->get('server'); - $this->authorize('create-schedule', $server); - $this->setRequest($request)->injectJavascript(); - - return view('server.schedules.new'); - } - - /** - * Handle request to store a new schedule and tasks in the database. - * - * @param \Pterodactyl\Http\Requests\Server\ScheduleCreationFormRequest $request - * @return \Illuminate\Http\RedirectResponse - * - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Service\Schedule\Task\TaskIntervalTooLongException - */ - public function store(ScheduleCreationFormRequest $request): RedirectResponse - { - $server = $request->attributes->get('server'); - - $schedule = $this->creationService->handle($server, $request->normalize(), $request->getTasks()); - $this->alert->success(trans('server.schedule.schedule_created'))->flash(); - - return redirect()->route('server.schedules.view', [ - 'server' => $server->uuidShort, - 'schedule' => $schedule->hashid, - ]); - } - - /** - * Return a view to modify a schedule. - * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\View\View - * - * @throws \Illuminate\Auth\Access\AuthorizationException - */ - public function view(Request $request): View - { - $server = $request->attributes->get('server'); - $schedule = $request->attributes->get('schedule'); - $this->authorize('view-schedule', $server); - - $this->setRequest($request)->injectJavascript([ - 'tasks' => $schedule->getRelation('tasks')->map(function ($task) { - /* @var \Pterodactyl\Models\Task $task */ - return collect($task->toArray())->only('action', 'time_offset', 'payload')->all(); - }), - ]); - - return view('server.schedules.view', ['schedule' => $schedule]); - } - - /** - * Update a specific parent task on the system. - * - * @param \Pterodactyl\Http\Requests\Server\ScheduleCreationFormRequest $request - * @return \Illuminate\Http\RedirectResponse - * - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - * @throws \Pterodactyl\Exceptions\Service\Schedule\Task\TaskIntervalTooLongException - */ - public function update(ScheduleCreationFormRequest $request): RedirectResponse - { - $server = $request->attributes->get('server'); - $schedule = $request->attributes->get('schedule'); - - $this->updateService->handle($schedule, $request->normalize(), $request->getTasks()); - $this->alert->success(trans('server.schedule.schedule_updated'))->flash(); - - return redirect()->route('server.schedules.view', [ - 'server' => $server->uuidShort, - 'schedule' => $schedule->hashid, - ]); - } - - /** - * Delete a parent task from the Panel. - * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\Http\Response - * - * @throws \Illuminate\Auth\Access\AuthorizationException - */ - public function delete(Request $request): Response - { - $server = $request->attributes->get('server'); - $schedule = $request->attributes->get('schedule'); - $this->authorize('delete-schedule', $server); - - $this->repository->delete($schedule->id); - - return response('', 204); - } -} diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index 85c1190e..fa4f8a38 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -9,6 +9,7 @@ use Pterodactyl\Http\Middleware\TrimStrings; use Pterodactyl\Http\Middleware\TrustProxies; use Illuminate\Session\Middleware\StartSession; use Pterodactyl\Http\Middleware\EncryptCookies; +use Pterodactyl\Http\Middleware\Api\IsValidJson; use Pterodactyl\Http\Middleware\VerifyCsrfToken; use Pterodactyl\Http\Middleware\VerifyReCaptcha; use Pterodactyl\Http\Middleware\AdminAuthenticate; @@ -28,17 +29,12 @@ use Pterodactyl\Http\Middleware\Api\ApiSubstituteBindings; use Illuminate\Foundation\Http\Middleware\ValidatePostSize; use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse; use Pterodactyl\Http\Middleware\Server\AccessingValidServer; -use Pterodactyl\Http\Middleware\Server\AuthenticateAsSubuser; use Pterodactyl\Http\Middleware\Api\Daemon\DaemonAuthenticate; -use Pterodactyl\Http\Middleware\Server\SubuserBelongsToServer; use Pterodactyl\Http\Middleware\RequireTwoFactorAuthentication; -use Pterodactyl\Http\Middleware\Server\DatabaseBelongsToServer; -use Pterodactyl\Http\Middleware\Server\ScheduleBelongsToServer; use Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode; use Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull; use Pterodactyl\Http\Middleware\Api\Client\SubstituteClientApiBindings; use Pterodactyl\Http\Middleware\Api\Application\AuthenticateApplicationUser; -use Pterodactyl\Http\Middleware\DaemonAuthenticate as OldDaemonAuthenticate; class Kernel extends HttpKernel { @@ -49,6 +45,7 @@ class Kernel extends HttpKernel */ protected $middleware = [ CheckForMaintenanceMode::class, + EncryptCookies::class, ValidatePostSize::class, TrimStrings::class, ConvertEmptyStringsToNull::class, @@ -62,7 +59,6 @@ class Kernel extends HttpKernel */ protected $middlewareGroups = [ 'web' => [ - EncryptCookies::class, AddQueuedCookiesToResponse::class, StartSession::class, AuthenticateSession::class, @@ -73,7 +69,7 @@ class Kernel extends HttpKernel RequireTwoFactorAuthentication::class, ], 'api' => [ - 'throttle:120,1', + IsValidJson::class, ApiSubstituteBindings::class, SetSessionDriver::class, 'api..key:' . ApiKey::TYPE_APPLICATION, @@ -81,9 +77,11 @@ class Kernel extends HttpKernel AuthenticateIPAccess::class, ], 'client-api' => [ - 'throttle:60,1', - SubstituteClientApiBindings::class, + StartSession::class, SetSessionDriver::class, + AuthenticateSession::class, + IsValidJson::class, + SubstituteClientApiBindings::class, 'api..key:' . ApiKey::TYPE_ACCOUNT, AuthenticateIPAccess::class, ], @@ -103,9 +101,7 @@ class Kernel extends HttpKernel 'auth.basic' => AuthenticateWithBasicAuth::class, 'guest' => RedirectIfAuthenticated::class, 'server' => AccessingValidServer::class, - 'subuser.auth' => AuthenticateAsSubuser::class, 'admin' => AdminAuthenticate::class, - 'daemon-old' => OldDaemonAuthenticate::class, 'csrf' => VerifyCsrfToken::class, 'throttle' => ThrottleRequests::class, 'can' => Authorize::class, @@ -113,14 +109,6 @@ class Kernel extends HttpKernel 'recaptcha' => VerifyReCaptcha::class, 'node.maintenance' => MaintenanceMiddleware::class, - // Server specific middleware (used for authenticating access to resources) - // - // These are only used for individual server authentication, and not global - // actions from other resources. They are defined in the route files. - 'server..database' => DatabaseBelongsToServer::class, - 'server..subuser' => SubuserBelongsToServer::class, - 'server..schedule' => ScheduleBelongsToServer::class, - // API Specific Middleware 'api..key' => AuthenticateKey::class, ]; diff --git a/app/Http/Middleware/Admin/Servers/ServerInstalled.php b/app/Http/Middleware/Admin/Servers/ServerInstalled.php new file mode 100644 index 00000000..2f0a384f --- /dev/null +++ b/app/Http/Middleware/Admin/Servers/ServerInstalled.php @@ -0,0 +1,40 @@ +route()->parameter('server'); + + if (! $server instanceof Server) { + throw new NotFoundHttpException( + 'No server resource was located in the request parameters.' + ); + } + + if ($server->installed !== 1) { + throw new HttpException( + Response::HTTP_FORBIDDEN, 'Access to this resource is not allowed due to the current installation state.' + ); + } + + return $next($request); + } +} diff --git a/app/Http/Middleware/AdminAuthenticate.php b/app/Http/Middleware/AdminAuthenticate.php index 6307669c..878e56bb 100644 --- a/app/Http/Middleware/AdminAuthenticate.php +++ b/app/Http/Middleware/AdminAuthenticate.php @@ -19,7 +19,7 @@ class AdminAuthenticate * Handle an incoming request. * * @param \Illuminate\Http\Request $request - * @param \Closure $next + * @param \Closure $next * @return mixed * * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException diff --git a/app/Http/Middleware/Api/ApiSubstituteBindings.php b/app/Http/Middleware/Api/ApiSubstituteBindings.php index 94af9b1d..5ef3bca3 100644 --- a/app/Http/Middleware/Api/ApiSubstituteBindings.php +++ b/app/Http/Middleware/Api/ApiSubstituteBindings.php @@ -42,7 +42,7 @@ class ApiSubstituteBindings extends SubstituteBindings * a 404 error if a model is not found. * * @param \Illuminate\Http\Request $request - * @param \Closure $next + * @param \Closure $next * @return mixed */ public function handle($request, Closure $next) diff --git a/app/Http/Middleware/Api/Application/AuthenticateApplicationUser.php b/app/Http/Middleware/Api/Application/AuthenticateApplicationUser.php index ddcd2948..70c52f5b 100644 --- a/app/Http/Middleware/Api/Application/AuthenticateApplicationUser.php +++ b/app/Http/Middleware/Api/Application/AuthenticateApplicationUser.php @@ -13,7 +13,7 @@ class AuthenticateApplicationUser * and should be allowed to proceed through the application API. * * @param \Illuminate\Http\Request $request - * @param \Closure $next + * @param \Closure $next * @return mixed */ public function handle(Request $request, Closure $next) diff --git a/app/Http/Middleware/Api/AuthenticateIPAccess.php b/app/Http/Middleware/Api/AuthenticateIPAccess.php index aed8f53a..30377794 100644 --- a/app/Http/Middleware/Api/AuthenticateIPAccess.php +++ b/app/Http/Middleware/Api/AuthenticateIPAccess.php @@ -14,7 +14,7 @@ class AuthenticateIPAccess * Determine if a request IP has permission to access the API. * * @param \Illuminate\Http\Request $request - * @param \Closure $next + * @param \Closure $next * @return mixed * * @throws \Exception diff --git a/app/Http/Middleware/Api/AuthenticateKey.php b/app/Http/Middleware/Api/AuthenticateKey.php index 8f400bb4..515cc1fc 100644 --- a/app/Http/Middleware/Api/AuthenticateKey.php +++ b/app/Http/Middleware/Api/AuthenticateKey.php @@ -5,6 +5,7 @@ namespace Pterodactyl\Http\Middleware\Api; use Closure; use Cake\Chronos\Chronos; use Illuminate\Http\Request; +use Pterodactyl\Models\User; use Pterodactyl\Models\ApiKey; use Illuminate\Auth\AuthManager; use Illuminate\Contracts\Encryption\Encrypter; @@ -34,8 +35,8 @@ class AuthenticateKey * AuthenticateKey constructor. * * @param \Pterodactyl\Contracts\Repository\ApiKeyRepositoryInterface $repository - * @param \Illuminate\Auth\AuthManager $auth - * @param \Illuminate\Contracts\Encryption\Encrypter $encrypter + * @param \Illuminate\Auth\AuthManager $auth + * @param \Illuminate\Contracts\Encryption\Encrypter $encrypter */ public function __construct(ApiKeyRepositoryInterface $repository, AuthManager $auth, Encrypter $encrypter) { @@ -49,8 +50,8 @@ class AuthenticateKey * is in a valid format and exists in the database. * * @param \Illuminate\Http\Request $request - * @param \Closure $next - * @param int $keyType + * @param \Closure $next + * @param int $keyType * @return mixed * * @throws \Pterodactyl\Exceptions\Model\DataValidationException @@ -58,13 +59,43 @@ class AuthenticateKey */ public function handle(Request $request, Closure $next, int $keyType) { - if (is_null($request->bearerToken())) { + if (is_null($request->bearerToken()) && is_null($request->user())) { throw new HttpException(401, null, null, ['WWW-Authenticate' => 'Bearer']); } $raw = $request->bearerToken(); - $identifier = substr($raw, 0, ApiKey::IDENTIFIER_LENGTH); - $token = substr($raw, ApiKey::IDENTIFIER_LENGTH); + + // This is a request coming through using cookies, we have an authenticated user not using + // an API key. Make some fake API key models and continue on through the process. + if (empty($raw) && $request->user() instanceof User) { + $model = (new ApiKey())->forceFill([ + 'user_id' => $request->user()->id, + 'key_type' => ApiKey::TYPE_ACCOUNT, + ]); + } else { + $model = $this->authenticateApiKey($raw, $keyType); + $this->auth->guard()->loginUsingId($model->user_id); + } + + $request->attributes->set('api_key', $model); + + return $next($request); + } + + /** + * Authenticate an API key. + * + * @param string $key + * @param int $keyType + * @return \Pterodactyl\Models\ApiKey + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + protected function authenticateApiKey(string $key, int $keyType): ApiKey + { + $identifier = substr($key, 0, ApiKey::IDENTIFIER_LENGTH); + $token = substr($key, ApiKey::IDENTIFIER_LENGTH); try { $model = $this->repository->findFirstWhere([ @@ -79,10 +110,8 @@ class AuthenticateKey throw new AccessDeniedHttpException; } - $this->auth->guard()->loginUsingId($model->user_id); - $request->attributes->set('api_key', $model); $this->repository->withoutFreshModel()->update($model->id, ['last_used_at' => Chronos::now()]); - return $next($request); + return $model; } } diff --git a/app/Http/Middleware/Api/Client/AuthenticateClientAccess.php b/app/Http/Middleware/Api/Client/AuthenticateClientAccess.php deleted file mode 100644 index e048b586..00000000 --- a/app/Http/Middleware/Api/Client/AuthenticateClientAccess.php +++ /dev/null @@ -1,60 +0,0 @@ -keyProviderService = $keyProviderService; - } - - /** - * Authenticate that the currently authenticated user has permission - * to access the specified server. This only checks that the user is an - * admin, owner, or a subuser. You'll need to do more specific checks in - * the API calls to determine if they can perform different actions. - * - * @param \Illuminate\Http\Request $request - * @param \Closure $next - * @return mixed - * - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - */ - public function handle(Request $request, Closure $next) - { - if (is_null($request->user())) { - throw new AccessDeniedHttpException('A request must be made using an authenticated client.'); - } - - /** @var \Pterodactyl\Models\Server $server */ - $server = $request->route()->parameter('server'); - - try { - $token = $this->keyProviderService->handle($server, $request->user()); - } catch (RecordNotFoundException $exception) { - throw new NotFoundHttpException('The requested server could not be located.'); - } - - $request->attributes->set('server_token', $token); - - return $next($request); - } -} diff --git a/app/Http/Middleware/Api/Client/Server/AllocationBelongsToServer.php b/app/Http/Middleware/Api/Client/Server/AllocationBelongsToServer.php new file mode 100644 index 00000000..d027d563 --- /dev/null +++ b/app/Http/Middleware/Api/Client/Server/AllocationBelongsToServer.php @@ -0,0 +1,33 @@ +route()->parameter('server'); + /** @var \Pterodactyl\Models\Allocation|null $allocation */ + $allocation = $request->route()->parameter('allocation'); + + if ($allocation && $allocation->server_id !== $server->id) { + throw new NotFoundHttpException; + } + + return $next($request); + } +} diff --git a/app/Http/Middleware/Api/Client/Server/AuthenticateServerAccess.php b/app/Http/Middleware/Api/Client/Server/AuthenticateServerAccess.php new file mode 100644 index 00000000..505f1a30 --- /dev/null +++ b/app/Http/Middleware/Api/Client/Server/AuthenticateServerAccess.php @@ -0,0 +1,86 @@ +repository = $repository; + } + + /** + * Authenticate that this server exists and is not suspended or marked as installing. + * + * @param \Illuminate\Http\Request $request + * @param \Closure $next + * @return mixed + */ + public function handle(Request $request, Closure $next) + { + /** @var \Pterodactyl\Models\User $user */ + $user = $request->user(); + $server = $request->route()->parameter('server'); + + if (! $server instanceof Server) { + throw new NotFoundHttpException(trans('exceptions.api.resource_not_found')); + } + + // At the very least, ensure that the user trying to make this request is the + // server owner, a subuser, or a root admin. We'll leave it up to the controllers + // to authenticate more detailed permissions if needed. + if ($user->id !== $server->owner_id && ! $user->root_admin) { + // Check for subuser status. + if (! $server->subusers->contains('user_id', $user->id)) { + throw new NotFoundHttpException(trans('exceptions.api.resource_not_found')); + } + } + + if ($server->suspended && !$request->routeIs('api:client:server.resources')) { + throw new BadRequestHttpException( + 'This server is currently suspended and the functionality requested is unavailable.' + ); + } + + if (! $server->isInstalled()) { + // Throw an exception for all server routes; however if the user is an admin and requesting the + // server details, don't throw the exception for them. + if (! $user->root_admin || ($user->root_admin && ! $request->routeIs($this->except))) { + throw new ConflictHttpException('Server has not completed the installation process.'); + } + } + + $request->attributes->set('server', $server); + + return $next($request); + } +} diff --git a/app/Http/Middleware/Api/Client/Server/SubuserBelongsToServer.php b/app/Http/Middleware/Api/Client/Server/SubuserBelongsToServer.php new file mode 100644 index 00000000..a80f6eef --- /dev/null +++ b/app/Http/Middleware/Api/Client/Server/SubuserBelongsToServer.php @@ -0,0 +1,36 @@ +route()->parameter('server'); + /** @var \Pterodactyl\Models\User $user */ + $user = $request->route()->parameter('user'); + + // Don't do anything if there isn't a user present in the request. + if (is_null($user)) { + return $next($request); + } + + $request->attributes->set('subuser', $server->subusers()->where('user_id', $user->id)->firstOrFail()); + + return $next($request); + } +} diff --git a/app/Http/Middleware/Api/Client/SubstituteClientApiBindings.php b/app/Http/Middleware/Api/Client/SubstituteClientApiBindings.php index f8a35fdd..7ab597b6 100644 --- a/app/Http/Middleware/Api/Client/SubstituteClientApiBindings.php +++ b/app/Http/Middleware/Api/Client/SubstituteClientApiBindings.php @@ -3,7 +3,11 @@ namespace Pterodactyl\Http\Middleware\Api\Client; use Closure; +use Pterodactyl\Models\User; +use Pterodactyl\Models\Backup; +use Pterodactyl\Models\Database; use Illuminate\Container\Container; +use Pterodactyl\Contracts\Extensions\HashidsInterface; use Pterodactyl\Http\Middleware\Api\ApiSubstituteBindings; use Pterodactyl\Exceptions\Repository\RecordNotFoundException; use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; @@ -15,7 +19,7 @@ class SubstituteClientApiBindings extends ApiSubstituteBindings * a 404 error if a model is not found. * * @param \Illuminate\Http\Request $request - * @param \Closure $next + * @param \Closure $next * @return mixed */ public function handle($request, Closure $next) @@ -24,8 +28,13 @@ class SubstituteClientApiBindings extends ApiSubstituteBindings // column rather than the default 'id'. $this->router->bind('server', function ($value) use ($request) { try { + $column = 'uuidShort'; + if (preg_match('/^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i', $value)) { + $column = 'uuid'; + } + return Container::getInstance()->make(ServerRepositoryInterface::class)->findFirstWhere([ - ['uuidShort', '=', $value], + [$column, '=', $value], ]); } catch (RecordNotFoundException $ex) { $request->attributes->set('is_missing_model', true); @@ -34,6 +43,20 @@ class SubstituteClientApiBindings extends ApiSubstituteBindings } }); + $this->router->bind('database', function ($value) use ($request) { + $id = Container::getInstance()->make(HashidsInterface::class)->decodeFirst($value); + + return Database::query()->where('id', $id)->firstOrFail(); + }); + + $this->router->bind('backup', function ($value) { + return Backup::query()->where('uuid', $value)->firstOrFail(); + }); + + $this->router->bind('user', function ($value) { + return User::query()->where('uuid', $value)->firstOrFail(); + }); + return parent::handle($request, $next); } } diff --git a/app/Http/Middleware/Api/Daemon/DaemonAuthenticate.php b/app/Http/Middleware/Api/Daemon/DaemonAuthenticate.php index c951b1b4..bc365e63 100644 --- a/app/Http/Middleware/Api/Daemon/DaemonAuthenticate.php +++ b/app/Http/Middleware/Api/Daemon/DaemonAuthenticate.php @@ -4,18 +4,25 @@ namespace Pterodactyl\Http\Middleware\Api\Daemon; use Closure; use Illuminate\Http\Request; +use Illuminate\Contracts\Encryption\Encrypter; +use Pterodactyl\Repositories\Eloquent\NodeRepository; use Symfony\Component\HttpKernel\Exception\HttpException; -use Pterodactyl\Contracts\Repository\NodeRepositoryInterface; use Pterodactyl\Exceptions\Repository\RecordNotFoundException; +use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; class DaemonAuthenticate { /** - * @var \Pterodactyl\Contracts\Repository\NodeRepositoryInterface + * @var \Pterodactyl\Repositories\Eloquent\NodeRepository */ private $repository; + /** + * @var \Illuminate\Contracts\Encryption\Encrypter + */ + private $encrypter; + /** * Daemon routes that this middleware should be skipped on. * @@ -28,18 +35,20 @@ class DaemonAuthenticate /** * DaemonAuthenticate constructor. * - * @param \Pterodactyl\Contracts\Repository\NodeRepositoryInterface $repository + * @param \Illuminate\Contracts\Encryption\Encrypter $encrypter + * @param \Pterodactyl\Repositories\Eloquent\NodeRepository $repository */ - public function __construct(NodeRepositoryInterface $repository) + public function __construct(Encrypter $encrypter, NodeRepository $repository) { $this->repository = $repository; + $this->encrypter = $encrypter; } /** * Check if a request from the daemon can be properly attributed back to a single node instance. * * @param \Illuminate\Http\Request $request - * @param \Closure $next + * @param \Closure $next * @return mixed * * @throws \Symfony\Component\HttpKernel\Exception\HttpException @@ -50,20 +59,37 @@ class DaemonAuthenticate return $next($request); } - $token = $request->bearerToken(); + if (is_null($bearer = $request->bearerToken())) { + throw new HttpException( + 401, 'Access this this endpoint must include an Authorization header.', null, ['WWW-Authenticate' => 'Bearer'] + ); + } - if (is_null($token)) { - throw new HttpException(401, null, null, ['WWW-Authenticate' => 'Bearer']); + $parts = explode('.', $bearer); + // Ensure that all of the correct parts are provided in the header. + if (count($parts) !== 2 || empty($parts[0]) || empty($parts[1])) { + throw new BadRequestHttpException( + 'The Authorization headed provided was not in a valid format.' + ); } try { - $node = $this->repository->findFirstWhere([['daemonSecret', '=', $token]]); + /** @var \Pterodactyl\Models\Node $node */ + $node = $this->repository->findFirstWhere([ + 'daemon_token_id' => $parts[0], + ]); + + if (hash_equals((string) $this->encrypter->decrypt($node->daemon_token), $parts[1])) { + $request->attributes->set('node', $node); + + return $next($request); + } } catch (RecordNotFoundException $exception) { - throw new AccessDeniedHttpException; + // Do nothing, we don't want to expose a node not existing at all. } - $request->attributes->set('node', $node); - - return $next($request); + throw new AccessDeniedHttpException( + 'You are not authorized to access this resource.' + ); } } diff --git a/app/Http/Middleware/Api/IsValidJson.php b/app/Http/Middleware/Api/IsValidJson.php new file mode 100644 index 00000000..20c54dab --- /dev/null +++ b/app/Http/Middleware/Api/IsValidJson.php @@ -0,0 +1,38 @@ +isJson() && ! empty($request->getContent())) { + json_decode($request->getContent(), true); + + if (json_last_error() !== JSON_ERROR_NONE) { + throw new BadRequestHttpException( + sprintf( + 'The JSON data passed in the request appears to be malformed. err_code: %d err_message: "%s"', + json_last_error(), + json_last_error_msg() + ) + ); + } + } + + return $next($request); + } +} diff --git a/app/Http/Middleware/Api/SetSessionDriver.php b/app/Http/Middleware/Api/SetSessionDriver.php index 3d5c1661..a04db7fa 100644 --- a/app/Http/Middleware/Api/SetSessionDriver.php +++ b/app/Http/Middleware/Api/SetSessionDriver.php @@ -4,17 +4,10 @@ namespace Pterodactyl\Http\Middleware\Api; use Closure; use Illuminate\Http\Request; -use Barryvdh\Debugbar\LaravelDebugbar; -use Illuminate\Contracts\Foundation\Application; use Illuminate\Contracts\Config\Repository as ConfigRepository; class SetSessionDriver { - /** - * @var \Illuminate\Contracts\Foundation\Application - */ - private $app; - /** * @var \Illuminate\Contracts\Config\Repository */ @@ -23,12 +16,10 @@ class SetSessionDriver /** * SetSessionDriver constructor. * - * @param \Illuminate\Contracts\Foundation\Application $app - * @param \Illuminate\Contracts\Config\Repository $config + * @param \Illuminate\Contracts\Config\Repository $config */ - public function __construct(Application $app, ConfigRepository $config) + public function __construct(ConfigRepository $config) { - $this->app = $app; $this->config = $config; } @@ -36,15 +27,11 @@ class SetSessionDriver * Set the session for API calls to only last for the one request. * * @param \Illuminate\Http\Request $request - * @param \Closure $next + * @param \Closure $next * @return mixed */ public function handle(Request $request, Closure $next) { - if ($this->config->get('app.debug')) { - $this->app->make(LaravelDebugbar::class)->disable(); - } - $this->config->set('session.driver', 'array'); return $next($request); diff --git a/app/Http/Middleware/Authenticate.php b/app/Http/Middleware/Authenticate.php index d85cf95b..ed8d31e0 100644 --- a/app/Http/Middleware/Authenticate.php +++ b/app/Http/Middleware/Authenticate.php @@ -12,7 +12,7 @@ class Authenticate * Handle an incoming request. * * @param \Illuminate\Http\Request $request - * @param \Closure $next + * @param \Closure $next * @return mixed * * @throws \Illuminate\Auth\AuthenticationException diff --git a/app/Http/Middleware/DaemonAuthenticate.php b/app/Http/Middleware/DaemonAuthenticate.php deleted file mode 100644 index b8e83bf8..00000000 --- a/app/Http/Middleware/DaemonAuthenticate.php +++ /dev/null @@ -1,69 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -namespace Pterodactyl\Http\Middleware; - -use Closure; -use Illuminate\Http\Request; -use Pterodactyl\Contracts\Repository\NodeRepositoryInterface; -use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; - -class DaemonAuthenticate -{ - /** - * An array of route names to not apply this middleware to. - * - * @var array - */ - private $except = [ - 'daemon.configuration', - ]; - - /** - * @var \Pterodactyl\Contracts\Repository\NodeRepositoryInterface - */ - private $repository; - - /** - * Create a new filter instance. - * - * @param \Pterodactyl\Contracts\Repository\NodeRepositoryInterface $repository - * @deprecated - */ - public function __construct(NodeRepositoryInterface $repository) - { - $this->repository = $repository; - } - - /** - * Handle an incoming request. - * - * @param \Illuminate\Http\Request $request - * @param \Closure $next - * @return mixed - * - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException - */ - public function handle(Request $request, Closure $next) - { - if (in_array($request->route()->getName(), $this->except)) { - return $next($request); - } - - if (! $request->header('X-Access-Node')) { - throw new AccessDeniedHttpException; - } - - $node = $this->repository->findFirstWhere(['daemonSecret' => $request->header('X-Access-Node')]); - $request->attributes->set('node', $node); - - return $next($request); - } -} diff --git a/app/Http/Middleware/LanguageMiddleware.php b/app/Http/Middleware/LanguageMiddleware.php index 65692d3e..3dba67e2 100644 --- a/app/Http/Middleware/LanguageMiddleware.php +++ b/app/Http/Middleware/LanguageMiddleware.php @@ -27,7 +27,7 @@ class LanguageMiddleware * Handle an incoming request and set the user's preferred language. * * @param \Illuminate\Http\Request $request - * @param \Closure $next + * @param \Closure $next * @return mixed */ public function handle(Request $request, Closure $next) diff --git a/app/Http/Middleware/MaintenanceMiddleware.php b/app/Http/Middleware/MaintenanceMiddleware.php index c67a3f05..8f733bcd 100644 --- a/app/Http/Middleware/MaintenanceMiddleware.php +++ b/app/Http/Middleware/MaintenanceMiddleware.php @@ -26,7 +26,7 @@ class MaintenanceMiddleware * Handle an incoming request. * * @param \Illuminate\Http\Request $request - * @param \Closure $next + * @param \Closure $next * @return mixed */ public function handle($request, Closure $next) diff --git a/app/Http/Middleware/RedirectIfAuthenticated.php b/app/Http/Middleware/RedirectIfAuthenticated.php index 8a5220cb..a8c51bb0 100644 --- a/app/Http/Middleware/RedirectIfAuthenticated.php +++ b/app/Http/Middleware/RedirectIfAuthenticated.php @@ -27,8 +27,8 @@ class RedirectIfAuthenticated * Handle an incoming request. * * @param \Illuminate\Http\Request $request - * @param \Closure $next - * @param string|null $guard + * @param \Closure $next + * @param string|null $guard * @return mixed */ public function handle(Request $request, Closure $next, string $guard = null) diff --git a/app/Http/Middleware/RequireTwoFactorAuthentication.php b/app/Http/Middleware/RequireTwoFactorAuthentication.php index aee1cf06..0689cc14 100644 --- a/app/Http/Middleware/RequireTwoFactorAuthentication.php +++ b/app/Http/Middleware/RequireTwoFactorAuthentication.php @@ -10,6 +10,7 @@ namespace Pterodactyl\Http\Middleware; use Closure; +use Illuminate\Support\Str; use Illuminate\Http\Request; use Prologue\Alerts\AlertsMessageBag; @@ -24,27 +25,12 @@ class RequireTwoFactorAuthentication */ private $alert; - /** - * The names of routes that should be accessible without 2FA enabled. - * - * @var array - */ - protected $except = [ - 'account.security', - 'account.security.revoke', - 'account.security.totp', - 'account.security.totp.set', - 'account.security.totp.disable', - 'auth.totp', - 'auth.logout', - ]; - /** * The route to redirect a user to to enable 2FA. * * @var string */ - protected $redirectRoute = 'account.security'; + protected $redirectRoute = 'account'; /** * RequireTwoFactorAuthentication constructor. @@ -60,7 +46,7 @@ class RequireTwoFactorAuthentication * Handle an incoming request. * * @param \Illuminate\Http\Request $request - * @param \Closure $next + * @param \Closure $next * @return mixed */ public function handle(Request $request, Closure $next) @@ -69,7 +55,8 @@ class RequireTwoFactorAuthentication return $next($request); } - if (in_array($request->route()->getName(), $this->except)) { + $current = $request->route()->getName(); + if (in_array($current, ['auth', 'account']) || Str::startsWith($current, ['auth.', 'account.'])) { return $next($request); } diff --git a/app/Http/Middleware/Server/AccessingValidServer.php b/app/Http/Middleware/Server/AccessingValidServer.php index ea29e0b9..1f464f7d 100644 --- a/app/Http/Middleware/Server/AccessingValidServer.php +++ b/app/Http/Middleware/Server/AccessingValidServer.php @@ -5,7 +5,6 @@ namespace Pterodactyl\Http\Middleware\Server; use Closure; use Illuminate\Http\Request; use Pterodactyl\Models\Server; -use Illuminate\Contracts\Session\Session; use Illuminate\Contracts\Routing\ResponseFactory; use Illuminate\Contracts\Config\Repository as ConfigRepository; use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; @@ -29,39 +28,30 @@ class AccessingValidServer */ private $response; - /** - * @var \Illuminate\Contracts\Session\Session - */ - private $session; - /** * AccessingValidServer constructor. * - * @param \Illuminate\Contracts\Config\Repository $config - * @param \Illuminate\Contracts\Routing\ResponseFactory $response + * @param \Illuminate\Contracts\Config\Repository $config + * @param \Illuminate\Contracts\Routing\ResponseFactory $response * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository - * @param \Illuminate\Contracts\Session\Session $session */ public function __construct( ConfigRepository $config, ResponseFactory $response, - ServerRepositoryInterface $repository, - Session $session + ServerRepositoryInterface $repository ) { $this->config = $config; $this->repository = $repository; $this->response = $response; - $this->session = $session; } /** * Determine if a given user has permission to access a server. * * @param \Illuminate\Http\Request $request - * @param \Closure $next + * @param \Closure $next * @return \Illuminate\Http\Response|mixed * - * @throws \Illuminate\Auth\AuthenticationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException @@ -90,10 +80,6 @@ class AccessingValidServer return $this->response->view('errors.installing', [], 409); } - // Store the server in the session. - // @todo remove from session. use request attributes. - $this->session->now('server_data.model', $server); - // Add server to the request attributes. This will replace sessions // as files are updated. $request->attributes->set('server', $server); diff --git a/app/Http/Middleware/Server/AuthenticateAsSubuser.php b/app/Http/Middleware/Server/AuthenticateAsSubuser.php deleted file mode 100644 index ebf9cd62..00000000 --- a/app/Http/Middleware/Server/AuthenticateAsSubuser.php +++ /dev/null @@ -1,59 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -namespace Pterodactyl\Http\Middleware\Server; - -use Closure; -use Illuminate\Http\Request; -use Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService; -use Pterodactyl\Exceptions\Repository\RecordNotFoundException; -use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; - -class AuthenticateAsSubuser -{ - /** - * @var \Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService - */ - private $keyProviderService; - - /** - * SubuserAccessAuthenticate constructor. - * - * @param \Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService $keyProviderService - */ - public function __construct(DaemonKeyProviderService $keyProviderService) - { - $this->keyProviderService = $keyProviderService; - } - - /** - * Determine if a subuser has permissions to access a server, if so set their access token. - * - * @param \Illuminate\Http\Request $request - * @param \Closure $next - * @return mixed - * - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException - */ - public function handle(Request $request, Closure $next) - { - $server = $request->attributes->get('server'); - - try { - $token = $this->keyProviderService->handle($server, $request->user()); - } catch (RecordNotFoundException $exception) { - throw new AccessDeniedHttpException('This account does not have permission to access this server.'); - } - - $request->attributes->set('server_token', $token); - - return $next($request); - } -} diff --git a/app/Http/Middleware/Server/DatabaseBelongsToServer.php b/app/Http/Middleware/Server/DatabaseBelongsToServer.php deleted file mode 100644 index 1617a852..00000000 --- a/app/Http/Middleware/Server/DatabaseBelongsToServer.php +++ /dev/null @@ -1,56 +0,0 @@ -repository = $repository; - } - - /** - * Check if a database being requested belongs to the currently loaded server. - * If it does not, throw a 404 error, otherwise continue on with the request - * and set an attribute with the database. - * - * @param \Illuminate\Http\Request $request - * @param \Closure $next - * @return mixed - * - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - */ - public function handle(Request $request, Closure $next) - { - $server = $request->attributes->get('server'); - $database = $request->input('database') ?? $request->route()->parameter('database'); - - if (! is_digit($database)) { - throw new NotFoundHttpException; - } - - $database = $this->repository->find($database); - if (is_null($database) || $database->server_id !== $server->id) { - throw new NotFoundHttpException; - } - - $request->attributes->set('database', $database); - - return $next($request); - } -} diff --git a/app/Http/Middleware/Server/ScheduleBelongsToServer.php b/app/Http/Middleware/Server/ScheduleBelongsToServer.php deleted file mode 100644 index 26da5f84..00000000 --- a/app/Http/Middleware/Server/ScheduleBelongsToServer.php +++ /dev/null @@ -1,60 +0,0 @@ -hashids = $hashids; - $this->repository = $repository; - } - - /** - * Determine if a task is assigned to the active server. - * - * @param \Illuminate\Http\Request $request - * @param \Closure $next - * @return mixed - * - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException - */ - public function handle(Request $request, Closure $next) - { - $server = $request->attributes->get('server'); - - $scheduleId = $this->hashids->decodeFirst($request->route()->parameter('schedule'), 0); - $schedule = $this->repository->getScheduleWithTasks($scheduleId); - - if ($schedule->server_id !== $server->id) { - throw new NotFoundHttpException; - } - - $request->attributes->set('schedule', $schedule); - - return $next($request); - } -} diff --git a/app/Http/Middleware/Server/SubuserBelongsToServer.php b/app/Http/Middleware/Server/SubuserBelongsToServer.php deleted file mode 100644 index cdcd3f09..00000000 --- a/app/Http/Middleware/Server/SubuserBelongsToServer.php +++ /dev/null @@ -1,67 +0,0 @@ -hashids = $hashids; - $this->repository = $repository; - } - - /** - * Determine if a user has permission to access and modify subuser. - * - * @param \Illuminate\Http\Request $request - * @param \Closure $next - * @return mixed - * - * @throws \Pterodactyl\Exceptions\DisplayException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException - */ - public function handle(Request $request, Closure $next) - { - $server = $request->attributes->get('server'); - - $hash = $request->route()->parameter('subuser', 0); - $subuser = $this->repository->find($this->hashids->decodeFirst($hash, 0)); - if (is_null($subuser) || $subuser->server_id !== $server->id) { - throw new NotFoundHttpException; - } - - if ($request->method() === 'PATCH') { - if ($subuser->user_id === $request->user()->id) { - throw new DisplayException(trans('exceptions.subusers.editing_self')); - } - } - - $request->attributes->set('subuser', $subuser); - - return $next($request); - } -} diff --git a/app/Http/Middleware/VerifyReCaptcha.php b/app/Http/Middleware/VerifyReCaptcha.php index 7464e854..71036047 100644 --- a/app/Http/Middleware/VerifyReCaptcha.php +++ b/app/Http/Middleware/VerifyReCaptcha.php @@ -6,8 +6,11 @@ use Closure; use stdClass; use GuzzleHttp\Client; use Illuminate\Http\Request; +use Illuminate\Http\Response; use Pterodactyl\Events\Auth\FailedCaptcha; use Illuminate\Contracts\Config\Repository; +use Illuminate\Contracts\Events\Dispatcher; +use Symfony\Component\HttpKernel\Exception\HttpException; class VerifyReCaptcha { @@ -16,21 +19,28 @@ class VerifyReCaptcha */ private $config; + /** + * @var \Illuminate\Contracts\Events\Dispatcher + */ + private $dispatcher; + /** * VerifyReCaptcha constructor. * + * @param \Illuminate\Contracts\Events\Dispatcher $dispatcher * @param \Illuminate\Contracts\Config\Repository $config */ - public function __construct(Repository $config) + public function __construct(Dispatcher $dispatcher, Repository $config) { $this->config = $config; + $this->dispatcher = $dispatcher; } /** * Handle an incoming request. * * @param \Illuminate\Http\Request $request - * @param \Closure $next + * @param \Closure $next * @return \Illuminate\Http\RedirectResponse|mixed */ public function handle($request, Closure $next) @@ -57,16 +67,21 @@ class VerifyReCaptcha } } - // Emit an event and return to the previous view with an error (only the captcha error will be shown!) - event(new FailedCaptcha($request->ip(), (! isset($result) ?: object_get($result, 'hostname')))); + $this->dispatcher->dispatch( + new FailedCaptcha( + $request->ip(), ! empty($result) ? ($result->hostname ?? null) : null + ) + ); - return redirect()->back()->withErrors(['g-recaptcha-response' => trans('strings.captcha_invalid')])->withInput(); + throw new HttpException( + Response::HTTP_BAD_REQUEST, 'Failed to validate reCAPTCHA data.' + ); } /** * Determine if the response from the recaptcha servers was valid. * - * @param stdClass $result + * @param stdClass $result * @param \Illuminate\Http\Request $request * @return bool */ diff --git a/app/Http/Requests/Admin/Api/StoreApplicationApiKeyRequest.php b/app/Http/Requests/Admin/Api/StoreApplicationApiKeyRequest.php index 2549517c..a2949628 100644 --- a/app/Http/Requests/Admin/Api/StoreApplicationApiKeyRequest.php +++ b/app/Http/Requests/Admin/Api/StoreApplicationApiKeyRequest.php @@ -15,7 +15,7 @@ class StoreApplicationApiKeyRequest extends AdminFormRequest */ public function rules() { - $modelRules = ApiKey::getCreateRules(); + $modelRules = ApiKey::getRules(); return collect(AdminAcl::getResourceList())->mapWithKeys(function ($resource) use ($modelRules) { return [AdminAcl::COLUMN_IDENTIFIER . $resource => $modelRules['r_' . $resource]]; diff --git a/app/Http/Requests/Admin/DatabaseHostFormRequest.php b/app/Http/Requests/Admin/DatabaseHostFormRequest.php index a7b0b4af..c6b2468a 100644 --- a/app/Http/Requests/Admin/DatabaseHostFormRequest.php +++ b/app/Http/Requests/Admin/DatabaseHostFormRequest.php @@ -12,10 +12,10 @@ class DatabaseHostFormRequest extends AdminFormRequest public function rules() { if ($this->method() !== 'POST') { - return DatabaseHost::getUpdateRulesForId($this->route()->parameter('host')); + return DatabaseHost::getRulesForUpdate($this->route()->parameter('host')); } - return DatabaseHost::getCreateRules(); + return DatabaseHost::getRules(); } /** @@ -29,10 +29,6 @@ class DatabaseHostFormRequest extends AdminFormRequest $this->merge(['node_id' => null]); } - $this->merge([ - 'host' => gethostbyname($this->input('host')), - ]); - return parent::getValidatorInstance(); } } diff --git a/app/Http/Requests/Admin/Egg/EggFormRequest.php b/app/Http/Requests/Admin/Egg/EggFormRequest.php index 539ee3ad..bda0e8c4 100644 --- a/app/Http/Requests/Admin/Egg/EggFormRequest.php +++ b/app/Http/Requests/Admin/Egg/EggFormRequest.php @@ -19,12 +19,12 @@ class EggFormRequest extends AdminFormRequest public function rules() { $rules = [ - 'name' => 'required|string|max:255', - 'description' => 'required|string', - 'docker_image' => 'required|string|max:255', + 'name' => 'required|string|max:191', + 'description' => 'nullable|string', + 'docker_image' => 'required|string|max:191', 'startup' => 'required|string', 'config_from' => 'sometimes|bail|nullable|numeric', - 'config_stop' => 'required_without:config_from|nullable|string|max:255', + 'config_stop' => 'required_without:config_from|nullable|string|max:191', 'config_startup' => 'required_without:config_from|nullable|json', 'config_logs' => 'required_without:config_from|nullable|json', 'config_files' => 'required_without:config_from|nullable|json', diff --git a/app/Http/Requests/Admin/Egg/EggVariableFormRequest.php b/app/Http/Requests/Admin/Egg/EggVariableFormRequest.php index 933bf834..d52fe94d 100644 --- a/app/Http/Requests/Admin/Egg/EggVariableFormRequest.php +++ b/app/Http/Requests/Admin/Egg/EggVariableFormRequest.php @@ -15,9 +15,9 @@ class EggVariableFormRequest extends AdminFormRequest public function rules() { return [ - 'name' => 'required|string|min:1|max:255', + 'name' => 'required|string|min:1|max:191', 'description' => 'sometimes|nullable|string', - 'env_variable' => 'required|regex:/^[\w]{1,255}$/|notIn:' . EggVariable::RESERVED_ENV_NAMES, + 'env_variable' => 'required|regex:/^[\w]{1,191}$/|notIn:' . EggVariable::RESERVED_ENV_NAMES, 'options' => 'sometimes|required|array', 'rules' => 'bail|required|string', 'default_value' => 'present', diff --git a/app/Http/Requests/Admin/LocationFormRequest.php b/app/Http/Requests/Admin/LocationFormRequest.php index 16d80a25..2ad202f9 100644 --- a/app/Http/Requests/Admin/LocationFormRequest.php +++ b/app/Http/Requests/Admin/LocationFormRequest.php @@ -21,9 +21,9 @@ class LocationFormRequest extends AdminFormRequest public function rules() { if ($this->method() === 'PATCH') { - return Location::getUpdateRulesForId($this->route()->parameter('location')->id); + return Location::getRulesForUpdate($this->route()->parameter('location')->id); } - return Location::getCreateRules(); + return Location::getRules(); } } diff --git a/app/Http/Requests/Admin/MountFormRequest.php b/app/Http/Requests/Admin/MountFormRequest.php new file mode 100644 index 00000000..bd94a633 --- /dev/null +++ b/app/Http/Requests/Admin/MountFormRequest.php @@ -0,0 +1,22 @@ +method() === 'PATCH') { + return Mount::getRulesForUpdate($this->route()->parameter('mount')->id); + } + + return Mount::getRules(); + } +} diff --git a/app/Http/Requests/Admin/Nest/StoreNestFormRequest.php b/app/Http/Requests/Admin/Nest/StoreNestFormRequest.php index 56255e55..2f01dfe9 100644 --- a/app/Http/Requests/Admin/Nest/StoreNestFormRequest.php +++ b/app/Http/Requests/Admin/Nest/StoreNestFormRequest.php @@ -19,8 +19,8 @@ class StoreNestFormRequest extends AdminFormRequest public function rules() { return [ - 'name' => 'required|string|min:1|max:255', - 'description' => 'required|nullable|string', + 'name' => 'required|string|min:1|max:191', + 'description' => 'string|nullable', ]; } } diff --git a/app/Http/Requests/Admin/Node/AllocationFormRequest.php b/app/Http/Requests/Admin/Node/AllocationFormRequest.php index 777d3033..3c580c02 100644 --- a/app/Http/Requests/Admin/Node/AllocationFormRequest.php +++ b/app/Http/Requests/Admin/Node/AllocationFormRequest.php @@ -20,7 +20,7 @@ class AllocationFormRequest extends AdminFormRequest { return [ 'allocation_ip' => 'required|string', - 'allocation_alias' => 'sometimes|nullable|string|max:255', + 'allocation_alias' => 'sometimes|nullable|string|max:191', 'allocation_ports' => 'required|array', ]; } diff --git a/app/Http/Requests/Admin/Node/NodeFormRequest.php b/app/Http/Requests/Admin/Node/NodeFormRequest.php index fcedd427..caef04f9 100644 --- a/app/Http/Requests/Admin/Node/NodeFormRequest.php +++ b/app/Http/Requests/Admin/Node/NodeFormRequest.php @@ -20,10 +20,10 @@ class NodeFormRequest extends AdminFormRequest public function rules() { if ($this->method() === 'PATCH') { - return Node::getUpdateRulesForId($this->route()->parameter('node')->id); + return Node::getRulesForUpdate($this->route()->parameter('node')); } - return Node::getCreateRules(); + return Node::getRules(); } /** diff --git a/app/Http/Requests/Admin/PackFormRequest.php b/app/Http/Requests/Admin/PackFormRequest.php deleted file mode 100644 index 7f15be95..00000000 --- a/app/Http/Requests/Admin/PackFormRequest.php +++ /dev/null @@ -1,49 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -namespace Pterodactyl\Http\Requests\Admin; - -use Pterodactyl\Models\Pack; -use Pterodactyl\Services\Packs\PackCreationService; - -class PackFormRequest extends AdminFormRequest -{ - /** - * @return array - */ - public function rules() - { - if ($this->method() === 'PATCH') { - return Pack::getUpdateRulesForId($this->route()->parameter('pack')->id); - } - - return Pack::getCreateRules(); - } - - /** - * Run validation after the rules above have been applied. - * - * @param \Illuminate\Validation\Validator $validator - */ - public function withValidator($validator) - { - if ($this->method() !== 'POST') { - return; - } - - $validator->after(function ($validator) { - $mimetypes = implode(',', PackCreationService::VALID_UPLOAD_TYPES); - - /* @var $validator \Illuminate\Validation\Validator */ - $validator->sometimes('file_upload', 'sometimes|required|file|mimetypes:' . $mimetypes, function () { - return true; - }); - }); - } -} diff --git a/app/Http/Requests/Admin/ServerFormRequest.php b/app/Http/Requests/Admin/ServerFormRequest.php index 33b9c8ff..6f930615 100644 --- a/app/Http/Requests/Admin/ServerFormRequest.php +++ b/app/Http/Requests/Admin/ServerFormRequest.php @@ -21,7 +21,7 @@ class ServerFormRequest extends AdminFormRequest */ public function rules() { - $rules = Server::getCreateRules(); + $rules = Server::getRules(); $rules['description'][] = 'nullable'; return $rules; @@ -62,15 +62,6 @@ class ServerFormRequest extends AdminFormRequest ], function ($input) { return ! ($input->auto_deploy); }); - - $validator->sometimes('pack_id', [ - Rule::exists('packs', 'id')->where(function ($query) { - $query->where('selectable', 1); - $query->where('egg_id', $this->input('egg_id')); - }), - ], function ($input) { - return $input->pack_id !== 0 && $input->pack_id !== null; - }); }); } } diff --git a/app/Http/Requests/Admin/Servers/Databases/StoreServerDatabaseRequest.php b/app/Http/Requests/Admin/Servers/Databases/StoreServerDatabaseRequest.php index ba8b76a8..40a4b06b 100644 --- a/app/Http/Requests/Admin/Servers/Databases/StoreServerDatabaseRequest.php +++ b/app/Http/Requests/Admin/Servers/Databases/StoreServerDatabaseRequest.php @@ -25,6 +25,7 @@ class StoreServerDatabaseRequest extends AdminFormRequest $query->where('database_host_id', $this->input('database_host_id') ?? 0); }), ], + 'max_connections' => 'nullable', 'remote' => 'required|string|regex:/^[0-9%.]{1,15}$/', 'database_host_id' => 'required|integer|exists:database_hosts,id', ]; diff --git a/app/Http/Requests/Admin/Settings/AdvancedSettingsFormRequest.php b/app/Http/Requests/Admin/Settings/AdvancedSettingsFormRequest.php index a80d8dab..a3f72972 100644 --- a/app/Http/Requests/Admin/Settings/AdvancedSettingsFormRequest.php +++ b/app/Http/Requests/Admin/Settings/AdvancedSettingsFormRequest.php @@ -15,12 +15,10 @@ class AdvancedSettingsFormRequest extends AdminFormRequest { return [ 'recaptcha:enabled' => 'required|in:true,false', - 'recaptcha:secret_key' => 'required|string|max:255', - 'recaptcha:website_key' => 'required|string|max:255', + 'recaptcha:secret_key' => 'required|string|max:191', + 'recaptcha:website_key' => 'required|string|max:191', 'pterodactyl:guzzle:timeout' => 'required|integer|between:1,60', 'pterodactyl:guzzle:connect_timeout' => 'required|integer|between:1,60', - 'pterodactyl:console:count' => 'required|integer|min:1', - 'pterodactyl:console:frequency' => 'required|integer|min:10', ]; } @@ -35,8 +33,6 @@ class AdvancedSettingsFormRequest extends AdminFormRequest 'recaptcha:website_key' => 'reCAPTCHA Website Key', 'pterodactyl:guzzle:timeout' => 'HTTP Request Timeout', 'pterodactyl:guzzle:connect_timeout' => 'HTTP Connection Timeout', - 'pterodactyl:console:count' => 'Console Message Count', - 'pterodactyl:console:frequency' => 'Console Frequency Tick', ]; } } diff --git a/app/Http/Requests/Admin/Settings/BaseSettingsFormRequest.php b/app/Http/Requests/Admin/Settings/BaseSettingsFormRequest.php index 0b02561d..208c15b1 100644 --- a/app/Http/Requests/Admin/Settings/BaseSettingsFormRequest.php +++ b/app/Http/Requests/Admin/Settings/BaseSettingsFormRequest.php @@ -16,9 +16,10 @@ class BaseSettingsFormRequest extends AdminFormRequest public function rules() { return [ - 'app:name' => 'required|string|max:255', + 'app:name' => 'required|string|max:191', 'pterodactyl:auth:2fa_required' => 'required|integer|in:0,1,2', 'app:locale' => ['required', 'string', Rule::in(array_keys($this->getAvailableLanguages()))], + 'app:analytics' => 'nullable|string', ]; } @@ -31,6 +32,7 @@ class BaseSettingsFormRequest extends AdminFormRequest 'app:name' => 'Company Name', 'pterodactyl:auth:2fa_required' => 'Require 2-Factor Authentication', 'app:locale' => 'Default Language', + 'app:analytics' => 'Google Analytics', ]; } } diff --git a/app/Http/Requests/Admin/Settings/MailSettingsFormRequest.php b/app/Http/Requests/Admin/Settings/MailSettingsFormRequest.php index 92a23272..728283af 100644 --- a/app/Http/Requests/Admin/Settings/MailSettingsFormRequest.php +++ b/app/Http/Requests/Admin/Settings/MailSettingsFormRequest.php @@ -18,10 +18,10 @@ class MailSettingsFormRequest extends AdminFormRequest 'mail:host' => 'required|string', 'mail:port' => 'required|integer|between:1,65535', 'mail:encryption' => ['present', Rule::in([null, 'tls', 'ssl'])], - 'mail:username' => 'nullable|string|max:255', - 'mail:password' => 'nullable|string|max:255', + 'mail:username' => 'nullable|string|max:191', + 'mail:password' => 'nullable|string|max:191', 'mail:from:address' => 'required|string|email', - 'mail:from:name' => 'nullable|string|max:255', + 'mail:from:name' => 'nullable|string|max:191', ]; } diff --git a/app/Http/Requests/Admin/UserFormRequest.php b/app/Http/Requests/Admin/UserFormRequest.php index c6a35839..4203e65d 100644 --- a/app/Http/Requests/Admin/UserFormRequest.php +++ b/app/Http/Requests/Admin/UserFormRequest.php @@ -3,6 +3,7 @@ namespace Pterodactyl\Http\Requests\Admin; use Pterodactyl\Models\User; +use Illuminate\Support\Collection; class UserFormRequest extends AdminFormRequest { @@ -12,16 +13,16 @@ class UserFormRequest extends AdminFormRequest */ public function rules() { - $rules = collect(User::getCreateRules()); - if ($this->method() === 'PATCH') { - $rules = collect(User::getUpdateRulesForId($this->route()->parameter('user')->id))->merge([ - 'ignore_connection_error' => ['sometimes', 'nullable', 'boolean'], - ]); - } - - return $rules->only([ - 'email', 'username', 'name_first', 'name_last', 'password', - 'language', 'ignore_connection_error', 'root_admin', + return Collection::make( + User::getRulesForUpdate($this->route()->parameter('user')) + )->only([ + 'email', + 'username', + 'name_first', + 'name_last', + 'password', + 'language', + 'root_admin', ])->toArray(); } } diff --git a/app/Http/Requests/Api/Application/Allocations/StoreAllocationRequest.php b/app/Http/Requests/Api/Application/Allocations/StoreAllocationRequest.php index f795a114..1e68b82e 100644 --- a/app/Http/Requests/Api/Application/Allocations/StoreAllocationRequest.php +++ b/app/Http/Requests/Api/Application/Allocations/StoreAllocationRequest.php @@ -24,7 +24,7 @@ class StoreAllocationRequest extends ApplicationApiRequest { return [ 'ip' => 'required|string', - 'alias' => 'sometimes|nullable|string|max:255', + 'alias' => 'sometimes|nullable|string|max:191', 'ports' => 'required|array', 'ports.*' => 'string', ]; diff --git a/app/Http/Requests/Api/Application/ApplicationApiRequest.php b/app/Http/Requests/Api/Application/ApplicationApiRequest.php index ca5f40dd..13353271 100644 --- a/app/Http/Requests/Api/Application/ApplicationApiRequest.php +++ b/app/Http/Requests/Api/Application/ApplicationApiRequest.php @@ -89,6 +89,7 @@ abstract class ApplicationApiRequest extends FormRequest * * @param string $model * @return mixed + * @deprecated * * @throws \Symfony\Component\Routing\Exception\InvalidParameterException */ @@ -126,10 +127,6 @@ abstract class ApplicationApiRequest extends FormRequest * * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException */ - - /** - * @return bool - */ protected function passesAuthorization() { // If we have already validated we do not need to call this function diff --git a/app/Http/Requests/Api/Application/Locations/StoreLocationRequest.php b/app/Http/Requests/Api/Application/Locations/StoreLocationRequest.php index 761c669d..a02a38ca 100644 --- a/app/Http/Requests/Api/Application/Locations/StoreLocationRequest.php +++ b/app/Http/Requests/Api/Application/Locations/StoreLocationRequest.php @@ -25,7 +25,7 @@ class StoreLocationRequest extends ApplicationApiRequest */ public function rules(): array { - return collect(Location::getCreateRules())->only([ + return collect(Location::getRules())->only([ 'long', 'short', ])->toArray(); diff --git a/app/Http/Requests/Api/Application/Locations/UpdateLocationRequest.php b/app/Http/Requests/Api/Application/Locations/UpdateLocationRequest.php index 2d83ab08..0877fa45 100644 --- a/app/Http/Requests/Api/Application/Locations/UpdateLocationRequest.php +++ b/app/Http/Requests/Api/Application/Locations/UpdateLocationRequest.php @@ -27,7 +27,7 @@ class UpdateLocationRequest extends StoreLocationRequest { $locationId = $this->route()->parameter('location')->id; - return collect(Location::getUpdateRulesForId($locationId))->only([ + return collect(Location::getRulesForUpdate($locationId))->only([ 'short', 'long', ])->toArray(); diff --git a/app/Http/Requests/Api/Application/Nodes/StoreNodeRequest.php b/app/Http/Requests/Api/Application/Nodes/StoreNodeRequest.php index 37dd3258..8e23db43 100644 --- a/app/Http/Requests/Api/Application/Nodes/StoreNodeRequest.php +++ b/app/Http/Requests/Api/Application/Nodes/StoreNodeRequest.php @@ -21,12 +21,12 @@ class StoreNodeRequest extends ApplicationApiRequest /** * Validation rules to apply to this request. * - * @param null|array $rules + * @param array|null $rules * @return array */ public function rules(array $rules = null): array { - return collect($rules ?? Node::getCreateRules())->only([ + return collect($rules ?? Node::getRules())->only([ 'public', 'name', 'location_id', diff --git a/app/Http/Requests/Api/Application/Nodes/UpdateNodeRequest.php b/app/Http/Requests/Api/Application/Nodes/UpdateNodeRequest.php index ffba39e6..bf228049 100644 --- a/app/Http/Requests/Api/Application/Nodes/UpdateNodeRequest.php +++ b/app/Http/Requests/Api/Application/Nodes/UpdateNodeRequest.php @@ -17,6 +17,6 @@ class UpdateNodeRequest extends StoreNodeRequest { $nodeId = $this->getModel(Node::class)->id; - return parent::rules(Node::getUpdateRulesForId($nodeId)); + return parent::rules(Node::getRulesForUpdate($nodeId)); } } diff --git a/app/Http/Requests/Api/Application/Servers/Databases/StoreServerDatabaseRequest.php b/app/Http/Requests/Api/Application/Servers/Databases/StoreServerDatabaseRequest.php index c2dbfe14..4ca01941 100644 --- a/app/Http/Requests/Api/Application/Servers/Databases/StoreServerDatabaseRequest.php +++ b/app/Http/Requests/Api/Application/Servers/Databases/StoreServerDatabaseRequest.php @@ -2,9 +2,12 @@ namespace Pterodactyl\Http\Requests\Api\Application\Servers\Databases; +use Webmozart\Assert\Assert; +use Pterodactyl\Models\Server; use Illuminate\Validation\Rule; use Illuminate\Database\Query\Builder; use Pterodactyl\Services\Acl\Api\AdminAcl; +use Pterodactyl\Services\Databases\DatabaseManagementService; use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest; class StoreServerDatabaseRequest extends ApplicationApiRequest @@ -26,14 +29,16 @@ class StoreServerDatabaseRequest extends ApplicationApiRequest */ public function rules(): array { + $server = $this->route()->parameter('server'); + return [ 'database' => [ 'required', - 'string', + 'alpha_dash', 'min:1', - 'max:24', - Rule::unique('databases')->where(function (Builder $query) { - $query->where('database_host_id', $this->input('host') ?? 0); + 'max:48', + Rule::unique('databases')->where(function (Builder $query) use ($server) { + $query->where('server_id', $server->id)->where('database', $this->databaseName()); }), ], 'remote' => 'required|string|regex:/^[0-9%.]{1,15}$/', @@ -68,4 +73,18 @@ class StoreServerDatabaseRequest extends ApplicationApiRequest 'database' => 'Database Name', ]; } + + /** + * Returns the database name in the expected format. + * + * @return string + */ + public function databaseName(): string + { + $server = $this->route()->parameter('server'); + + Assert::isInstanceOf($server, Server::class); + + return DatabaseManagementService::generateUniqueDatabaseName($this->input('database'), $server->id); + } } diff --git a/app/Http/Requests/Api/Application/Servers/StoreServerRequest.php b/app/Http/Requests/Api/Application/Servers/StoreServerRequest.php index 4378474b..15780d69 100644 --- a/app/Http/Requests/Api/Application/Servers/StoreServerRequest.php +++ b/app/Http/Requests/Api/Application/Servers/StoreServerRequest.php @@ -28,7 +28,7 @@ class StoreServerRequest extends ApplicationApiRequest */ public function rules(): array { - $rules = Server::getCreateRules(); + $rules = Server::getRules(); return [ 'external_id' => $rules['external_id'], @@ -36,7 +36,6 @@ class StoreServerRequest extends ApplicationApiRequest 'description' => array_merge(['nullable'], $rules['description']), 'user' => $rules['owner_id'], 'egg' => $rules['egg_id'], - 'pack' => $rules['pack_id'], 'docker_image' => $rules['image'], 'startup' => $rules['startup'], 'environment' => 'present|array', @@ -49,6 +48,7 @@ class StoreServerRequest extends ApplicationApiRequest 'limits.swap' => $rules['swap'], 'limits.disk' => $rules['disk'], 'limits.io' => $rules['io'], + 'limits.threads' => $rules['threads'], 'limits.cpu' => $rules['cpu'], // Application Resource Limits @@ -87,7 +87,6 @@ class StoreServerRequest extends ApplicationApiRequest 'description' => array_get($data, 'description'), 'owner_id' => array_get($data, 'user'), 'egg_id' => array_get($data, 'egg'), - 'pack_id' => array_get($data, 'pack'), 'image' => array_get($data, 'docker_image'), 'startup' => array_get($data, 'startup'), 'environment' => array_get($data, 'environment'), @@ -96,6 +95,7 @@ class StoreServerRequest extends ApplicationApiRequest 'disk' => array_get($data, 'limits.disk'), 'io' => array_get($data, 'limits.io'), 'cpu' => array_get($data, 'limits.cpu'), + 'threads' => array_get($data, 'limits.threads'), 'skip_scripts' => array_get($data, 'skip_scripts', false), 'allocation_id' => array_get($data, 'allocation.default'), 'allocation_additional' => array_get($data, 'allocation.additional'), diff --git a/app/Http/Requests/Api/Application/Servers/UpdateServerBuildConfigurationRequest.php b/app/Http/Requests/Api/Application/Servers/UpdateServerBuildConfigurationRequest.php index 0238cdcf..3f0e1c8c 100644 --- a/app/Http/Requests/Api/Application/Servers/UpdateServerBuildConfigurationRequest.php +++ b/app/Http/Requests/Api/Application/Servers/UpdateServerBuildConfigurationRequest.php @@ -14,7 +14,7 @@ class UpdateServerBuildConfigurationRequest extends ServerWriteRequest */ public function rules(): array { - $rules = Server::getUpdateRulesForId($this->getModel(Server::class)->id); + $rules = Server::getRulesForUpdate($this->getModel(Server::class)); return [ 'allocation' => $rules['allocation_id'], @@ -25,6 +25,7 @@ class UpdateServerBuildConfigurationRequest extends ServerWriteRequest 'limits.swap' => $this->requiredToOptional('swap', $rules['swap'], true), 'limits.io' => $this->requiredToOptional('io', $rules['io'], true), 'limits.cpu' => $this->requiredToOptional('cpu', $rules['cpu'], true), + 'limits.threads' => $this->requiredToOptional('threads', $rules['threads'], true), 'limits.disk' => $this->requiredToOptional('disk', $rules['disk'], true), // Legacy rules to maintain backwards compatable API support without requiring @@ -35,6 +36,7 @@ class UpdateServerBuildConfigurationRequest extends ServerWriteRequest 'swap' => $this->requiredToOptional('swap', $rules['swap']), 'io' => $this->requiredToOptional('io', $rules['io']), 'cpu' => $this->requiredToOptional('cpu', $rules['cpu']), + 'threads' => $this->requiredToOptional('threads', $rules['threads']), 'disk' => $this->requiredToOptional('disk', $rules['disk']), 'add_allocations' => 'bail|array', @@ -97,8 +99,8 @@ class UpdateServerBuildConfigurationRequest extends ServerWriteRequest * call. * * @param string $field - * @param array $rules - * @param bool $limits + * @param array $rules + * @param bool $limits * @return array * * @see https://github.com/pterodactyl/panel/issues/1500 diff --git a/app/Http/Requests/Api/Application/Servers/UpdateServerDetailsRequest.php b/app/Http/Requests/Api/Application/Servers/UpdateServerDetailsRequest.php index aa585bad..172dd1e3 100644 --- a/app/Http/Requests/Api/Application/Servers/UpdateServerDetailsRequest.php +++ b/app/Http/Requests/Api/Application/Servers/UpdateServerDetailsRequest.php @@ -13,7 +13,7 @@ class UpdateServerDetailsRequest extends ServerWriteRequest */ public function rules(): array { - $rules = Server::getUpdateRulesForId($this->getModel(Server::class)->id); + $rules = Server::getRulesForUpdate($this->getModel(Server::class)); return [ 'external_id' => $rules['external_id'], diff --git a/app/Http/Requests/Api/Application/Servers/UpdateServerStartupRequest.php b/app/Http/Requests/Api/Application/Servers/UpdateServerStartupRequest.php index acb6043c..fc367cda 100644 --- a/app/Http/Requests/Api/Application/Servers/UpdateServerStartupRequest.php +++ b/app/Http/Requests/Api/Application/Servers/UpdateServerStartupRequest.php @@ -25,13 +25,12 @@ class UpdateServerStartupRequest extends ApplicationApiRequest */ public function rules(): array { - $data = Server::getUpdateRulesForId($this->getModel(Server::class)->id); + $data = Server::getRulesForUpdate($this->getModel(Server::class)); return [ 'startup' => $data['startup'], 'environment' => 'present|array', 'egg' => $data['egg_id'], - 'pack' => $data['pack_id'], 'image' => $data['image'], 'skip_scripts' => 'present|boolean', ]; @@ -48,7 +47,6 @@ class UpdateServerStartupRequest extends ApplicationApiRequest return collect($data)->only(['startup', 'environment', 'skip_scripts'])->merge([ 'egg_id' => array_get($data, 'egg'), - 'pack_id' => array_get($data, 'pack'), 'docker_image' => array_get($data, 'image'), ])->toArray(); } diff --git a/app/Http/Requests/Api/Application/Users/GetExternalUserRequest.php b/app/Http/Requests/Api/Application/Users/GetExternalUserRequest.php index b1f77918..4ad98c0f 100644 --- a/app/Http/Requests/Api/Application/Users/GetExternalUserRequest.php +++ b/app/Http/Requests/Api/Application/Users/GetExternalUserRequest.php @@ -47,6 +47,7 @@ class GetExternalUserRequest extends ApplicationApiRequest /** * Return the user model for the requested external user. + * * @return \Pterodactyl\Models\User */ public function getUserModel(): User diff --git a/app/Http/Requests/Api/Application/Users/StoreUserRequest.php b/app/Http/Requests/Api/Application/Users/StoreUserRequest.php index 71153836..9cddad7e 100644 --- a/app/Http/Requests/Api/Application/Users/StoreUserRequest.php +++ b/app/Http/Requests/Api/Application/Users/StoreUserRequest.php @@ -26,7 +26,7 @@ class StoreUserRequest extends ApplicationApiRequest */ public function rules(array $rules = null): array { - $rules = $rules ?? User::getCreateRules(); + $rules = $rules ?? User::getRules(); $response = collect($rules)->only([ 'external_id', diff --git a/app/Http/Requests/Api/Application/Users/UpdateUserRequest.php b/app/Http/Requests/Api/Application/Users/UpdateUserRequest.php index 929a77f3..ce38348d 100644 --- a/app/Http/Requests/Api/Application/Users/UpdateUserRequest.php +++ b/app/Http/Requests/Api/Application/Users/UpdateUserRequest.php @@ -16,6 +16,6 @@ class UpdateUserRequest extends StoreUserRequest { $userId = $this->getModel(User::class)->id; - return parent::rules(User::getUpdateRulesForId($userId)); + return parent::rules(User::getRulesForUpdate($userId)); } } diff --git a/app/Http/Requests/Api/Client/Account/StoreApiKeyRequest.php b/app/Http/Requests/Api/Client/Account/StoreApiKeyRequest.php new file mode 100644 index 00000000..1a263286 --- /dev/null +++ b/app/Http/Requests/Api/Client/Account/StoreApiKeyRequest.php @@ -0,0 +1,33 @@ + $rules['memo'], + 'allowed_ips' => $rules['allowed_ips'], + 'allowed_ips.*' => 'ip', + ]; + } + + /** + * @return array|string[] + */ + public function messages() + { + return [ + 'allowed_ips.*' => 'All of the IP addresses entered must be valid IPv4 addresses.', + ]; + } +} diff --git a/app/Http/Requests/Api/Client/Account/UpdateEmailRequest.php b/app/Http/Requests/Api/Client/Account/UpdateEmailRequest.php new file mode 100644 index 00000000..c9936508 --- /dev/null +++ b/app/Http/Requests/Api/Client/Account/UpdateEmailRequest.php @@ -0,0 +1,39 @@ +input('password'), $this->user()->password)) { + throw new InvalidPasswordProvidedException(trans('validation.internal.invalid_password')); + } + + return true; + } + + /** + * @return array + */ + public function rules(): array + { + $rules = User::getRulesForUpdate($this->user()); + + return ['email' => $rules['email']]; + } +} diff --git a/app/Http/Requests/Api/Client/Account/UpdatePasswordRequest.php b/app/Http/Requests/Api/Client/Account/UpdatePasswordRequest.php new file mode 100644 index 00000000..f52b7129 --- /dev/null +++ b/app/Http/Requests/Api/Client/Account/UpdatePasswordRequest.php @@ -0,0 +1,39 @@ +input('current_password'), $this->user()->password)) { + throw new InvalidPasswordProvidedException(trans('validation.internal.invalid_password')); + } + + return true; + } + + /** + * @return array + */ + public function rules(): array + { + $rules = User::getRulesForUpdate($this->user()); + + return ['password' => array_merge($rules['password'], ['confirmed'])]; + } +} diff --git a/app/Http/Requests/Api/Client/ClientApiRequest.php b/app/Http/Requests/Api/Client/ClientApiRequest.php index 92402e51..465d5a48 100644 --- a/app/Http/Requests/Api/Client/ClientApiRequest.php +++ b/app/Http/Requests/Api/Client/ClientApiRequest.php @@ -2,18 +2,34 @@ namespace Pterodactyl\Http\Requests\Api\Client; +use Pterodactyl\Models\Server; +use Pterodactyl\Contracts\Http\ClientPermissionsRequest; use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest; -abstract class ClientApiRequest extends ApplicationApiRequest +/** + * @method \Pterodactyl\Models\User user($guard = null) + */ +class ClientApiRequest extends ApplicationApiRequest { /** - * Determine if the current user is authorized to perform - * the requested action against the API. + * Determine if the current user is authorized to perform the requested action against the API. * * @return bool */ public function authorize(): bool { + if ($this instanceof ClientPermissionsRequest || method_exists($this, 'permission')) { + $server = $this->route()->parameter('server'); + + if ($server instanceof Server) { + return $this->user()->can($this->permission(), $server); + } + + // If there is no server available on the reqest, trigger a failure since + // we expect there to be one at this point. + return false; + } + return true; } } diff --git a/app/Http/Requests/Api/Client/Servers/Backups/DeleteBackupRequest.php b/app/Http/Requests/Api/Client/Servers/Backups/DeleteBackupRequest.php new file mode 100644 index 00000000..33b68aab --- /dev/null +++ b/app/Http/Requests/Api/Client/Servers/Backups/DeleteBackupRequest.php @@ -0,0 +1,17 @@ +route()->parameter('server'); + /** @var \Pterodactyl\Models\Backup|mixed $backup */ + $backup = $this->route()->parameter('backup'); + + if ($server instanceof Server && $backup instanceof Backup) { + if ($server->exists && $backup->exists && $server->id === $backup->server_id) { + return true; + } + } + + return false; + } +} diff --git a/app/Http/Requests/Api/Client/Servers/Backups/GetBackupsRequest.php b/app/Http/Requests/Api/Client/Servers/Backups/GetBackupsRequest.php new file mode 100644 index 00000000..f938906d --- /dev/null +++ b/app/Http/Requests/Api/Client/Servers/Backups/GetBackupsRequest.php @@ -0,0 +1,17 @@ + 'nullable|string|max:191', + 'ignored' => 'nullable|string', + ]; + } +} diff --git a/app/Http/Requests/Api/Client/Servers/Databases/DeleteDatabaseRequest.php b/app/Http/Requests/Api/Client/Servers/Databases/DeleteDatabaseRequest.php new file mode 100644 index 00000000..c2161680 --- /dev/null +++ b/app/Http/Requests/Api/Client/Servers/Databases/DeleteDatabaseRequest.php @@ -0,0 +1,28 @@ +getModel(Server::class)->id === $this->getModel(Database::class)->server_id; + } +} diff --git a/app/Http/Requests/Api/Client/Servers/Databases/GetDatabasesRequest.php b/app/Http/Requests/Api/Client/Servers/Databases/GetDatabasesRequest.php new file mode 100644 index 00000000..be78b5eb --- /dev/null +++ b/app/Http/Requests/Api/Client/Servers/Databases/GetDatabasesRequest.php @@ -0,0 +1,18 @@ +route()->parameter('server'); + + Assert::isInstanceOf($server, Server::class); + + return [ + 'database' => [ + 'required', + 'alpha_dash', + 'min:1', + 'max:48', + // Yes, I am aware that you could have the same database name across two unique hosts. However, + // I don't really care about that for this validation. We just want to make sure it is unique to + // the server itself. No need for complexity. + Rule::unique('databases')->where(function (Builder $query) use ($server) { + $query->where('server_id', $server->id) + ->where('database', DatabaseManagementService::generateUniqueDatabaseName($this->input('database'), $server->id)); + }), + ], + 'remote' => 'required|string|regex:/^[0-9%.]{1,15}$/', + ]; + } + + /** + * @return array + */ + public function messages() + { + return [ + 'database.unique' => 'The database name you have selected is already in use by this server.', + ]; + } +} diff --git a/app/Http/Requests/Api/Client/Servers/Files/CompressFilesRequest.php b/app/Http/Requests/Api/Client/Servers/Files/CompressFilesRequest.php new file mode 100644 index 00000000..bd574cf5 --- /dev/null +++ b/app/Http/Requests/Api/Client/Servers/Files/CompressFilesRequest.php @@ -0,0 +1,31 @@ + 'sometimes|nullable|string', + 'files' => 'required|array', + 'files.*' => 'string', + ]; + } +} diff --git a/app/Http/Requests/Api/Client/Servers/Files/CopyFileRequest.php b/app/Http/Requests/Api/Client/Servers/Files/CopyFileRequest.php new file mode 100644 index 00000000..97bfc3fb --- /dev/null +++ b/app/Http/Requests/Api/Client/Servers/Files/CopyFileRequest.php @@ -0,0 +1,28 @@ + 'required|string', + ]; + } +} diff --git a/app/Http/Requests/Api/Client/Servers/Files/CreateFolderRequest.php b/app/Http/Requests/Api/Client/Servers/Files/CreateFolderRequest.php new file mode 100644 index 00000000..10457ffe --- /dev/null +++ b/app/Http/Requests/Api/Client/Servers/Files/CreateFolderRequest.php @@ -0,0 +1,30 @@ + 'sometimes|nullable|string', + 'name' => 'required|string', + ]; + } +} diff --git a/app/Http/Requests/Api/Client/Servers/Files/DecompressFilesRequest.php b/app/Http/Requests/Api/Client/Servers/Files/DecompressFilesRequest.php new file mode 100644 index 00000000..f8493ec4 --- /dev/null +++ b/app/Http/Requests/Api/Client/Servers/Files/DecompressFilesRequest.php @@ -0,0 +1,32 @@ + 'sometimes|nullable|string', + 'file' => 'required|string', + ]; + } +} diff --git a/app/Http/Requests/Api/Client/Servers/Files/DeleteFileRequest.php b/app/Http/Requests/Api/Client/Servers/Files/DeleteFileRequest.php new file mode 100644 index 00000000..7f5ccbf7 --- /dev/null +++ b/app/Http/Requests/Api/Client/Servers/Files/DeleteFileRequest.php @@ -0,0 +1,30 @@ + 'required|nullable|string', + 'files' => 'required|array', + 'files.*' => 'string', + ]; + } +} diff --git a/app/Http/Requests/Api/Client/Servers/Files/DownloadFileRequest.php b/app/Http/Requests/Api/Client/Servers/Files/DownloadFileRequest.php new file mode 100644 index 00000000..0eb9791c --- /dev/null +++ b/app/Http/Requests/Api/Client/Servers/Files/DownloadFileRequest.php @@ -0,0 +1,20 @@ +user()->can('file.read', $this->getModel(Server::class)); + } +} diff --git a/app/Http/Requests/Api/Client/Servers/Files/GetFileContentsRequest.php b/app/Http/Requests/Api/Client/Servers/Files/GetFileContentsRequest.php new file mode 100644 index 00000000..008b4443 --- /dev/null +++ b/app/Http/Requests/Api/Client/Servers/Files/GetFileContentsRequest.php @@ -0,0 +1,32 @@ + 'required|string', + ]; + } +} diff --git a/app/Http/Requests/Api/Client/Servers/Files/ListFilesRequest.php b/app/Http/Requests/Api/Client/Servers/Files/ListFilesRequest.php new file mode 100644 index 00000000..4f71648c --- /dev/null +++ b/app/Http/Requests/Api/Client/Servers/Files/ListFilesRequest.php @@ -0,0 +1,30 @@ + 'sometimes|nullable|string', + ]; + } +} diff --git a/app/Http/Requests/Api/Client/Servers/Files/RenameFileRequest.php b/app/Http/Requests/Api/Client/Servers/Files/RenameFileRequest.php new file mode 100644 index 00000000..c8529a35 --- /dev/null +++ b/app/Http/Requests/Api/Client/Servers/Files/RenameFileRequest.php @@ -0,0 +1,35 @@ + 'required|nullable|string', + 'files' => 'required|array', + 'files.*' => 'array', + 'files.*.to' => 'required|string', + 'files.*.from' => 'required|string', + ]; + } +} diff --git a/app/Http/Requests/Api/Client/Servers/Files/UploadFileRequest.php b/app/Http/Requests/Api/Client/Servers/Files/UploadFileRequest.php new file mode 100644 index 00000000..6808a549 --- /dev/null +++ b/app/Http/Requests/Api/Client/Servers/Files/UploadFileRequest.php @@ -0,0 +1,17 @@ + 'required|string', + ]; + } +} diff --git a/app/Http/Requests/Api/Client/Servers/Network/DeleteAllocationRequest.php b/app/Http/Requests/Api/Client/Servers/Network/DeleteAllocationRequest.php new file mode 100644 index 00000000..9c0d911f --- /dev/null +++ b/app/Http/Requests/Api/Client/Servers/Network/DeleteAllocationRequest.php @@ -0,0 +1,17 @@ + array_merge($rules['notes'], ['present']), + ]; + } +} diff --git a/app/Http/Requests/Api/Client/Servers/Schedules/DeleteScheduleRequest.php b/app/Http/Requests/Api/Client/Servers/Schedules/DeleteScheduleRequest.php new file mode 100644 index 00000000..8ecc7af0 --- /dev/null +++ b/app/Http/Requests/Api/Client/Servers/Schedules/DeleteScheduleRequest.php @@ -0,0 +1,16 @@ + $rules['name'], + 'is_active' => array_merge(['filled'], $rules['is_active']), + 'minute' => $rules['cron_minute'], + 'hour' => $rules['cron_hour'], + 'day_of_month' => $rules['cron_day_of_month'], + 'day_of_week' => $rules['cron_day_of_week'], + ]; + } +} diff --git a/app/Http/Requests/Api/Client/Servers/Schedules/StoreTaskRequest.php b/app/Http/Requests/Api/Client/Servers/Schedules/StoreTaskRequest.php new file mode 100644 index 00000000..cd95bf5e --- /dev/null +++ b/app/Http/Requests/Api/Client/Servers/Schedules/StoreTaskRequest.php @@ -0,0 +1,33 @@ + 'required|in:command,power,backup', + 'payload' => 'required_unless:action,backup|string|nullable', + 'time_offset' => 'required|numeric|min:0|max:900', + 'sequence_id' => 'sometimes|required|numeric|min:1', + ]; + } +} diff --git a/app/Http/Requests/Api/Client/Servers/Schedules/UpdateScheduleRequest.php b/app/Http/Requests/Api/Client/Servers/Schedules/UpdateScheduleRequest.php new file mode 100644 index 00000000..844388e2 --- /dev/null +++ b/app/Http/Requests/Api/Client/Servers/Schedules/UpdateScheduleRequest.php @@ -0,0 +1,16 @@ +route()->parameter('server'); + $schedule = $this->route()->parameter('schedule'); + + // If the schedule does not belong to this server throw a 404 error. Also throw an + // error if the task being requested does not belong to the associated schedule. + if ($server instanceof Server && $schedule instanceof Schedule) { + $task = $this->route()->parameter('task'); + + if ($schedule->server_id !== $server->id || ($task instanceof Task && $task->schedule_id !== $schedule->id)) { + throw new NotFoundHttpException( + 'The requested resource does not exist on the system.' + ); + } + } + + return true; + } + + /** + * @return string + */ + public function permission(): string + { + return Permission::ACTION_SCHEDULE_READ; + } +} diff --git a/app/Http/Requests/Api/Client/Servers/SendCommandRequest.php b/app/Http/Requests/Api/Client/Servers/SendCommandRequest.php index 22feb2af..3f2f6c19 100644 --- a/app/Http/Requests/Api/Client/Servers/SendCommandRequest.php +++ b/app/Http/Requests/Api/Client/Servers/SendCommandRequest.php @@ -2,18 +2,19 @@ namespace Pterodactyl\Http\Requests\Api\Client\Servers; -use Pterodactyl\Models\Server; +use Pterodactyl\Models\Permission; +use Pterodactyl\Http\Requests\Api\Client\ClientApiRequest; -class SendCommandRequest extends GetServerRequest +class SendCommandRequest extends ClientApiRequest { /** * Determine if the API user has permission to perform this action. * - * @return bool + * @return string */ - public function authorize(): bool + public function permission(): string { - return $this->user()->can('send-command', $this->getModel(Server::class)); + return Permission::ACTION_CONTROL_CONSOLE; } /** diff --git a/app/Http/Requests/Api/Client/Servers/SendPowerRequest.php b/app/Http/Requests/Api/Client/Servers/SendPowerRequest.php index 7a32c117..ea7e00fc 100644 --- a/app/Http/Requests/Api/Client/Servers/SendPowerRequest.php +++ b/app/Http/Requests/Api/Client/Servers/SendPowerRequest.php @@ -2,7 +2,7 @@ namespace Pterodactyl\Http\Requests\Api\Client\Servers; -use Pterodactyl\Models\Server; +use Pterodactyl\Models\Permission; use Pterodactyl\Http\Requests\Api\Client\ClientApiRequest; class SendPowerRequest extends ClientApiRequest @@ -10,11 +10,21 @@ class SendPowerRequest extends ClientApiRequest /** * Determine if the user has permission to send a power command to a server. * - * @return bool + * @return string */ - public function authorize(): bool + public function permission(): string { - return $this->user()->can('power-' . $this->input('signal', '_undefined'), $this->getModel(Server::class)); + switch ($this->input('signal')) { + case 'start': + return Permission::ACTION_CONTROL_START; + case 'stop': + case 'kill': + return Permission::ACTION_CONTROL_STOP; + case 'restart': + return Permission::ACTION_CONTROL_RESTART; + } + + return '__invalid'; } /** diff --git a/app/Http/Requests/Api/Client/Servers/Settings/ReinstallServerRequest.php b/app/Http/Requests/Api/Client/Servers/Settings/ReinstallServerRequest.php new file mode 100644 index 00000000..9edc8ad1 --- /dev/null +++ b/app/Http/Requests/Api/Client/Servers/Settings/ReinstallServerRequest.php @@ -0,0 +1,17 @@ + Server::getRules()['name'], + ]; + } +} diff --git a/app/Http/Requests/Api/Client/Servers/Startup/GetStartupRequest.php b/app/Http/Requests/Api/Client/Servers/Startup/GetStartupRequest.php new file mode 100644 index 00000000..25ab2ce2 --- /dev/null +++ b/app/Http/Requests/Api/Client/Servers/Startup/GetStartupRequest.php @@ -0,0 +1,17 @@ + 'required|string', + 'value' => 'present', + ]; + } +} diff --git a/app/Http/Requests/Api/Client/Servers/Subusers/DeleteSubuserRequest.php b/app/Http/Requests/Api/Client/Servers/Subusers/DeleteSubuserRequest.php new file mode 100644 index 00000000..ef0a09aa --- /dev/null +++ b/app/Http/Requests/Api/Client/Servers/Subusers/DeleteSubuserRequest.php @@ -0,0 +1,16 @@ + 'required|email|between:1,191', + 'permissions' => 'required|array', + 'permissions.*' => 'string', + ]; + } +} diff --git a/app/Http/Requests/Api/Client/Servers/Subusers/SubuserRequest.php b/app/Http/Requests/Api/Client/Servers/Subusers/SubuserRequest.php new file mode 100644 index 00000000..98d0d964 --- /dev/null +++ b/app/Http/Requests/Api/Client/Servers/Subusers/SubuserRequest.php @@ -0,0 +1,82 @@ +route()->parameter('user'); + // Don't allow a user to edit themselves on the server. + if ($user instanceof User) { + if ($user->uuid === $this->user()->uuid) { + return false; + } + } + + // If this is a POST request, validate that the user can even assign the permissions they + // have selected to assign. + if ($this->method() === Request::METHOD_POST && $this->has('permissions')) { + $this->validatePermissionsCanBeAssigned( + $this->input('permissions') ?? [] + ); + } + + return true; + } + + /** + * Validates that the permissions we are trying to assign can actually be assigned + * by the user making the request. + * + * @param array $permissions + * + * @throws \Illuminate\Contracts\Container\BindingResolutionException + */ + protected function validatePermissionsCanBeAssigned(array $permissions) + { + $user = $this->user(); + /** @var \Pterodactyl\Models\Server $server */ + $server = $this->route()->parameter('server'); + + // If we are a root admin or the server owner, no need to perform these checks. + if ($user->root_admin || $user->id === $server->owner_id) { + return; + } + + // Otherwise, get the current subuser's permission set, and ensure that the + // permissions they are trying to assign are not _more_ than the ones they + // already have. + /** @var \Pterodactyl\Models\Subuser|null $subuser */ + /** @var \Pterodactyl\Services\Servers\GetUserPermissionsService $service */ + $service = $this->container->make(GetUserPermissionsService::class); + + if (count(array_diff($permissions, $service->handle($server, $user))) > 0) { + throw new HttpForbiddenException( + 'Cannot assign permissions to a subuser that your account does not actively possess.' + ); + } + } +} diff --git a/app/Http/Requests/Api/Client/Servers/Subusers/UpdateSubuserRequest.php b/app/Http/Requests/Api/Client/Servers/Subusers/UpdateSubuserRequest.php new file mode 100644 index 00000000..3a84a027 --- /dev/null +++ b/app/Http/Requests/Api/Client/Servers/Subusers/UpdateSubuserRequest.php @@ -0,0 +1,27 @@ + 'required|array', + 'permissions.*' => 'string', + ]; + } +} diff --git a/app/Http/Requests/Api/Remote/AuthenticateWebsocketDetailsRequest.php b/app/Http/Requests/Api/Remote/AuthenticateWebsocketDetailsRequest.php new file mode 100644 index 00000000..885e1923 --- /dev/null +++ b/app/Http/Requests/Api/Remote/AuthenticateWebsocketDetailsRequest.php @@ -0,0 +1,26 @@ + 'required|string', + ]; + } +} diff --git a/app/Http/Requests/Api/Remote/InstallationDataRequest.php b/app/Http/Requests/Api/Remote/InstallationDataRequest.php new file mode 100644 index 00000000..0737d71b --- /dev/null +++ b/app/Http/Requests/Api/Remote/InstallationDataRequest.php @@ -0,0 +1,26 @@ + 'present|boolean', + ]; + } +} diff --git a/app/Http/Requests/Api/Remote/ReportBackupCompleteRequest.php b/app/Http/Requests/Api/Remote/ReportBackupCompleteRequest.php new file mode 100644 index 00000000..a90a2b2b --- /dev/null +++ b/app/Http/Requests/Api/Remote/ReportBackupCompleteRequest.php @@ -0,0 +1,21 @@ + 'present|boolean', + 'checksum' => 'nullable|string|required_if:successful,true', + 'checksum_type' => 'nullable|string|required_if:successful,true', + 'size' => 'nullable|numeric|required_if:successful,true', + ]; + } +} diff --git a/app/Http/Requests/Auth/LoginCheckpointRequest.php b/app/Http/Requests/Auth/LoginCheckpointRequest.php new file mode 100644 index 00000000..87d84ce9 --- /dev/null +++ b/app/Http/Requests/Auth/LoginCheckpointRequest.php @@ -0,0 +1,45 @@ + 'required|string', + 'authentication_code' => [ + 'nullable', + 'numeric', + Rule::requiredIf(function () { + return empty($this->input('recovery_token')); + }), + ], + 'recovery_token' => [ + 'nullable', + 'string', + Rule::requiredIf(function () { + return empty($this->input('authentication_code')); + }), + ], + ]; + } +} diff --git a/app/Http/Requests/Auth/LoginRequest.php b/app/Http/Requests/Auth/LoginRequest.php new file mode 100644 index 00000000..7fed2093 --- /dev/null +++ b/app/Http/Requests/Auth/LoginRequest.php @@ -0,0 +1,27 @@ + 'required|string|min:1', + 'password' => 'required|string', + ]; + } +} diff --git a/app/Http/Requests/Auth/ResetPasswordRequest.php b/app/Http/Requests/Auth/ResetPasswordRequest.php new file mode 100644 index 00000000..e06883c2 --- /dev/null +++ b/app/Http/Requests/Auth/ResetPasswordRequest.php @@ -0,0 +1,28 @@ + 'required|string', + 'email' => 'required|email', + 'password' => 'required|string|confirmed|min:8', + ]; + } +} diff --git a/app/Http/Requests/Base/AccountDataFormRequest.php b/app/Http/Requests/Base/AccountDataFormRequest.php deleted file mode 100644 index 076cba9b..00000000 --- a/app/Http/Requests/Base/AccountDataFormRequest.php +++ /dev/null @@ -1,71 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -namespace Pterodactyl\Http\Requests\Base; - -use Pterodactyl\Models\User; -use Pterodactyl\Http\Requests\FrontendUserFormRequest; -use Pterodactyl\Exceptions\Http\Base\InvalidPasswordProvidedException; - -class AccountDataFormRequest extends FrontendUserFormRequest -{ - /** - * @return bool - * @throws \Pterodactyl\Exceptions\Http\Base\InvalidPasswordProvidedException - */ - public function authorize() - { - if (! parent::authorize()) { - return false; - } - - // Verify password matches when changing password or email. - if (in_array($this->input('do_action'), ['password', 'email'])) { - if (! password_verify($this->input('current_password'), $this->user()->password)) { - throw new InvalidPasswordProvidedException(trans('base.account.invalid_password')); - } - } - - return true; - } - - /** - * @return array - */ - public function rules() - { - $modelRules = User::getUpdateRulesForId($this->user()->id); - - switch ($this->input('do_action')) { - case 'email': - $rules = [ - 'new_email' => array_get($modelRules, 'email'), - ]; - break; - case 'password': - $rules = [ - 'new_password' => 'required|confirmed|string|min:8', - 'new_password_confirmation' => 'required', - ]; - break; - case 'identity': - $rules = [ - 'name_first' => array_get($modelRules, 'name_first'), - 'name_last' => array_get($modelRules, 'name_last'), - 'username' => array_get($modelRules, 'username'), - 'language' => array_get($modelRules, 'language'), - ]; - break; - default: - abort(422); - } - - return $rules; - } -} diff --git a/app/Http/Requests/Base/ApiKeyFormRequest.php b/app/Http/Requests/Base/ApiKeyFormRequest.php deleted file mode 100644 index 5959657f..00000000 --- a/app/Http/Requests/Base/ApiKeyFormRequest.php +++ /dev/null @@ -1,74 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -namespace Pterodactyl\Http\Requests\Base; - -use IPTools\Network; -use Pterodactyl\Http\Requests\FrontendUserFormRequest; - -class ApiKeyFormRequest extends FrontendUserFormRequest -{ - /** - * Rules applied to data passed in this request. - * - * @return array - */ - public function rules() - { - $this->parseAllowedIntoArray(); - - return [ - 'memo' => 'required|nullable|string|max:500', - 'permissions' => 'sometimes|present|array', - 'admin_permissions' => 'sometimes|present|array', - 'allowed_ips' => 'present', - 'allowed_ips.*' => 'sometimes|string', - ]; - } - - /** - * Parse the string of allowed IPs into an array. - */ - protected function parseAllowedIntoArray() - { - $loop = []; - if (! empty($this->input('allowed_ips'))) { - foreach (explode(PHP_EOL, $this->input('allowed_ips')) as $ip) { - $loop[] = trim($ip); - } - } - - $this->merge(['allowed_ips' => $loop]); - } - - /** - * Run additional validation rules on the request to ensure all of the data is good. - * - * @param \Illuminate\Validation\Validator $validator - */ - public function withValidator($validator) - { - $validator->after(function ($validator) { - /* @var \Illuminate\Validation\Validator $validator */ - if (empty($this->input('permissions')) && empty($this->input('admin_permissions'))) { - $validator->errors()->add('permissions', 'At least one permission must be selected.'); - } - - foreach ($this->input('allowed_ips') as $ip) { - $ip = trim($ip); - - try { - Network::parse($ip); - } catch (\Exception $ex) { - $validator->errors()->add('allowed_ips', 'Could not parse IP ' . $ip . ' because it is in an invalid format.'); - } - } - }); - } -} diff --git a/app/Http/Requests/Base/CreateClientApiKeyRequest.php b/app/Http/Requests/Base/CreateClientApiKeyRequest.php deleted file mode 100644 index b8e7bbfe..00000000 --- a/app/Http/Requests/Base/CreateClientApiKeyRequest.php +++ /dev/null @@ -1,21 +0,0 @@ - 'required|string|max:255', - 'allowed_ips' => 'nullable|string', - ]; - } -} diff --git a/app/Http/Requests/Base/StoreAccountKeyRequest.php b/app/Http/Requests/Base/StoreAccountKeyRequest.php deleted file mode 100644 index 2cfc2278..00000000 --- a/app/Http/Requests/Base/StoreAccountKeyRequest.php +++ /dev/null @@ -1,23 +0,0 @@ - 'required|nullable|string|max:500', - 'allowed_ips' => 'present', - 'allowed_ips.*' => 'sometimes|string', - ]; - } -} diff --git a/app/Http/Requests/Server/Database/DeleteServerDatabaseRequest.php b/app/Http/Requests/Server/Database/DeleteServerDatabaseRequest.php deleted file mode 100644 index eed54e2e..00000000 --- a/app/Http/Requests/Server/Database/DeleteServerDatabaseRequest.php +++ /dev/null @@ -1,40 +0,0 @@ - 'required|string|min:1', - 'remote' => 'required|string|regex:/^[0-9%.]{1,15}$/', - ]; - } -} diff --git a/app/Http/Requests/Server/ScheduleCreationFormRequest.php b/app/Http/Requests/Server/ScheduleCreationFormRequest.php deleted file mode 100644 index 6291d3cb..00000000 --- a/app/Http/Requests/Server/ScheduleCreationFormRequest.php +++ /dev/null @@ -1,79 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -namespace Pterodactyl\Http\Requests\Server; - -class ScheduleCreationFormRequest extends ServerFormRequest -{ - /** - * Permission to validate this request against. - * - * @return string - */ - protected function permission(): string - { - if ($this->method() === 'PATCH') { - return 'edit-schedule'; - } - - return 'create-schedule'; - } - - /** - * Validation rules to apply to the request. - * - * @return array - */ - public function rules() - { - return [ - 'name' => 'nullable|string|max:255', - 'cron_day_of_week' => 'required|string', - 'cron_day_of_month' => 'required|string', - 'cron_hour' => 'required|string', - 'cron_minute' => 'required|string', - 'tasks' => 'sometimes|array|size:4', - 'tasks.time_value' => 'required_with:tasks|max:5', - 'tasks.time_interval' => 'required_with:tasks|max:5', - 'tasks.action' => 'required_with:tasks|max:5', - 'tasks.payload' => 'required_with:tasks|max:5', - 'tasks.time_value.*' => 'numeric|between:0,59', - 'tasks.time_interval.*' => 'string|in:s,m', - 'tasks.action.*' => 'string|in:power,command', - 'tasks.payload.*' => 'string', - ]; - } - - /** - * Normalize the request into a format that can be used by the application. - * - * @return array - */ - public function normalize() - { - return $this->only('name', 'cron_day_of_week', 'cron_day_of_month', 'cron_hour', 'cron_minute'); - } - - /** - * Return the tasks provided in the request that are associated with this schedule. - * - * @return array|null - */ - public function getTasks() - { - $restructured = []; - foreach (array_get($this->all(), 'tasks', []) as $key => $values) { - for ($i = 0; $i < count($values); $i++) { - $restructured[$i][$key] = $values[$i]; - } - } - - return empty($restructured) ? null : $restructured; - } -} diff --git a/app/Http/Requests/Server/ServerFormRequest.php b/app/Http/Requests/Server/ServerFormRequest.php deleted file mode 100644 index c0ca370b..00000000 --- a/app/Http/Requests/Server/ServerFormRequest.php +++ /dev/null @@ -1,35 +0,0 @@ -user()->can($this->permission(), $this->getServer()); - } - - public function getServer(): Server - { - return $this->attributes->get('server'); - } -} diff --git a/app/Http/Requests/Server/Settings/ChangeServerNameRequest.php b/app/Http/Requests/Server/Settings/ChangeServerNameRequest.php deleted file mode 100644 index c969cb0e..00000000 --- a/app/Http/Requests/Server/Settings/ChangeServerNameRequest.php +++ /dev/null @@ -1,31 +0,0 @@ - Server::getCreateRules()['name'], - ]; - } -} diff --git a/app/Http/Requests/Server/Subuser/SubuserStoreFormRequest.php b/app/Http/Requests/Server/Subuser/SubuserStoreFormRequest.php deleted file mode 100644 index 9b7c6ce4..00000000 --- a/app/Http/Requests/Server/Subuser/SubuserStoreFormRequest.php +++ /dev/null @@ -1,31 +0,0 @@ - 'required|email', - 'permissions' => 'sometimes|array', - ]; - } -} diff --git a/app/Http/Requests/Server/Subuser/SubuserUpdateFormRequest.php b/app/Http/Requests/Server/Subuser/SubuserUpdateFormRequest.php deleted file mode 100644 index 7ff82abc..00000000 --- a/app/Http/Requests/Server/Subuser/SubuserUpdateFormRequest.php +++ /dev/null @@ -1,30 +0,0 @@ - 'present|array', - ]; - } -} diff --git a/app/Http/Requests/Server/UpdateFileContentsFormRequest.php b/app/Http/Requests/Server/UpdateFileContentsFormRequest.php deleted file mode 100644 index 7ded39bc..00000000 --- a/app/Http/Requests/Server/UpdateFileContentsFormRequest.php +++ /dev/null @@ -1,101 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -namespace Pterodactyl\Http\Requests\Server; - -use GuzzleHttp\Exception\RequestException; -use Illuminate\Contracts\Config\Repository; -use Pterodactyl\Exceptions\Http\Server\FileSizeTooLargeException; -use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; -use Pterodactyl\Contracts\Repository\Daemon\FileRepositoryInterface; -use Pterodactyl\Exceptions\Http\Server\FileTypeNotEditableException; -use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException; - -class UpdateFileContentsFormRequest extends ServerFormRequest -{ - /** - * Return the permission string to validate this request against. - * - * @return string - */ - protected function permission(): string - { - return 'edit-files'; - } - - /** - * Authorize a request to edit a file. - * - * @return bool - * - * @throws \Pterodactyl\Exceptions\DisplayException - * @throws \Pterodactyl\Exceptions\Http\Server\FileSizeTooLargeException - * @throws \Pterodactyl\Exceptions\Http\Server\FileTypeNotEditableException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - */ - public function authorize() - { - if (! parent::authorize()) { - return false; - } - - $server = $this->attributes->get('server'); - $token = $this->attributes->get('server_token'); - - return $this->checkFileCanBeEdited($server, $token); - } - - /** - * @return array - */ - public function rules() - { - return []; - } - - /** - * Checks if a given file can be edited by a user on this server. - * - * @param \Pterodactyl\Models\Server $server - * @param string $token - * @return bool - * - * @throws \Pterodactyl\Exceptions\DisplayException - * @throws \Pterodactyl\Exceptions\Http\Server\FileSizeTooLargeException - * @throws \Pterodactyl\Exceptions\Http\Server\FileTypeNotEditableException - */ - private function checkFileCanBeEdited($server, $token) - { - $config = app()->make(Repository::class); - $repository = app()->make(FileRepositoryInterface::class); - - try { - $stats = $repository->setServer($server)->setToken($token)->getFileStat($this->route()->parameter('file')); - } catch (RequestException $exception) { - switch ($exception->getCode()) { - case 404: - throw new NotFoundHttpException; - default: - throw new DaemonConnectionException($exception); - } - } - - if ((! $stats->file && ! $stats->symlink) || ! in_array($stats->mime, $config->get('pterodactyl.files.editable'))) { - throw new FileTypeNotEditableException(trans('server.files.exceptions.invalid_mime')); - } - - if ($stats->size > $config->get('pterodactyl.files.max_edit_size')) { - throw new FileSizeTooLargeException(trans('server.files.exceptions.max_size')); - } - - $this->attributes->set('file_stats', $stats); - - return true; - } -} diff --git a/app/Http/Requests/Server/UpdateStartupParametersFormRequest.php b/app/Http/Requests/Server/UpdateStartupParametersFormRequest.php deleted file mode 100644 index 41c15103..00000000 --- a/app/Http/Requests/Server/UpdateStartupParametersFormRequest.php +++ /dev/null @@ -1,61 +0,0 @@ -user()->can('edit-startup', $this->attributes->get('server')); - } - - /** - * Validate that all of the required fields were passed and that the environment - * variable values meet the defined criteria for those fields. - * - * @return array - */ - public function rules() - { - $repository = $this->container->make(EggVariableRepositoryInterface::class); - - $variables = $repository->getEditableVariables($this->attributes->get('server')->egg_id); - $rules = $variables->mapWithKeys(function ($variable) { - $this->validationAttributes['environment.' . $variable->env_variable] = $variable->name; - - return ['environment.' . $variable->env_variable => $variable->rules]; - })->toArray(); - - return array_merge($rules, [ - 'environment' => 'required|array', - ]); - } - - /** - * Return attributes to provide better naming conventions for error messages. - * - * @return array - */ - public function attributes() - { - return $this->validationAttributes; - } -} diff --git a/app/Http/ViewComposers/AssetComposer.php b/app/Http/ViewComposers/AssetComposer.php new file mode 100644 index 00000000..6da825ad --- /dev/null +++ b/app/Http/ViewComposers/AssetComposer.php @@ -0,0 +1,43 @@ +assetHashService = $assetHashService; + } + + /** + * Provide access to the asset service in the views. + * + * @param \Illuminate\View\View $view + */ + public function compose(View $view) + { + $view->with('asset', $this->assetHashService); + $view->with('siteConfiguration', [ + 'name' => config('app.name') ?? 'Pterodactyl', + 'locale' => config('app.locale') ?? 'en', + 'recaptcha' => [ + 'enabled' => config('recaptcha.enabled', false), + 'siteKey' => config('recaptcha.website_key') ?? '', + ], + 'analytics' => config('app.analytics') ?? '', + ]); + } +} diff --git a/app/Http/ViewComposers/Server/ServerDataComposer.php b/app/Http/ViewComposers/Server/ServerDataComposer.php deleted file mode 100644 index 9e185864..00000000 --- a/app/Http/ViewComposers/Server/ServerDataComposer.php +++ /dev/null @@ -1,38 +0,0 @@ -request = $request; - } - - /** - * Attach server data to a view automatically. - * - * @param \Illuminate\View\View $view - */ - public function compose(View $view) - { - $server = $this->request->get('server'); - - $view->with('server', $server); - $view->with('node', object_get($server, 'node')); - $view->with('daemon_token', $this->request->get('server_token')); - } -} diff --git a/app/Http/ViewComposers/ServerListComposer.php b/app/Http/ViewComposers/ServerListComposer.php deleted file mode 100644 index 4c3ac71f..00000000 --- a/app/Http/ViewComposers/ServerListComposer.php +++ /dev/null @@ -1,51 +0,0 @@ -request = $request; - $this->repository = $repository; - } - - /** - * Attach a list of servers the user can access to the view. - * - * @param \Illuminate\View\View $view - */ - public function compose(View $view) - { - if (! $this->request->user()) { - return; - } - - $servers = $this->repository - ->setColumns(['id', 'owner_id', 'uuidShort', 'name', 'description']) - ->filterUserAccessServers($this->request->user(), User::FILTER_LEVEL_SUBUSER, false); - - $view->with('sidebarServerList', $servers); - } -} diff --git a/app/Jobs/Schedule/RunTaskJob.php b/app/Jobs/Schedule/RunTaskJob.php index 7f206ec8..bab1d61d 100644 --- a/app/Jobs/Schedule/RunTaskJob.php +++ b/app/Jobs/Schedule/RunTaskJob.php @@ -3,122 +3,87 @@ namespace Pterodactyl\Jobs\Schedule; use Exception; -use Cake\Chronos\Chronos; use Pterodactyl\Jobs\Job; +use Carbon\CarbonImmutable; +use Pterodactyl\Models\Task; use InvalidArgumentException; use Illuminate\Queue\SerializesModels; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\DispatchesJobs; -use Pterodactyl\Contracts\Repository\TaskRepositoryInterface; -use Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService; -use Pterodactyl\Contracts\Repository\ScheduleRepositoryInterface; -use Pterodactyl\Contracts\Repository\Daemon\PowerRepositoryInterface; -use Pterodactyl\Contracts\Repository\Daemon\CommandRepositoryInterface; +use Pterodactyl\Repositories\Eloquent\TaskRepository; +use Pterodactyl\Services\Backups\InitiateBackupService; +use Pterodactyl\Repositories\Wings\DaemonPowerRepository; +use Pterodactyl\Repositories\Wings\DaemonCommandRepository; class RunTaskJob extends Job implements ShouldQueue { use DispatchesJobs, InteractsWithQueue, SerializesModels; /** - * @var \Pterodactyl\Contracts\Repository\Daemon\CommandRepositoryInterface - */ - protected $commandRepository; - - /** - * @var \Pterodactyl\Contracts\Repository\Daemon\PowerRepositoryInterface - */ - protected $powerRepository; - - /** - * @var int - */ - public $schedule; - - /** - * @var int + * @var \Pterodactyl\Models\Task */ public $task; - /** - * @var \Pterodactyl\Contracts\Repository\TaskRepositoryInterface - */ - protected $taskRepository; - /** * RunTaskJob constructor. * - * @param int $task - * @param int $schedule + * @param \Pterodactyl\Models\Task $task */ - public function __construct(int $task, int $schedule) + public function __construct(Task $task) { $this->queue = config('pterodactyl.queues.standard'); $this->task = $task; - $this->schedule = $schedule; } /** * Run the job and send actions to the daemon running the server. * - * @param \Pterodactyl\Contracts\Repository\Daemon\CommandRepositoryInterface $commandRepository - * @param \Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService $keyProviderService - * @param \Pterodactyl\Contracts\Repository\Daemon\PowerRepositoryInterface $powerRepository - * @param \Pterodactyl\Contracts\Repository\TaskRepositoryInterface $taskRepository + * @param \Pterodactyl\Repositories\Wings\DaemonCommandRepository $commandRepository + * @param \Pterodactyl\Services\Backups\InitiateBackupService $backupService + * @param \Pterodactyl\Repositories\Wings\DaemonPowerRepository $powerRepository + * @param \Pterodactyl\Repositories\Eloquent\TaskRepository $taskRepository * - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\Daemon\InvalidPowerSignalException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Throwable */ public function handle( - CommandRepositoryInterface $commandRepository, - DaemonKeyProviderService $keyProviderService, - PowerRepositoryInterface $powerRepository, - TaskRepositoryInterface $taskRepository + DaemonCommandRepository $commandRepository, + InitiateBackupService $backupService, + DaemonPowerRepository $powerRepository, + TaskRepository $taskRepository ) { - $this->commandRepository = $commandRepository; - $this->powerRepository = $powerRepository; - $this->taskRepository = $taskRepository; - - $task = $this->taskRepository->getTaskForJobProcess($this->task); - $server = $task->getRelation('server'); - $user = $server->getRelation('user'); - // Do not process a task that is not set to active. - if (! $task->getRelation('schedule')->is_active) { + if (! $this->task->schedule->is_active) { $this->markTaskNotQueued(); $this->markScheduleComplete(); return; } + $server = $this->task->server; // Perform the provided task against the daemon. - switch ($task->action) { + switch ($this->task->action) { case 'power': - $this->powerRepository->setServer($server) - ->setToken($keyProviderService->handle($server, $user)) - ->sendSignal($task->payload); + $powerRepository->setServer($server)->send($this->task->payload); break; case 'command': - $this->commandRepository->setServer($server) - ->setToken($keyProviderService->handle($server, $user)) - ->send($task->payload); + $commandRepository->setServer($server)->send($this->task->payload); + break; + case 'backup': + $backupService->setIgnoredFiles(explode(PHP_EOL, $this->task->payload))->handle($server, null); break; default: throw new InvalidArgumentException('Cannot run a task that points to a non-existent action.'); } $this->markTaskNotQueued(); - $this->queueNextTask($task->sequence_id); + $this->queueNextTask(); } /** * Handle a failure while sending the action to the daemon or otherwise processing the job. * - * @param null|\Exception $exception - * - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @param \Exception|null $exception */ public function failed(Exception $exception = null) { @@ -128,49 +93,41 @@ class RunTaskJob extends Job implements ShouldQueue /** * Get the next task in the schedule and queue it for running after the defined period of wait time. - * - * @param int $sequence - * - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - private function queueNextTask($sequence) + private function queueNextTask() { - $nextTask = $this->taskRepository->getNextTask($this->schedule, $sequence); + /** @var \Pterodactyl\Models\Task|null $nextTask */ + $nextTask = Task::query()->where('schedule_id', $this->task->schedule_id) + ->where('sequence_id', $this->task->sequence_id + 1) + ->first(); + if (is_null($nextTask)) { $this->markScheduleComplete(); return; } - $this->taskRepository->update($nextTask->id, ['is_queued' => true]); - $this->dispatch((new self($nextTask->id, $this->schedule))->delay($nextTask->time_offset)); + $nextTask->update(['is_queued' => true]); + + $this->dispatch((new self($nextTask))->delay($nextTask->time_offset)); } /** * Marks the parent schedule as being complete. - * - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ private function markScheduleComplete() { - $repository = app()->make(ScheduleRepositoryInterface::class); - $repository->withoutFreshModel()->update($this->schedule, [ + $this->task->schedule()->update([ 'is_processing' => false, - 'last_run_at' => Chronos::now()->toDateTimeString(), + 'last_run_at' => CarbonImmutable::now()->toDateTimeString(), ]); } /** * Mark a specific task as no longer being queued. - * - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ private function markTaskNotQueued() { - $repository = app()->make(TaskRepositoryInterface::class); - $repository->update($this->task, ['is_queued' => false]); + $this->task->update(['is_queued' => false]); } } diff --git a/app/Models/Allocation.php b/app/Models/Allocation.php index 5921c0a2..81e59652 100644 --- a/app/Models/Allocation.php +++ b/app/Models/Allocation.php @@ -2,16 +2,25 @@ namespace Pterodactyl\Models; -use Sofa\Eloquence\Eloquence; -use Sofa\Eloquence\Validable; -use Illuminate\Database\Eloquent\Model; -use Sofa\Eloquence\Contracts\CleansAttributes; -use Sofa\Eloquence\Contracts\Validable as ValidableContract; - -class Allocation extends Model implements CleansAttributes, ValidableContract +/** + * @property int $id + * @property int $node_id + * @property string $ip + * @property string|null $ip_alias + * @property int $port + * @property int|null $server_id + * @property string|null $notes + * @property \Carbon\Carbon|null $created_at + * @property \Carbon\Carbon|null $updated_at + * + * @property string $alias + * @property bool $has_alias + * + * @property \Pterodactyl\Models\Server|null $server + * @property \Pterodactyl\Models\Node $node + */ +class Allocation extends Model { - use Eloquence, Validable; - /** * The resource name for this model when it is transformed into an * API representation using fractal. @@ -46,21 +55,13 @@ class Allocation extends Model implements CleansAttributes, ValidableContract /** * @var array */ - protected static $applicationRules = [ - 'node_id' => 'required', - 'ip' => 'required', - 'port' => 'required', - ]; - - /** - * @var array - */ - protected static $dataIntegrityRules = [ - 'node_id' => 'exists:nodes,id', - 'ip' => 'ip', - 'port' => 'numeric|between:1024,65553', + public static $validationRules = [ + 'node_id' => 'required|exists:nodes,id', + 'ip' => 'required|ip', + 'port' => 'required|numeric|between:1024,65553', 'ip_alias' => 'nullable|string', 'server_id' => 'nullable|exists:servers,id', + 'notes' => 'nullable|string|max:256', ]; /** @@ -76,7 +77,7 @@ class Allocation extends Model implements CleansAttributes, ValidableContract /** * Accessor to automatically provide the IP alias if defined. * - * @param null|string $value + * @param string|null $value * @return string */ public function getAliasAttribute($value) @@ -87,7 +88,7 @@ class Allocation extends Model implements CleansAttributes, ValidableContract /** * Accessor to quickly determine if this allocation has an alias. * - * @param null|string $value + * @param string|null $value * @return bool */ public function getHasAliasAttribute($value) diff --git a/app/Models/ApiKey.php b/app/Models/ApiKey.php index 797522c3..072a74f9 100644 --- a/app/Models/ApiKey.php +++ b/app/Models/ApiKey.php @@ -2,16 +2,23 @@ namespace Pterodactyl\Models; -use Sofa\Eloquence\Eloquence; -use Sofa\Eloquence\Validable; -use Illuminate\Database\Eloquent\Model; use Pterodactyl\Services\Acl\Api\AdminAcl; -use Sofa\Eloquence\Contracts\CleansAttributes; -use Sofa\Eloquence\Contracts\Validable as ValidableContract; -class ApiKey extends Model implements CleansAttributes, ValidableContract +/** + * @property int $id + * @property int $user_id + * @property int $key_type + * @property string $identifier + * @property string $token + * @property array $allowed_ips + * @property string $memo + * @property \Carbon\Carbon|null $last_used_at + * @property \Carbon\Carbon $created_at + * @property \Carbon\Carbon $updated_at + */ +class ApiKey extends Model { - use Eloquence, Validable; + const RESOURCE_NAME = 'api_key'; /** * Different API keys that can exist on the system. @@ -46,7 +53,7 @@ class ApiKey extends Model implements CleansAttributes, ValidableContract * @var array */ protected $casts = [ - 'allowed_ips' => 'json', + 'allowed_ips' => 'array', 'user_id' => 'int', 'r_' . AdminAcl::RESOURCE_USERS => 'int', 'r_' . AdminAcl::RESOURCE_ALLOCATIONS => 'int', @@ -56,7 +63,6 @@ class ApiKey extends Model implements CleansAttributes, ValidableContract 'r_' . AdminAcl::RESOURCE_LOCATIONS => 'int', 'r_' . AdminAcl::RESOURCE_NESTS => 'int', 'r_' . AdminAcl::RESOURCE_NODES => 'int', - 'r_' . AdminAcl::RESOURCE_PACKS => 'int', 'r_' . AdminAcl::RESOURCE_SERVERS => 'int', ]; @@ -81,31 +87,19 @@ class ApiKey extends Model implements CleansAttributes, ValidableContract */ protected $hidden = ['token']; - /** - * Rules defining what fields must be passed when making a model. - * - * @var array - */ - protected static $applicationRules = [ - 'identifier' => 'required', - 'memo' => 'required', - 'user_id' => 'required', - 'token' => 'required', - 'key_type' => 'present', - ]; - /** * Rules to protect against invalid data entry to DB. * * @var array */ - protected static $dataIntegrityRules = [ - 'user_id' => 'exists:users,id', - 'key_type' => 'integer|min:0|max:4', - 'identifier' => 'string|size:16|unique:api_keys,identifier', - 'token' => 'string', - 'memo' => 'nullable|string|max:500', - 'allowed_ips' => 'nullable|json', + public static $validationRules = [ + 'user_id' => 'required|exists:users,id', + 'key_type' => 'present|integer|min:0|max:4', + 'identifier' => 'required|string|size:16|unique:api_keys,identifier', + 'token' => 'required|string', + 'memo' => 'required|nullable|string|max:500', + 'allowed_ips' => 'nullable|array', + 'allowed_ips.*' => 'string', 'last_used_at' => 'nullable|date', 'r_' . AdminAcl::RESOURCE_USERS => 'integer|min:0|max:3', 'r_' . AdminAcl::RESOURCE_ALLOCATIONS => 'integer|min:0|max:3', @@ -115,7 +109,6 @@ class ApiKey extends Model implements CleansAttributes, ValidableContract 'r_' . AdminAcl::RESOURCE_LOCATIONS => 'integer|min:0|max:3', 'r_' . AdminAcl::RESOURCE_NESTS => 'integer|min:0|max:3', 'r_' . AdminAcl::RESOURCE_NODES => 'integer|min:0|max:3', - 'r_' . AdminAcl::RESOURCE_PACKS => 'integer|min:0|max:3', 'r_' . AdminAcl::RESOURCE_SERVERS => 'integer|min:0|max:3', ]; diff --git a/app/Models/Backup.php b/app/Models/Backup.php new file mode 100644 index 00000000..5a8ab28e --- /dev/null +++ b/app/Models/Backup.php @@ -0,0 +1,90 @@ + 'int', + 'is_successful' => 'bool', + 'bytes' => 'int', + 'ignored_files' => 'array', + ]; + + /** + * @var array + */ + protected $dates = [ + 'completed_at', + ]; + + /** + * @var array + */ + protected $attributes = [ + 'is_successful' => true, + 'checksum' => null, + 'bytes' => 0, + ]; + + /** + * @var array + */ + public static $validationRules = [ + 'server_id' => 'bail|required|numeric|exists:servers,id', + 'uuid' => 'required|uuid', + 'is_successful' => 'boolean', + 'name' => 'required|string', + 'ignored_files' => 'array', + 'disk' => 'required|string', + 'checksum' => 'nullable|string', + 'bytes' => 'numeric', + ]; + + /** + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function server() + { + return $this->belongsTo(Server::class); + } +} diff --git a/app/Models/DaemonKey.php b/app/Models/DaemonKey.php deleted file mode 100644 index c4c2940a..00000000 --- a/app/Models/DaemonKey.php +++ /dev/null @@ -1,93 +0,0 @@ - 'integer', - 'server_id' => 'integer', - ]; - - /** - * @var array - */ - protected $dates = [ - self::CREATED_AT, - self::UPDATED_AT, - 'expires_at', - ]; - - /** - * @var array - */ - protected $fillable = ['user_id', 'server_id', 'secret', 'expires_at']; - - /** - * @var array - */ - protected static $applicationRules = [ - 'user_id' => 'required', - 'server_id' => 'required', - 'secret' => 'required', - 'expires_at' => 'required', - ]; - - /** - * @var array - */ - protected static $dataIntegrityRules = [ - 'user_id' => 'numeric|exists:users,id', - 'server_id' => 'numeric|exists:servers,id', - 'secret' => 'string|min:20', - 'expires_at' => 'date', - ]; - - /** - * Return the server relation. - * - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo - */ - public function server() - { - return $this->belongsTo(Server::class); - } - - /** - * Return the node relation. - * - * @return \Znck\Eloquent\Relations\BelongsToThrough - * @throws \Exception - */ - public function node() - { - return $this->belongsToThrough(Node::class, Server::class); - } - - /** - * Return the user relation. - * - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo - */ - public function user() - { - return $this->belongsTo(User::class); - } -} diff --git a/app/Models/Database.php b/app/Models/Database.php index 9ff1d8c1..8e66219f 100644 --- a/app/Models/Database.php +++ b/app/Models/Database.php @@ -2,16 +2,23 @@ namespace Pterodactyl\Models; -use Sofa\Eloquence\Eloquence; -use Sofa\Eloquence\Validable; -use Illuminate\Database\Eloquent\Model; -use Sofa\Eloquence\Contracts\CleansAttributes; -use Sofa\Eloquence\Contracts\Validable as ValidableContract; - -class Database extends Model implements CleansAttributes, ValidableContract +/** + * @property int $id + * @property int $server_id + * @property int $database_host_id + * @property string $database + * @property string $username + * @property string $remote + * @property string $password + * @property int $max_connections + * @property \Carbon\Carbon $created_at + * @property \Carbon\Carbon $updated_at + * + * @property \Pterodactyl\Models\Server $server + * @property \Pterodactyl\Models\DatabaseHost $host + */ +class Database extends Model { - use Eloquence, Validable; - /** * The resource name for this model when it is transformed into an * API representation using fractal. @@ -38,7 +45,7 @@ class Database extends Model implements CleansAttributes, ValidableContract * @var array */ protected $fillable = [ - 'server_id', 'database_host_id', 'database', 'username', 'password', 'remote', + 'server_id', 'database_host_id', 'database', 'username', 'password', 'remote', 'max_connections', ]; /** @@ -49,21 +56,19 @@ class Database extends Model implements CleansAttributes, ValidableContract protected $casts = [ 'server_id' => 'integer', 'database_host_id' => 'integer', + 'max_connections' => 'integer', ]; - protected static $applicationRules = [ - 'server_id' => 'required', - 'database_host_id' => 'required', - 'database' => 'required', - 'remote' => 'required', - ]; - - protected static $dataIntegrityRules = [ - 'server_id' => 'numeric|exists:servers,id', - 'database_host_id' => 'exists:database_hosts,id', - 'database' => 'string|alpha_dash|between:3,100', + /** + * @var array + */ + public static $validationRules = [ + 'server_id' => 'required|numeric|exists:servers,id', + 'database_host_id' => 'required|exists:database_hosts,id', + 'database' => 'required|string|alpha_dash|between:3,48', 'username' => 'string|alpha_dash|between:3,100', - 'remote' => 'string|regex:/^[0-9%.]{1,15}$/', + 'max_connections' => 'nullable|integer', + 'remote' => 'required|string|regex:/^[0-9%.]{1,15}$/', 'password' => 'string', ]; diff --git a/app/Models/DatabaseHost.php b/app/Models/DatabaseHost.php index f48977b1..750ca0de 100644 --- a/app/Models/DatabaseHost.php +++ b/app/Models/DatabaseHost.php @@ -2,16 +2,8 @@ namespace Pterodactyl\Models; -use Sofa\Eloquence\Eloquence; -use Sofa\Eloquence\Validable; -use Illuminate\Database\Eloquent\Model; -use Sofa\Eloquence\Contracts\CleansAttributes; -use Sofa\Eloquence\Contracts\Validable as ValidableContract; - -class DatabaseHost extends Model implements CleansAttributes, ValidableContract +class DatabaseHost extends Model { - use Eloquence, Validable; - /** * The resource name for this model when it is transformed into an * API representation using fractal. @@ -52,31 +44,18 @@ class DatabaseHost extends Model implements CleansAttributes, ValidableContract 'node_id' => 'integer', ]; - /** - * Application validation rules. - * - * @var array - */ - protected static $applicationRules = [ - 'name' => 'required', - 'host' => 'required', - 'port' => 'required', - 'username' => 'required', - 'node_id' => 'sometimes', - ]; - /** * Validation rules to assign to this model. * * @var array */ - protected static $dataIntegrityRules = [ - 'name' => 'string|max:255', - 'host' => 'unique:database_hosts,host', - 'port' => 'numeric|between:1,65535', - 'username' => 'string|max:32', + public static $validationRules = [ + 'name' => 'required|string|max:191', + 'host' => 'required|string', + 'port' => 'required|numeric|between:1,65535', + 'username' => 'required|string|max:32', 'password' => 'nullable|string', - 'node_id' => 'nullable|integer|exists:nodes,id', + 'node_id' => 'sometimes|nullable|integer|exists:nodes,id', ]; /** diff --git a/app/Models/Egg.php b/app/Models/Egg.php index ade28239..4aa33bef 100644 --- a/app/Models/Egg.php +++ b/app/Models/Egg.php @@ -2,16 +2,44 @@ namespace Pterodactyl\Models; -use Sofa\Eloquence\Eloquence; -use Sofa\Eloquence\Validable; -use Illuminate\Database\Eloquent\Model; -use Sofa\Eloquence\Contracts\CleansAttributes; -use Sofa\Eloquence\Contracts\Validable as ValidableContract; - -class Egg extends Model implements CleansAttributes, ValidableContract +/** + * @property int $id + * @property string $uuid + * @property int $nest_id + * @property string $author + * @property string $name + * @property string|null $description + * @property string $docker_image + * @property string|null $config_files + * @property string|null $config_startup + * @property string|null $config_logs + * @property string|null $config_stop + * @property int|null $config_from + * @property string|null $startup + * @property bool $script_is_privileged + * @property string|null $script_install + * @property string $script_entry + * @property string $script_container + * @property int|null $copy_script_from + * @property \Carbon\Carbon $created_at + * @property \Carbon\Carbon $updated_at + * + * @property string|null $copy_script_install + * @property string $copy_script_entry + * @property string $copy_script_container + * @property string|null $inherit_config_files + * @property string|null $inherit_config_startup + * @property string|null $inherit_config_logs + * @property string|null $inherit_config_stop + * + * @property \Pterodactyl\Models\Nest $nest + * @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\Server[] $servers + * @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\EggVariable[] $variables + * @property \Pterodactyl\Models\Egg|null $scriptFrom + * @property \Pterodactyl\Models\Egg|null $configFrom + */ +class Egg extends Model { - use Eloquence, Validable; - /** * The resource name for this model when it is transformed into an * API representation using fractal. @@ -62,37 +90,19 @@ class Egg extends Model implements CleansAttributes, ValidableContract /** * @var array */ - protected static $applicationRules = [ - 'nest_id' => 'required', - 'uuid' => 'required', - 'name' => 'required', - 'description' => 'required', - 'author' => 'required', - 'docker_image' => 'required', - 'startup' => 'required', - 'config_from' => 'sometimes', - 'config_stop' => 'required_without:config_from', - 'config_startup' => 'required_without:config_from', - 'config_logs' => 'required_without:config_from', - 'config_files' => 'required_without:config_from', - ]; - - /** - * @var array - */ - protected static $dataIntegrityRules = [ - 'nest_id' => 'bail|numeric|exists:nests,id', - 'uuid' => 'string|size:36', - 'name' => 'string|max:255', - 'description' => 'string', - 'author' => 'string|email', - 'docker_image' => 'string|max:255', - 'startup' => 'nullable|string', - 'config_from' => 'bail|nullable|numeric|exists:eggs,id', - 'config_stop' => 'nullable|string|max:255', - 'config_startup' => 'nullable|json', - 'config_logs' => 'nullable|json', - 'config_files' => 'nullable|json', + public static $validationRules = [ + 'nest_id' => 'required|bail|numeric|exists:nests,id', + 'uuid' => 'required|string|size:36', + 'name' => 'required|string|max:191', + 'description' => 'string|nullable', + 'author' => 'required|string|email', + 'docker_image' => 'required|string|max:191', + 'startup' => 'required|nullable|string', + 'config_from' => 'sometimes|bail|nullable|numeric|exists:eggs,id', + 'config_stop' => 'required_without:config_from|nullable|string|max:191', + 'config_startup' => 'required_without:config_from|nullable|json', + 'config_logs' => 'required_without:config_from|nullable|json', + 'config_files' => 'required_without:config_from|nullable|json', ]; /** @@ -236,16 +246,6 @@ class Egg extends Model implements CleansAttributes, ValidableContract return $this->hasMany(EggVariable::class, 'egg_id'); } - /** - * Gets all packs associated with this egg. - * - * @return \Illuminate\Database\Eloquent\Relations\HasMany - */ - public function packs() - { - return $this->hasMany(Pack::class, 'egg_id'); - } - /** * Get the parent egg from which to copy scripts. * diff --git a/app/Models/EggMount.php b/app/Models/EggMount.php new file mode 100644 index 00000000..cd85673c --- /dev/null +++ b/app/Models/EggMount.php @@ -0,0 +1,21 @@ + 'integer', - 'user_viewable' => 'integer', - 'user_editable' => 'integer', + 'user_viewable' => 'bool', + 'user_editable' => 'bool', ]; /** * @var array */ - protected static $applicationRules = [ - 'name' => 'required', - 'env_variable' => 'required', - 'rules' => 'required', - ]; - - /** - * @var array - */ - protected static $dataIntegrityRules = [ + public static $validationRules = [ 'egg_id' => 'exists:eggs,id', - 'name' => 'string|between:1,255', + 'name' => 'required|string|between:1,191', 'description' => 'string', - 'env_variable' => 'regex:/^[\w]{1,255}$/|notIn:' . self::RESERVED_ENV_NAMES, + 'env_variable' => 'required|regex:/^[\w]{1,191}$/|notIn:' . self::RESERVED_ENV_NAMES, 'default_value' => 'string', 'user_viewable' => 'boolean', 'user_editable' => 'boolean', - 'rules' => 'string', + 'rules' => 'required|string', ]; /** @@ -82,12 +91,19 @@ class EggVariable extends Model implements CleansAttributes, ValidableContract ]; /** - * @param $value * @return bool */ - public function getRequiredAttribute($value) + public function getRequiredAttribute() { - return $this->rules === 'required' || str_contains($this->rules, ['required|', '|required']); + return in_array('required', explode('|', $this->rules)); + } + + /** + * @return \Illuminate\Database\Eloquent\Relations\HasOne + */ + public function egg() + { + return $this->hasOne(Egg::class); } /** diff --git a/app/Models/Location.php b/app/Models/Location.php index 10fff147..74fed181 100644 --- a/app/Models/Location.php +++ b/app/Models/Location.php @@ -2,16 +2,18 @@ namespace Pterodactyl\Models; -use Sofa\Eloquence\Eloquence; -use Sofa\Eloquence\Validable; -use Illuminate\Database\Eloquent\Model; -use Sofa\Eloquence\Contracts\CleansAttributes; -use Sofa\Eloquence\Contracts\Validable as ValidableContract; - -class Location extends Model implements CleansAttributes, ValidableContract +/** + * @property int $id + * @property string $short + * @property string $long + * @property \Carbon\Carbon $created_at + * @property \Carbon\Carbon $updated_at + * + * @property \Pterodactyl\Models\Node[] $nodes + * @property \Pterodactyl\Models\Server[] $servers + */ +class Location extends Model { - use Eloquence, Validable; - /** * The resource name for this model when it is transformed into an * API representation using fractal. @@ -32,24 +34,14 @@ class Location extends Model implements CleansAttributes, ValidableContract */ protected $guarded = ['id', 'created_at', 'updated_at']; - /** - * Validation rules to apply to this model. - * - * @var array - */ - protected static $applicationRules = [ - 'short' => 'required', - 'long' => 'required', - ]; - /** * Rules ensuring that the raw data stored in the database meets expectations. * * @var array */ - protected static $dataIntegrityRules = [ - 'short' => 'string|between:1,60|unique:locations,short', - 'long' => 'string|between:1,255', + public static $validationRules = [ + 'short' => 'required|string|between:1,60|unique:locations,short', + 'long' => 'string|nullable|between:1,191', ]; /** diff --git a/app/Models/Model.php b/app/Models/Model.php new file mode 100644 index 00000000..f6b94a3a --- /dev/null +++ b/app/Models/Model.php @@ -0,0 +1,178 @@ +make(Factory::class); + + static::saving(function (Model $model) { + if (! $model->validate()) { + throw new DataValidationException($model->getValidator()); + } + + return true; + }); + } + + /** + * Set the model to skip validation when saving. + * + * @return $this + */ + public function skipValidation() + { + $this->skipValidation = true; + + return $this; + } + + /** + * Returns the validator instance used by this model. + * + * @return \Illuminate\Validation\Validator|\Illuminate\Contracts\Validation\Validator + */ + public function getValidator() + { + $rules = $this->getKey() ? static::getRulesForUpdate($this) : static::getRules(); + + return $this->validator ?: $this->validator = static::$validatorFactory->make( + [], $rules, [], [] + ); + } + + /** + * Returns the rules associated with this model. + * + * @return array + */ + public static function getRules() + { + $rules = static::$validationRules; + foreach ($rules as $key => &$rule) { + $rule = is_array($rule) ? $rule : explode('|', $rule); + } + + return $rules; + } + + /** + * Returns the rules associated with the model, specifically for updating the given model + * rather than just creating it. + * + * @param \Illuminate\Database\Eloquent\Model|int|string $id + * @param string $primaryKey + * @return array + */ + public static function getRulesForUpdate($id, string $primaryKey = 'id') + { + if ($id instanceof Model) { + [$primaryKey, $id] = [$id->getKeyName(), $id->getKey()]; + } + + $rules = static::getRules(); + foreach ($rules as $key => &$data) { + // For each rule in a given field, iterate over it and confirm if the rule + // is one for a unique field. If that is the case, append the ID of the current + // working model so we don't run into errors due to the way that field validation + // works. + foreach ($data as &$datum) { + if (! is_string($datum) || ! Str::startsWith($datum, 'unique')) { + continue; + } + + [, $args] = explode(':', $datum); + $args = explode(',', $args); + + $datum = Rule::unique($args[0], $args[1] ?? $key)->ignore($id, $primaryKey)->__toString(); + } + } + + return $rules; + } + + /** + * Determines if the model is in a valid state or not. + * + * @return bool + */ + public function validate() + { + if ($this->skipValidation) { + return true; + } + + return $this->getValidator()->setData( + // Trying to do self::toArray() here will leave out keys based on the whitelist/blacklist + // for that model. Doing this will return all of the attributes in a format that can + // properly be validated. + $this->addCastAttributesToArray( + $this->getAttributes(), $this->getMutatedAttributes() + ) + )->passes(); + } + + /** + * Return a timestamp as DateTime object. + * + * @param mixed $value + * @return \Illuminate\Support\Carbon|\Carbon\CarbonImmutable + */ + protected function asDateTime($value) + { + if (! $this->immutableDates) { + return parent::asDateTime($value); + } + + return parent::asDateTime($value)->toImmutable(); + } +} diff --git a/app/Models/Mount.php b/app/Models/Mount.php new file mode 100644 index 00000000..b69c0c78 --- /dev/null +++ b/app/Models/Mount.php @@ -0,0 +1,102 @@ + 'int', + 'read_only' => 'bool', + 'user_mountable' => 'bool', + ]; + + /** + * Rules verifying that the data being stored matches the expectations of the database. + * + * @var string + */ + public static $validationRules = [ + 'name' => 'required|string|min:2|max:64|unique:mounts,name', + 'description' => 'nullable|string|max:191', + 'source' => 'required|string', + 'target' => 'required|string', + 'read_only' => 'sometimes|boolean', + 'user_mountable' => 'sometimes|boolean', + ]; + + /** + * Disable timestamps on this model. + * + * @var bool + */ + public $timestamps = false; + + /** + * Returns all eggs that have this mount assigned. + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany + */ + public function eggs() + { + return $this->belongsToMany(Egg::class); + } + + /** + * Returns all nodes that have this mount assigned. + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany + */ + public function nodes() + { + return $this->belongsToMany(Node::class); + } + + /** + * Returns all servers that have this mount assigned. + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany + */ + public function servers() + { + return $this->belongsToMany(Server::class); + } +} diff --git a/app/Models/MountNode.php b/app/Models/MountNode.php new file mode 100644 index 00000000..a897dd6d --- /dev/null +++ b/app/Models/MountNode.php @@ -0,0 +1,23 @@ + 'required', - 'name' => 'required', - 'description' => 'sometimes', - ]; - - /** - * @var array - */ - protected static $dataIntegrityRules = [ - 'author' => 'string|email', - 'name' => 'string|max:255', + public static $validationRules = [ + 'author' => 'required|string|email', + 'name' => 'required|string|max:191', 'description' => 'nullable|string', ]; @@ -63,16 +58,6 @@ class Nest extends Model implements CleansAttributes, ValidableContract return $this->hasMany(Egg::class); } - /** - * Returns all of the packs associated with a nest, regardless of the egg. - * - * @return \Illuminate\Database\Eloquent\Relations\HasManyThrough - */ - public function packs() - { - return $this->hasManyThrough(Pack::class, Egg::class, 'nest_id', 'egg_id'); - } - /** * Gets all servers associated with this nest. * diff --git a/app/Models/Node.php b/app/Models/Node.php index 1a412be9..8258ae86 100644 --- a/app/Models/Node.php +++ b/app/Models/Node.php @@ -2,16 +2,43 @@ namespace Pterodactyl\Models; -use Sofa\Eloquence\Eloquence; -use Sofa\Eloquence\Validable; -use Illuminate\Database\Eloquent\Model; +use Symfony\Component\Yaml\Yaml; +use Illuminate\Container\Container; use Illuminate\Notifications\Notifiable; -use Sofa\Eloquence\Contracts\CleansAttributes; -use Sofa\Eloquence\Contracts\Validable as ValidableContract; +use Illuminate\Contracts\Encryption\Encrypter; -class Node extends Model implements CleansAttributes, ValidableContract +/** + * @property int $id + * @property string $uuid + * @property bool $public + * @property string $name + * @property string|null $description + * @property int $location_id + * @property string $fqdn + * @property string $scheme + * @property bool $behind_proxy + * @property bool $maintenance_mode + * @property int $memory + * @property int $memory_overallocate + * @property int $disk + * @property int $disk_overallocate + * @property int $upload_size + * @property string $daemon_token_id + * @property string $daemon_token + * @property int $daemonListen + * @property int $daemonSFTP + * @property string $daemonBase + * @property \Carbon\Carbon $created_at + * @property \Carbon\Carbon $updated_at + * + * @property \Pterodactyl\Models\Location $location + * @property \Pterodactyl\Models\Mount[]|\Illuminate\Database\Eloquent\Collection $mounts + * @property \Pterodactyl\Models\Server[]|\Illuminate\Database\Eloquent\Collection $servers + * @property \Pterodactyl\Models\Allocation[]|\Illuminate\Database\Eloquent\Collection $allocations + */ +class Node extends Model { - use Eloquence, Notifiable, Validable; + use Notifiable; /** * The resource name for this model when it is transformed into an @@ -19,7 +46,8 @@ class Node extends Model implements CleansAttributes, ValidableContract */ const RESOURCE_NAME = 'node'; - const DAEMON_SECRET_LENGTH = 36; + const DAEMON_TOKEN_ID_LENGTH = 16; + const DAEMON_TOKEN_LENGTH = 64; /** * The table associated with the model. @@ -33,7 +61,7 @@ class Node extends Model implements CleansAttributes, ValidableContract * * @var array */ - protected $hidden = ['daemonSecret']; + protected $hidden = ['daemon_token_id', 'daemon_token']; /** * Cast values to correct type. @@ -60,58 +88,29 @@ class Node extends Model implements CleansAttributes, ValidableContract 'public', 'name', 'location_id', 'fqdn', 'scheme', 'behind_proxy', 'memory', 'memory_overallocate', 'disk', - 'disk_overallocate', 'upload_size', - 'daemonSecret', 'daemonBase', + 'disk_overallocate', 'upload_size', 'daemonBase', 'daemonSFTP', 'daemonListen', 'description', 'maintenance_mode', ]; /** - * Fields that are searchable. - * * @var array */ - protected $searchableColumns = [ - 'name' => 10, - 'fqdn' => 8, - 'location.short' => 4, - 'location.long' => 4, - ]; - - /** - * @var array - */ - protected static $applicationRules = [ - 'name' => 'required', - 'location_id' => 'required', - 'fqdn' => 'required', - 'scheme' => 'required', - 'memory' => 'required', - 'memory_overallocate' => 'required', - 'disk' => 'required', - 'disk_overallocate' => 'required', - 'daemonBase' => 'sometimes|required', - 'daemonSFTP' => 'required', - 'daemonListen' => 'required', - ]; - - /** - * @var array - */ - protected static $dataIntegrityRules = [ - 'name' => 'regex:/^([\w .-]{1,100})$/', - 'description' => 'string', - 'location_id' => 'exists:locations,id', + public static $validationRules = [ + 'name' => 'required|regex:/^([\w .-]{1,100})$/', + 'description' => 'string|nullable', + 'location_id' => 'required|exists:locations,id', 'public' => 'boolean', - 'fqdn' => 'string', + 'fqdn' => 'required|string', + 'scheme' => 'required', 'behind_proxy' => 'boolean', - 'memory' => 'numeric|min:1', - 'memory_overallocate' => 'numeric|min:-1', - 'disk' => 'numeric|min:1', - 'disk_overallocate' => 'numeric|min:-1', - 'daemonBase' => 'regex:/^([\/][\d\w.\-\/]+)$/', - 'daemonSFTP' => 'numeric|between:1,65535', - 'daemonListen' => 'numeric|between:1,65535', + 'memory' => 'required|numeric|min:1', + 'memory_overallocate' => 'required|numeric|min:-1', + 'disk' => 'required|numeric|min:1', + 'disk_overallocate' => 'required|numeric|min:-1', + 'daemonBase' => 'sometimes|required|regex:/^([\/][\d\w.\-\/]+)$/', + 'daemonSFTP' => 'required|numeric|between:1,65535', + 'daemonListen' => 'required|numeric|between:1,65535', 'maintenance_mode' => 'boolean', 'upload_size' => 'int|between:1,1024', ]; @@ -126,80 +125,94 @@ class Node extends Model implements CleansAttributes, ValidableContract 'behind_proxy' => false, 'memory_overallocate' => 0, 'disk_overallocate' => 0, - 'daemonBase' => '/srv/daemon-data', + 'daemonBase' => '/var/lib/pterodactyl/volumes', 'daemonSFTP' => 2022, 'daemonListen' => 8080, 'maintenance_mode' => false, ]; + /** + * Get the connection address to use when making calls to this node. + * + * @return string + */ + public function getConnectionAddress(): string + { + return sprintf('%s://%s:%s', $this->scheme, $this->fqdn, $this->daemonListen); + } + + /** + * Returns the configuration as an array. + * + * @return array + */ + public function getConfiguration() + { + return [ + 'debug' => false, + 'uuid' => $this->uuid, + 'token_id' => $this->daemon_token_id, + 'token' => Container::getInstance()->make(Encrypter::class)->decrypt($this->daemon_token), + 'api' => [ + 'host' => '0.0.0.0', + 'port' => $this->daemonListen, + 'ssl' => [ + 'enabled' => (! $this->behind_proxy && $this->scheme === 'https'), + 'cert' => '/etc/letsencrypt/live/' . $this->fqdn . '/fullchain.pem', + 'key' => '/etc/letsencrypt/live/' . $this->fqdn . '/privkey.pem', + ], + 'upload_limit' => $this->upload_size, + ], + 'system' => [ + 'data' => $this->daemonBase, + 'sftp' => [ + 'bind_port' => $this->daemonSFTP, + ], + ], + 'allowed_mounts' => $this->mounts->pluck('source')->toArray(), + 'remote' => route('index'), + ]; + } + + /** + * Returns the configuration in Yaml format. + * + * @return string + */ + public function getYamlConfiguration() + { + return Yaml::dump($this->getConfiguration(), 4, 2, Yaml::DUMP_EMPTY_ARRAY_AS_SEQUENCE); + } + /** * Returns the configuration in JSON format. * * @param bool $pretty * @return string */ - public function getConfigurationAsJson($pretty = false) + public function getJsonConfiguration(bool $pretty = false) { - $config = [ - 'web' => [ - 'host' => '0.0.0.0', - 'listen' => $this->daemonListen, - 'ssl' => [ - 'enabled' => (! $this->behind_proxy && $this->scheme === 'https'), - 'certificate' => '/etc/letsencrypt/live/' . $this->fqdn . '/fullchain.pem', - 'key' => '/etc/letsencrypt/live/' . $this->fqdn . '/privkey.pem', - ], - ], - 'docker' => [ - 'container' => [ - 'user' => null, - ], - 'network' => [ - 'name' => 'pterodactyl_nw', - ], - 'socket' => '/var/run/docker.sock', - 'autoupdate_images' => true, - ], - 'filesystem' => [ - 'server_logs' => '/tmp/pterodactyl', - ], - 'internals' => [ - 'disk_use_seconds' => 30, - 'set_permissions_on_boot' => true, - 'throttle' => [ - 'enabled' => true, - 'kill_at_count' => 5, - 'decay' => 10, - 'lines' => 1000, - 'check_interval_ms' => 100, - ], - ], - 'sftp' => [ - 'path' => $this->daemonBase, - 'ip' => '0.0.0.0', - 'port' => $this->daemonSFTP, - 'keypair' => [ - 'bits' => 2048, - 'e' => 65537, - ], - ], - 'logger' => [ - 'path' => 'logs/', - 'src' => false, - 'level' => 'info', - 'period' => '1d', - 'count' => 3, - ], - 'remote' => [ - 'base' => route('index'), - ], - 'uploads' => [ - 'size_limit' => $this->upload_size, - ], - 'keys' => [$this->daemonSecret], - ]; + return json_encode($this->getConfiguration(), $pretty ? JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT : JSON_UNESCAPED_SLASHES); + } - return json_encode($config, ($pretty) ? JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT : JSON_UNESCAPED_SLASHES); + /** + * Helper function to return the decrypted key for a node. + * + * @return string + */ + public function getDecryptedKey(): string + { + return (string)Container::getInstance()->make(Encrypter::class)->decrypt( + $this->daemon_token + ); + } + + /** + * @return \Illuminate\Database\Eloquent\Relations\HasManyThrough + */ + public function mounts() + { + return $this->hasManyThrough(Mount::class, MountNode::class, 'node_id', 'id', 'id', 'mount_id'); } /** @@ -231,4 +244,19 @@ class Node extends Model implements CleansAttributes, ValidableContract { return $this->hasMany(Allocation::class); } + + /** + * Returns a boolean if the node is viable for an additional server to be placed on it. + * + * @param int $memory + * @param int $disk + * @return bool + */ + public function isViable(int $memory, int $disk): bool + { + $memoryLimit = $this->memory * (1 + ($this->memory_overallocate / 100)); + $diskLimit = $this->disk * (1 + ($this->disk_overallocate / 100)); + + return ($this->sum_memory + $memory) <= $memoryLimit && ($this->sum_disk + $disk) <= $diskLimit; + } } diff --git a/app/Models/Pack.php b/app/Models/Pack.php deleted file mode 100644 index 657d2f1d..00000000 --- a/app/Models/Pack.php +++ /dev/null @@ -1,107 +0,0 @@ - 'required', - 'version' => 'required', - 'description' => 'sometimes', - 'selectable' => 'sometimes|required', - 'visible' => 'sometimes|required', - 'locked' => 'sometimes|required', - 'egg_id' => 'required', - ]; - - /** - * @var array - */ - protected static $dataIntegrityRules = [ - 'name' => 'string', - 'version' => 'string', - 'description' => 'nullable|string', - 'selectable' => 'boolean', - 'visible' => 'boolean', - 'locked' => 'boolean', - 'egg_id' => 'exists:eggs,id', - ]; - - /** - * Cast values to correct type. - * - * @var array - */ - protected $casts = [ - 'egg_id' => 'integer', - 'selectable' => 'boolean', - 'visible' => 'boolean', - 'locked' => 'boolean', - ]; - - /** - * Parameters for search querying. - * - * @var array - */ - protected $searchableColumns = [ - 'name' => 10, - 'uuid' => 8, - 'egg.name' => 6, - 'egg.docker_image' => 5, - 'version' => 2, - ]; - - /** - * Gets egg associated with a service pack. - * - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo - */ - public function egg() - { - return $this->belongsTo(Egg::class); - } - - /** - * Gets servers associated with a pack. - * - * @return \Illuminate\Database\Eloquent\Relations\HasMany - */ - public function servers() - { - return $this->hasMany(Server::class); - } -} diff --git a/app/Models/Permission.php b/app/Models/Permission.php index 5d9ea6c7..96429f31 100644 --- a/app/Models/Permission.php +++ b/app/Models/Permission.php @@ -2,22 +2,66 @@ namespace Pterodactyl\Models; -use Sofa\Eloquence\Eloquence; -use Sofa\Eloquence\Validable; -use Illuminate\Database\Eloquent\Model; -use Sofa\Eloquence\Contracts\CleansAttributes; -use Sofa\Eloquence\Contracts\Validable as ValidableContract; +use Illuminate\Support\Collection; -class Permission extends Model implements CleansAttributes, ValidableContract +class Permission extends Model { - use Eloquence, Validable; - /** * The resource name for this model when it is transformed into an * API representation using fractal. */ const RESOURCE_NAME = 'subuser_permission'; + /** + * Constants defining different permissions available. + */ + const ACTION_WEBSOCKET_CONNECT = 'websocket.connect'; + const ACTION_CONTROL_CONSOLE = 'control.console'; + const ACTION_CONTROL_START = 'control.start'; + const ACTION_CONTROL_STOP = 'control.stop'; + const ACTION_CONTROL_RESTART = 'control.restart'; + + const ACTION_DATABASE_READ = 'database.read'; + const ACTION_DATABASE_CREATE = 'database.create'; + const ACTION_DATABASE_UPDATE = 'database.update'; + const ACTION_DATABASE_DELETE = 'database.delete'; + const ACTION_DATABASE_VIEW_PASSWORD = 'database.view_password'; + + const ACTION_SCHEDULE_READ = 'schedule.read'; + const ACTION_SCHEDULE_CREATE = 'schedule.create'; + const ACTION_SCHEDULE_UPDATE = 'schedule.update'; + const ACTION_SCHEDULE_DELETE = 'schedule.delete'; + + const ACTION_USER_READ = 'user.read'; + const ACTION_USER_CREATE = 'user.create'; + const ACTION_USER_UPDATE = 'user.update'; + const ACTION_USER_DELETE = 'user.delete'; + + const ACTION_BACKUP_READ = 'backup.read'; + const ACTION_BACKUP_CREATE = 'backup.create'; + const ACTION_BACKUP_UPDATE = 'backup.update'; + const ACTION_BACKUP_DELETE = 'backup.delete'; + const ACTION_BACKUP_DOWNLOAD = 'backup.download'; + + const ACTION_ALLOCATION_READ = 'allocation.read'; + const ACTION_ALLOCATION_CREATE = 'allocation.create'; + const ACTION_ALLOCATION_UPDATE = 'allocation.update'; + const ACTION_ALLOCATION_DELETE = 'allocation.delete'; + + const ACTION_FILE_READ = 'file.read'; + const ACTION_FILE_READ_CONTENT = 'file.read-content'; + const ACTION_FILE_CREATE = 'file.create'; + const ACTION_FILE_UPDATE = 'file.update'; + const ACTION_FILE_DELETE = 'file.delete'; + const ACTION_FILE_ARCHIVE = 'file.archive'; + const ACTION_FILE_SFTP = 'file.sftp'; + + const ACTION_STARTUP_READ = 'startup.read'; + const ACTION_STARTUP_UPDATE = 'startup.update'; + + const ACTION_SETTINGS_RENAME = 'settings.rename'; + const ACTION_SETTINGS_REINSTALL = 'settings.reinstall'; + /** * Should timestamps be used on this model. * @@ -51,114 +95,128 @@ class Permission extends Model implements CleansAttributes, ValidableContract /** * @var array */ - protected static $applicationRules = [ - 'subuser_id' => 'required', - 'permission' => 'required', + public static $validationRules = [ + 'subuser_id' => 'required|numeric|min:1', + 'permission' => 'required|string', ]; /** - * @var array - */ - protected static $dataIntegrityRules = [ - 'subuser_id' => 'numeric|min:1', - 'permission' => 'string', - ]; - - /** - * A list of all permissions available for a user. + * All of the permissions available on the system. You should use self::permissions() + * to retrieve them, and not directly access this array as it is subject to change. * * @var array + * @see \Pterodactyl\Models\Permission::permissions() */ protected static $permissions = [ - 'power' => [ - 'power-start' => 's:power:start', - 'power-stop' => 's:power:stop', - 'power-restart' => 's:power:restart', - 'power-kill' => 's:power:kill', - 'send-command' => 's:command', + 'websocket' => [ + 'description' => 'Allows the user to connect to the server websocket, giving them access to view console output and realtime server stats.', + 'keys' => [ + 'connect' => 'Allows a user to connect to the websocket instance for a server to stream the console.', + ], ], - 'subuser' => [ - 'list-subusers' => null, - 'view-subuser' => null, - 'edit-subuser' => null, - 'create-subuser' => null, - 'delete-subuser' => null, + + 'control' => [ + 'description' => 'Permissions that control a user\'s ability to control the power state of a server, or send commands.', + 'keys' => [ + 'console' => 'Allows a user to send commands to the server instance via the console.', + 'start' => 'Allows a user to start the server if it is stopped.', + 'stop' => 'Allows a user to stop a server if it is running.', + 'restart' => 'Allows a user to perform a server restart. This allows them to start the server if it is offline, but not put the server in a completely stopped state.', + ], ], - 'server' => [ - 'view-allocations' => null, - 'edit-allocation' => null, - 'view-startup' => null, - 'edit-startup' => null, - ], - 'database' => [ - 'view-databases' => null, - 'reset-db-password' => null, - 'delete-database' => null, - 'create-database' => null, + + 'user' => [ + 'description' => 'Permissions that allow a user to manage other subusers on a server. They will never be able to edit their own account, or assign permissions they do not have themselves.', + 'keys' => [ + 'create' => 'Allows a user to create new subusers for the server.', + 'read' => 'Allows the user to view subusers and their permissions for the server.', + 'update' => 'Allows a user to modify other subusers.', + 'delete' => 'Allows a user to delete a subuser from the server.', + ], ], + 'file' => [ - 'access-sftp' => null, - 'list-files' => 's:files:get', - 'edit-files' => 's:files:read', - 'save-files' => 's:files:post', - 'move-files' => 's:files:move', - 'copy-files' => 's:files:copy', - 'compress-files' => 's:files:compress', - 'decompress-files' => 's:files:decompress', - 'create-files' => 's:files:create', - 'upload-files' => 's:files:upload', - 'delete-files' => 's:files:delete', - 'download-files' => 's:files:download', + 'description' => 'Permissions that control a user\'s ability to modify the filesystem for this server.', + 'keys' => [ + 'create' => 'Allows a user to create additional files and folders via the Panel or direct upload.', + 'read' => 'Allows a user to view the contents of a directory, but not view the contents of or download files.', + 'read-content' => 'Allows a user to view the contents of a given file. This will also allow the user to download files.', + 'update' => 'Allows a user to update the contents of an existing file or directory.', + 'delete' => 'Allows a user to delete files or directories.', + 'archive' => 'Allows a user to archive the contents of a directory as well as decompress existing archives on the system.', + 'sftp' => 'Allows a user to connect to SFTP and manage server files using the other assigned file permissions.', + ], ], - 'task' => [ - 'list-schedules' => null, - 'view-schedule' => null, - 'toggle-schedule' => null, - 'queue-schedule' => null, - 'edit-schedule' => null, - 'create-schedule' => null, - 'delete-schedule' => null, + + 'backup' => [ + 'description' => 'Permissions that control a user\'s ability to generate and manage server backups.', + 'keys' => [ + 'create' => 'Allows a user to create new backups for this server.', + 'read' => 'Allows a user to view all backups that exist for this server.', + 'update' => '', + 'delete' => 'Allows a user to remove backups from the system.', + 'download' => 'Allows a user to download backups.', + ], + ], + + // Controls permissions for editing or viewing a server's allocations. + 'allocation' => [ + 'description' => 'Permissions that control a user\'s ability to modify the port allocations for this server.', + 'keys' => [ + 'read' => 'Allows a user to view the allocations assigned to this server.', + 'create' => 'Allows a user to assign additional allocations to the server.', + 'update' => 'Allows a user to change the primary server allocation and attach notes to each allocation.', + 'delete' => 'Allows a user to delete an allocation from the server.', + ], + ], + + // Controls permissions for editing or viewing a server's startup parameters. + 'startup' => [ + 'description' => 'Permissions that control a user\'s ability to view this server\'s startup parameters.', + 'keys' => [ + 'read' => 'Allows a user to view the startup variables for a server.', + 'update' => 'Allows a user to modify the startup variables for the server.', + ], + ], + + 'database' => [ + 'description' => 'Permissions that control a user\'s access to the database management for this server.', + 'keys' => [ + 'create' => 'Allows a user to create a new database for this server.', + 'read' => 'Allows a user to view the database associated with this server.', + 'update' => 'Allows a user to rotate the password on a database instance. If the user does not have the view_password permission they will not see the updated password.', + 'delete' => 'Allows a user to remove a database instance from this server.', + 'view_password' => 'Allows a user to view the password associated with a database instance for this server.', + ], + ], + + 'schedule' => [ + 'description' => 'Permissions that control a user\'s access to the schedule management for this server.', + 'keys' => [ + 'create' => '', // task.create-schedule + 'read' => '', // task.view-schedule, task.list-schedules + 'update' => '', // task.edit-schedule, task.queue-schedule, task.toggle-schedule + 'delete' => '', // task.delete-schedule + ], + ], + + 'settings' => [ + 'description' => 'Permissions that control a user\'s access to the settings for this server.', + 'keys' => [ + 'rename' => '', + 'reinstall' => '', + ], ], ]; /** - * Return a collection of permissions available. + * Returns all of the permissions available on the system for a user to + * have when controlling a server. * - * @param bool $array - * @return array|\Illuminate\Support\Collection + * @return \Illuminate\Database\Eloquent\Collection */ - public static function getPermissions($array = false) + public static function permissions(): Collection { - if ($array) { - return collect(self::$permissions)->mapWithKeys(function ($item) { - return $item; - })->all(); - } - - return collect(self::$permissions); - } - - /** - * Find permission by permission node. - * - * @param \Illuminate\Database\Query\Builder $query - * @param string $permission - * @return \Illuminate\Database\Query\Builder - */ - public function scopePermission($query, $permission) - { - return $query->where('permission', $permission); - } - - /** - * Filter permission by server. - * - * @param \Illuminate\Database\Query\Builder $query - * @param \Pterodactyl\Models\Server $server - * @return \Illuminate\Database\Query\Builder - */ - public function scopeServer($query, Server $server) - { - return $query->where('server_id', $server->id); + return Collection::make(self::$permissions); } } diff --git a/app/Models/RecoveryToken.php b/app/Models/RecoveryToken.php new file mode 100644 index 00000000..5cd00a9d --- /dev/null +++ b/app/Models/RecoveryToken.php @@ -0,0 +1,44 @@ + 'required|string', + ]; + + /** + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function user() + { + return $this->belongsTo(User::class); + } +} diff --git a/app/Models/Schedule.php b/app/Models/Schedule.php index 83971d79..d737edd2 100644 --- a/app/Models/Schedule.php +++ b/app/Models/Schedule.php @@ -2,16 +2,31 @@ namespace Pterodactyl\Models; -use Sofa\Eloquence\Eloquence; -use Sofa\Eloquence\Validable; -use Illuminate\Database\Eloquent\Model; -use Sofa\Eloquence\Contracts\CleansAttributes; -use Sofa\Eloquence\Contracts\Validable as ValidableContract; +use Illuminate\Container\Container; +use Pterodactyl\Contracts\Extensions\HashidsInterface; -class Schedule extends Model implements CleansAttributes, ValidableContract +/** + * @property int $id + * @property int $server_id + * @property string $name + * @property string $cron_day_of_week + * @property string $cron_day_of_month + * @property string $cron_hour + * @property string $cron_minute + * @property bool $is_active + * @property bool $is_processing + * @property \Carbon\Carbon|null $last_run_at + * @property \Carbon\Carbon|null $next_run_at + * @property \Carbon\Carbon $created_at + * @property \Carbon\Carbon $updated_at + * + * @property string $hashid + * + * @property \Pterodactyl\Models\Server $server + * @property \Pterodactyl\Models\Task[]|\Illuminate\Support\Collection $tasks + */ +class Schedule extends Model { - use Eloquence, Validable; - /** * The resource name for this model when it is transformed into an * API representation using fractal. @@ -25,6 +40,13 @@ class Schedule extends Model implements CleansAttributes, ValidableContract */ protected $table = 'schedules'; + /** + * Always return the tasks associated with this schedule. + * + * @var array + */ + protected $with = ['tasks']; + /** * Mass assignable attributes on this model. * @@ -59,8 +81,6 @@ class Schedule extends Model implements CleansAttributes, ValidableContract * @var array */ protected $dates = [ - self::CREATED_AT, - self::UPDATED_AT, 'last_run_at', 'next_run_at', ]; @@ -81,24 +101,13 @@ class Schedule extends Model implements CleansAttributes, ValidableContract /** * @var array */ - protected static $applicationRules = [ - 'server_id' => 'required', - 'cron_day_of_week' => 'required', - 'cron_day_of_month' => 'required', - 'cron_hour' => 'required', - 'cron_minute' => 'required', - ]; - - /** - * @var array - */ - protected static $dataIntegrityRules = [ - 'server_id' => 'exists:servers,id', - 'name' => 'nullable|string|max:255', - 'cron_day_of_week' => 'string', - 'cron_day_of_month' => 'string', - 'cron_hour' => 'string', - 'cron_minute' => 'string', + public static $validationRules = [ + 'server_id' => 'required|exists:servers,id', + 'name' => 'required|string|max:191', + 'cron_day_of_week' => 'required|string', + 'cron_day_of_month' => 'required|string', + 'cron_hour' => 'required|string', + 'cron_minute' => 'required|string', 'is_active' => 'boolean', 'is_processing' => 'boolean', 'last_run_at' => 'nullable|date', @@ -112,7 +121,7 @@ class Schedule extends Model implements CleansAttributes, ValidableContract */ public function getHashidAttribute() { - return app()->make('hashids')->encode($this->id); + return Container::getInstance()->make(HashidsInterface::class)->encode($this->id); } /** diff --git a/app/Models/Server.php b/app/Models/Server.php index fd5d9d12..aa4a39f0 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -2,18 +2,59 @@ namespace Pterodactyl\Models; -use Schema; -use Sofa\Eloquence\Eloquence; -use Sofa\Eloquence\Validable; -use Illuminate\Database\Eloquent\Model; use Illuminate\Notifications\Notifiable; +use Illuminate\Database\Query\JoinClause; use Znck\Eloquent\Traits\BelongsToThrough; -use Sofa\Eloquence\Contracts\CleansAttributes; -use Sofa\Eloquence\Contracts\Validable as ValidableContract; -class Server extends Model implements CleansAttributes, ValidableContract +/** + * @property int $id + * @property string|null $external_id + * @property string $uuid + * @property string $uuidShort + * @property int $node_id + * @property string $name + * @property string $description + * @property bool $skip_scripts + * @property bool $suspended + * @property int $owner_id + * @property int $memory + * @property int $swap + * @property int $disk + * @property int $io + * @property int $cpu + * @property string $threads + * @property bool $oom_disabled + * @property int $allocation_id + * @property int $nest_id + * @property int $egg_id + * @property string $startup + * @property string $image + * @property int $installed + * @property int $allocation_limit + * @property int $database_limit + * @property int $backup_limit + * @property \Carbon\Carbon $created_at + * @property \Carbon\Carbon $updated_at + * + * @property \Pterodactyl\Models\User $user + * @property \Pterodactyl\Models\Subuser[]|\Illuminate\Database\Eloquent\Collection $subusers + * @property \Pterodactyl\Models\Allocation $allocation + * @property \Pterodactyl\Models\Allocation[]|\Illuminate\Database\Eloquent\Collection $allocations + * @property \Pterodactyl\Models\Node $node + * @property \Pterodactyl\Models\Nest $nest + * @property \Pterodactyl\Models\Egg $egg + * @property \Pterodactyl\Models\EggVariable[]|\Illuminate\Database\Eloquent\Collection $variables + * @property \Pterodactyl\Models\Schedule[]|\Illuminate\Database\Eloquent\Collection $schedule + * @property \Pterodactyl\Models\Database[]|\Illuminate\Database\Eloquent\Collection $databases + * @property \Pterodactyl\Models\Location $location + * @property \Pterodactyl\Models\ServerTransfer $transfer + * @property \Pterodactyl\Models\Backup[]|\Illuminate\Database\Eloquent\Collection $backups + * @property \Pterodactyl\Models\Mount[]|\Illuminate\Database\Eloquent\Collection $mounts + */ +class Server extends Model { - use BelongsToThrough, Eloquence, Notifiable, Validable; + use BelongsToThrough; + use Notifiable; /** * The resource name for this model when it is transformed into an @@ -21,6 +62,10 @@ class Server extends Model implements CleansAttributes, ValidableContract */ const RESOURCE_NAME = 'server'; + const STATUS_INSTALLING = 0; + const STATUS_INSTALLED = 1; + const STATUS_INSTALL_FAILED = 2; + /** * The table associated with the model. * @@ -55,53 +100,29 @@ class Server extends Model implements CleansAttributes, ValidableContract /** * @var array */ - protected static $applicationRules = [ - 'external_id' => 'sometimes', - 'owner_id' => 'required', - 'name' => 'required', - 'memory' => 'required', - 'swap' => 'required', - 'io' => 'required', - 'cpu' => 'required', - 'oom_disabled' => 'sometimes', - 'disk' => 'required', - 'nest_id' => 'required', - 'egg_id' => 'required', - 'node_id' => 'required', - 'allocation_id' => 'required', - 'pack_id' => 'sometimes', - 'skip_scripts' => 'sometimes', - 'image' => 'required', - 'startup' => 'required', - 'database_limit' => 'present', - 'allocation_limit' => 'sometimes', - ]; - - /** - * @var array - */ - protected static $dataIntegrityRules = [ - 'external_id' => 'nullable|string|between:1,191|unique:servers', - 'owner_id' => 'integer|exists:users,id', - 'name' => 'string|min:1|max:255', - 'node_id' => 'exists:nodes,id', + public static $validationRules = [ + 'external_id' => 'sometimes|nullable|string|between:1,191|unique:servers', + 'owner_id' => 'required|integer|exists:users,id', + 'name' => 'required|string|min:1|max:191', + 'node_id' => 'required|exists:nodes,id', 'description' => 'string', - 'memory' => 'numeric|min:0', - 'swap' => 'numeric|min:-1', - 'io' => 'numeric|between:10,1000', - 'cpu' => 'numeric|min:0', - 'oom_disabled' => 'boolean', - 'disk' => 'numeric|min:0', - 'allocation_id' => 'bail|unique:servers|exists:allocations,id', - 'nest_id' => 'exists:nests,id', - 'egg_id' => 'exists:eggs,id', - 'pack_id' => 'nullable|numeric|min:0', - 'startup' => 'string', - 'skip_scripts' => 'boolean', - 'image' => 'string|max:255', + 'memory' => 'required|numeric|min:0', + 'swap' => 'required|numeric|min:-1', + 'io' => 'required|numeric|between:10,1000', + 'cpu' => 'required|numeric|min:0', + 'threads' => 'nullable|regex:/^[0-9-,]+$/', + 'oom_disabled' => 'sometimes|boolean', + 'disk' => 'required|numeric|min:0', + 'allocation_id' => 'required|bail|unique:servers|exists:allocations,id', + 'nest_id' => 'required|exists:nests,id', + 'egg_id' => 'required|exists:eggs,id', + 'startup' => 'required|string', + 'skip_scripts' => 'sometimes|boolean', + 'image' => 'required|string|max:191', 'installed' => 'in:0,1,2', - 'database_limit' => 'nullable|integer|min:0', - 'allocation_limit' => 'nullable|integer|min:0', + 'database_limit' => 'present|nullable|integer|min:0', + 'allocation_limit' => 'sometimes|nullable|integer|min:0', + 'backup_limit' => 'present|integer|min:0', ]; /** @@ -112,7 +133,7 @@ class Server extends Model implements CleansAttributes, ValidableContract protected $casts = [ 'node_id' => 'integer', 'skip_scripts' => 'boolean', - 'suspended' => 'integer', + 'suspended' => 'boolean', 'owner_id' => 'integer', 'memory' => 'integer', 'swap' => 'integer', @@ -123,36 +144,30 @@ class Server extends Model implements CleansAttributes, ValidableContract 'allocation_id' => 'integer', 'nest_id' => 'integer', 'egg_id' => 'integer', - 'pack_id' => 'integer', 'installed' => 'integer', 'database_limit' => 'integer', 'allocation_limit' => 'integer', + 'backup_limit' => 'integer', ]; /** - * Parameters for search querying. - * - * @var array - */ - protected $searchableColumns = [ - 'name' => 100, - 'uuid' => 80, - 'uuidShort' => 80, - 'external_id' => 50, - 'user.email' => 40, - 'user.username' => 30, - 'node.name' => 10, - 'pack.name' => 10, - ]; - - /** - * Return the columns available for this table. + * Returns the format for server allocations when communicating with the Daemon. * * @return array */ - public function getTableColumns() + public function getAllocationMappings(): array { - return Schema::getColumnListing($this->getTable()); + return $this->allocations->where('node_id', $this->node_id)->groupBy('ip')->map(function ($item) { + return $item->pluck('port'); + })->toArray(); + } + + /** + * @return bool + */ + public function isInstalled(): bool + { + return $this->installed === 1; } /** @@ -195,16 +210,6 @@ class Server extends Model implements CleansAttributes, ValidableContract return $this->hasMany(Allocation::class, 'server_id'); } - /** - * Gets information for the pack associated with this server. - * - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo - */ - public function pack() - { - return $this->belongsTo(Pack::class); - } - /** * Gets information for the nest associated with this server. * @@ -218,11 +223,11 @@ class Server extends Model implements CleansAttributes, ValidableContract /** * Gets information for the egg associated with this server. * - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + * @return \Illuminate\Database\Eloquent\Relations\HasOne */ public function egg() { - return $this->belongsTo(Egg::class); + return $this->hasOne(Egg::class, 'id', 'egg_id'); } /** @@ -232,7 +237,17 @@ class Server extends Model implements CleansAttributes, ValidableContract */ public function variables() { - return $this->hasMany(ServerVariable::class); + return $this->hasMany(EggVariable::class, 'egg_id', 'egg_id') + ->select(['egg_variables.*', 'server_variables.variable_value as server_value']) + ->leftJoin('server_variables', function (JoinClause $join) { + // Don't forget to join against the server ID as well since the way we're using this relationship + // would actually return all of the variables and their values for _all_ servers using that egg,\ + // rather than only the server for this model. + // + // @see https://github.com/pterodactyl/panel/issues/2250 + $join->on('server_variables.variable_id', 'egg_variables.id') + ->where('server_variables.server_id', $this->id); + }); } /** @@ -278,22 +293,30 @@ class Server extends Model implements CleansAttributes, ValidableContract } /** - * Return the key belonging to the server owner. + * Returns the associated server transfer. * * @return \Illuminate\Database\Eloquent\Relations\HasOne */ - public function key() + public function transfer() { - return $this->hasOne(DaemonKey::class, 'user_id', 'owner_id'); + return $this->hasOne(ServerTransfer::class)->orderByDesc('id'); } /** - * Returns all of the daemon keys belonging to this server. - * * @return \Illuminate\Database\Eloquent\Relations\HasMany */ - public function keys() + public function backups() { - return $this->hasMany(DaemonKey::class); + return $this->hasMany(Backup::class); + } + + /** + * Returns all mounts that have this server has mounted. + * + * @return \Illuminate\Database\Eloquent\Relations\HasManyThrough + */ + public function mounts() + { + return $this->hasManyThrough(Mount::class, MountServer::class, 'server_id', 'id', 'id', 'mount_id'); } } diff --git a/app/Models/ServerTransfer.php b/app/Models/ServerTransfer.php new file mode 100644 index 00000000..8d712064 --- /dev/null +++ b/app/Models/ServerTransfer.php @@ -0,0 +1,81 @@ + 'int', + 'old_node' => 'int', + 'new_node' => 'int', + 'old_allocation' => 'int', + 'new_allocation' => 'int', + 'old_additional_allocations' => 'string', + 'new_additional_allocations' => 'string', + 'successful' => 'bool', + ]; + + /** + * @var array + */ + public static $validationRules = [ + 'server_id' => 'required|numeric|exists:servers,id', + 'old_node' => 'required|numeric', + 'new_node' => 'required|numeric', + 'old_allocation' => 'required|numeric', + 'new_allocation' => 'required|numeric', + 'old_additional_allocations' => 'nullable', + 'new_additional_allocations' => 'nullable', + 'successful' => 'sometimes|boolean', + ]; + + /** + * Gets the server associated with a server transfer. + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function server() + { + return $this->belongsTo(Server::class); + } +} diff --git a/app/Models/ServerVariable.php b/app/Models/ServerVariable.php index f706d594..b2bb4b7d 100644 --- a/app/Models/ServerVariable.php +++ b/app/Models/ServerVariable.php @@ -32,9 +32,9 @@ class ServerVariable extends Model * @var array */ protected $casts = [ - 'server_id' => 'integer', - 'variable_id' => 'integer', - ]; + 'server_id' => 'integer', + 'variable_id' => 'integer', + ]; /** * Determine if variable is viewable by users. diff --git a/app/Models/Session.php b/app/Models/Session.php index 3eb9d526..535afc80 100644 --- a/app/Models/Session.php +++ b/app/Models/Session.php @@ -1,11 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Models; diff --git a/app/Models/Setting.php b/app/Models/Setting.php index 90d41f3d..45824862 100644 --- a/app/Models/Setting.php +++ b/app/Models/Setting.php @@ -2,16 +2,8 @@ namespace Pterodactyl\Models; -use Sofa\Eloquence\Eloquence; -use Sofa\Eloquence\Validable; -use Illuminate\Database\Eloquent\Model; -use Sofa\Eloquence\Contracts\CleansAttributes; -use Sofa\Eloquence\Contracts\Validable as ValidableContract; - -class Setting extends Model implements CleansAttributes, ValidableContract +class Setting extends Model { - use Eloquence, Validable; - /** * The table associated with the model. * @@ -32,8 +24,8 @@ class Setting extends Model implements CleansAttributes, ValidableContract /** * @var array */ - protected static $applicationRules = [ - 'key' => 'required|string|between:1,255', + public static $validationRules = [ + 'key' => 'required|string|between:1,191', 'value' => 'string', ]; } diff --git a/app/Models/Subuser.php b/app/Models/Subuser.php index 93c62217..ab85b85d 100644 --- a/app/Models/Subuser.php +++ b/app/Models/Subuser.php @@ -2,16 +2,22 @@ namespace Pterodactyl\Models; -use Sofa\Eloquence\Eloquence; -use Sofa\Eloquence\Validable; -use Illuminate\Database\Eloquent\Model; use Illuminate\Notifications\Notifiable; -use Sofa\Eloquence\Contracts\CleansAttributes; -use Sofa\Eloquence\Contracts\Validable as ValidableContract; -class Subuser extends Model implements CleansAttributes, ValidableContract +/** + * @property int $id + * @property int $user_id + * @property int $server_id + * @property array $permissions + * @property \Carbon\Carbon $created_at + * @property \Carbon\Carbon $updated_at + * + * @property \Pterodactyl\Models\User $user + * @property \Pterodactyl\Models\Server $server + */ +class Subuser extends Model { - use Eloquence, Notifiable, Validable; + use Notifiable; /** * The resource name for this model when it is transformed into an @@ -39,24 +45,19 @@ class Subuser extends Model implements CleansAttributes, ValidableContract * @var array */ protected $casts = [ - 'user_id' => 'integer', - 'server_id' => 'integer', + 'user_id' => 'int', + 'server_id' => 'int', + 'permissions' => 'array', ]; /** * @var array */ - protected static $applicationRules = [ - 'user_id' => 'required', - 'server_id' => 'required', - ]; - - /** - * @var array - */ - protected static $dataIntegrityRules = [ - 'user_id' => 'numeric|exists:users,id', - 'server_id' => 'numeric|exists:servers,id', + public static $validationRules = [ + 'user_id' => 'required|numeric|exists:users,id', + 'server_id' => 'required|numeric|exists:servers,id', + 'permissions' => 'nullable|array', + 'permissions.*' => 'string', ]; /** @@ -98,14 +99,4 @@ class Subuser extends Model implements CleansAttributes, ValidableContract { return $this->hasMany(Permission::class); } - - /** - * Return the key that belongs to this subuser for the server. - * - * @return \Illuminate\Database\Eloquent\Relations\HasOne - */ - public function key() - { - return $this->hasOne(DaemonKey::class, 'server_id', 'server_id')->where('daemon_keys.user_id', '=', $this->user_id); - } } diff --git a/app/Models/Task.php b/app/Models/Task.php index 28c8e323..f241b071 100644 --- a/app/Models/Task.php +++ b/app/Models/Task.php @@ -2,16 +2,29 @@ namespace Pterodactyl\Models; -use Sofa\Eloquence\Eloquence; -use Sofa\Eloquence\Validable; -use Illuminate\Database\Eloquent\Model; +use Illuminate\Container\Container; use Znck\Eloquent\Traits\BelongsToThrough; -use Sofa\Eloquence\Contracts\CleansAttributes; -use Sofa\Eloquence\Contracts\Validable as ValidableContract; +use Pterodactyl\Contracts\Extensions\HashidsInterface; -class Task extends Model implements CleansAttributes, ValidableContract +/** + * @property int $id + * @property int $schedule_id + * @property int $sequence_id + * @property string $action + * @property string $payload + * @property int $time_offset + * @property bool $is_queued + * @property \Carbon\Carbon $created_at + * @property \Carbon\Carbon $updated_at + * + * @property string $hashid + * + * @property \Pterodactyl\Models\Schedule $schedule + * @property \Pterodactyl\Models\Server $server + */ +class Task extends Model { - use BelongsToThrough, Eloquence, Validable; + use BelongsToThrough; /** * The resource name for this model when it is transformed into an @@ -66,29 +79,19 @@ class Task extends Model implements CleansAttributes, ValidableContract * @var array */ protected $attributes = [ + 'time_offset' => 0, 'is_queued' => false, ]; /** * @var array */ - protected static $applicationRules = [ - 'schedule_id' => 'required', - 'sequence_id' => 'required', - 'action' => 'required', - 'payload' => 'required', - 'time_offset' => 'required', - ]; - - /** - * @var array - */ - protected static $dataIntegrityRules = [ - 'schedule_id' => 'numeric|exists:schedules,id', - 'sequence_id' => 'numeric|min:1', - 'action' => 'string', - 'payload' => 'string', - 'time_offset' => 'numeric|between:0,900', + public static $validationRules = [ + 'schedule_id' => 'required|numeric|exists:schedules,id', + 'sequence_id' => 'required|numeric|min:1', + 'action' => 'required|string', + 'payload' => 'required_unless:action,backup|string', + 'time_offset' => 'required|numeric|between:0,900', 'is_queued' => 'boolean', ]; @@ -99,7 +102,7 @@ class Task extends Model implements CleansAttributes, ValidableContract */ public function getHashidAttribute() { - return app()->make('hashids')->encode($this->id); + return Container::getInstance()->make(HashidsInterface::class)->encode($this->id); } /** diff --git a/app/Models/TaskLog.php b/app/Models/TaskLog.php index ec85677e..eabfde60 100644 --- a/app/Models/TaskLog.php +++ b/app/Models/TaskLog.php @@ -1,11 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Models; diff --git a/app/Models/User.php b/app/Models/User.php index 6732b23a..24ef981f 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -2,42 +2,58 @@ namespace Pterodactyl\Models; -use Sofa\Eloquence\Eloquence; -use Sofa\Eloquence\Validable; use Pterodactyl\Rules\Username; +use Illuminate\Support\Collection; use Illuminate\Validation\Rules\In; use Illuminate\Auth\Authenticatable; -use Illuminate\Database\Eloquent\Model; use Illuminate\Notifications\Notifiable; -use Sofa\Eloquence\Contracts\CleansAttributes; +use Illuminate\Database\Eloquent\Builder; use Illuminate\Auth\Passwords\CanResetPassword; use Pterodactyl\Traits\Helpers\AvailableLanguages; use Illuminate\Foundation\Auth\Access\Authorizable; -use Sofa\Eloquence\Contracts\Validable as ValidableContract; use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract; use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract; use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract; use Pterodactyl\Notifications\SendPasswordReset as ResetPasswordNotification; +/** + * @property int $id + * @property string|null $external_id + * @property string $uuid + * @property string $username + * @property string $email + * @property string|null $name_first + * @property string|null $name_last + * @property string $password + * @property string|null $remeber_token + * @property string $language + * @property bool $root_admin + * @property bool $use_totp + * @property string|null $totp_secret + * @property \Carbon\Carbon|null $totp_authenticated_at + * @property bool $gravatar + * @property \Carbon\Carbon $created_at + * @property \Carbon\Carbon $updated_at + * + * @property string $name + * @property \Pterodactyl\Models\ApiKey[]|\Illuminate\Database\Eloquent\Collection $apiKeys + * @property \Pterodactyl\Models\Server[]|\Illuminate\Database\Eloquent\Collection $servers + * @property \Pterodactyl\Models\RecoveryToken[]|\Illuminate\Database\Eloquent\Collection $recoveryTokens + */ class User extends Model implements AuthenticatableContract, AuthorizableContract, - CanResetPasswordContract, - CleansAttributes, - ValidableContract + CanResetPasswordContract { - use Authenticatable, Authorizable, AvailableLanguages, CanResetPassword, Eloquence, Notifiable, Validable { - gatherRules as eloquenceGatherRules; - } + use Authenticatable; + use Authorizable; + use AvailableLanguages; + use CanResetPassword; + use Notifiable; const USER_LEVEL_USER = 0; const USER_LEVEL_ADMIN = 1; - const FILTER_LEVEL_ALL = 0; - const FILTER_LEVEL_OWNER = 1; - const FILTER_LEVEL_ADMIN = 2; - const FILTER_LEVEL_SUBUSER = 3; - /** * The resource name for this model when it is transformed into an * API representation using fractal. @@ -92,7 +108,7 @@ class User extends Model implements /** * @var array */ - protected $dates = [self::CREATED_AT, self::UPDATED_AT, 'totp_authenticated_at']; + protected $dates = ['totp_authenticated_at']; /** * The attributes excluded from the model's JSON form. @@ -101,20 +117,6 @@ class User extends Model implements */ protected $hidden = ['password', 'remember_token', 'totp_secret', 'totp_authenticated_at']; - /** - * Parameters for search querying. - * - * @var array - */ - protected $searchableColumns = [ - 'username' => 100, - 'email' => 100, - 'external_id' => 80, - 'uuid' => 80, - 'name_first' => 40, - 'name_last' => 40, - ]; - /** * Default values for specific fields in the database. * @@ -128,36 +130,19 @@ class User extends Model implements 'totp_secret' => null, ]; - /** - * Rules verifying that the data passed in forms is valid and meets application logic rules. - * - * @var array - */ - protected static $applicationRules = [ - 'uuid' => 'required', - 'email' => 'required', - 'external_id' => 'sometimes', - 'username' => 'required', - 'name_first' => 'required', - 'name_last' => 'required', - 'password' => 'sometimes', - 'language' => 'sometimes', - 'use_totp' => 'sometimes', - ]; - /** * Rules verifying that the data being stored matches the expectations of the database. * * @var array */ - protected static $dataIntegrityRules = [ - 'uuid' => 'string|size:36|unique:users,uuid', - 'email' => 'email|unique:users,email', - 'external_id' => 'nullable|string|max:255|unique:users,external_id', - 'username' => 'between:1,255|unique:users,username', - 'name_first' => 'string|between:1,255', - 'name_last' => 'string|between:1,255', - 'password' => 'nullable|string', + public static $validationRules = [ + 'uuid' => 'required|string|size:36|unique:users,uuid', + 'email' => 'required|email|between:1,191|unique:users,email', + 'external_id' => 'sometimes|nullable|string|max:191|unique:users,external_id', + 'username' => 'required|between:1,191|unique:users,username', + 'name_first' => 'required|string|between:1,191', + 'name_last' => 'required|string|between:1,191', + 'password' => 'sometimes|nullable|string', 'root_admin' => 'boolean', 'language' => 'string', 'use_totp' => 'boolean', @@ -168,15 +153,26 @@ class User extends Model implements * Implement language verification by overriding Eloquence's gather * rules function. */ - protected static function gatherRules() + public static function getRules() { - $rules = self::eloquenceGatherRules(); + $rules = parent::getRules(); + $rules['language'][] = new In(array_keys((new self)->getAvailableLanguages())); $rules['username'][] = new Username; return $rules; } + /** + * Return the user model in a format that can be passed over to Vue templates. + * + * @return array + */ + public function toVueObject(): array + { + return (new Collection($this->toArray()))->except(['id', 'external_id'])->toArray(); + } + /** * Send the password reset notification. * @@ -204,17 +200,7 @@ class User extends Model implements */ public function getNameAttribute() { - return $this->name_first . ' ' . $this->name_last; - } - - /** - * Returns all permissions that a user has. - * - * @return \Illuminate\Database\Eloquent\Relations\HasManyThrough - */ - public function permissions() - { - return $this->hasManyThrough(Permission::class, Subuser::class); + return trim($this->name_first . ' ' . $this->name_last); } /** @@ -228,22 +214,36 @@ class User extends Model implements } /** - * Return all servers that user is listed as a subuser of directly. - * * @return \Illuminate\Database\Eloquent\Relations\HasMany */ - public function subuserOf() + public function apiKeys() { - return $this->hasMany(Subuser::class); + return $this->hasMany(ApiKey::class) + ->where('key_type', ApiKey::TYPE_ACCOUNT); } /** - * Return all of the daemon keys that a user belongs to. - * * @return \Illuminate\Database\Eloquent\Relations\HasMany */ - public function keys() + public function recoveryTokens() { - return $this->hasMany(DaemonKey::class); + return $this->hasMany(RecoveryToken::class); + } + + /** + * Returns all of the servers that a user can access by way of being the owner of the + * server, or because they are assigned as a subuser for that server. + * + * @return \Illuminate\Database\Eloquent\Builder + */ + public function accessibleServers() + { + return Server::query() + ->select('servers.*') + ->leftJoin('subusers', 'subusers.server_id', '=', 'servers.id') + ->where(function (Builder $builder) { + $builder->where('servers.owner_id', $this->id)->orWhere('subusers.user_id', $this->id); + }) + ->groupBy('servers.id'); } } diff --git a/app/Notifications/AccountCreated.php b/app/Notifications/AccountCreated.php index 7dd258dd..30497804 100644 --- a/app/Notifications/AccountCreated.php +++ b/app/Notifications/AccountCreated.php @@ -31,7 +31,7 @@ class AccountCreated extends Notification implements ShouldQueue * Create a new notification instance. * * @param \Pterodactyl\Models\User $user - * @param string|null $token + * @param string|null $token */ public function __construct(User $user, string $token = null) { diff --git a/app/Notifications/AddedToServer.php b/app/Notifications/AddedToServer.php index 2ecaa45f..7b6cac8c 100644 --- a/app/Notifications/AddedToServer.php +++ b/app/Notifications/AddedToServer.php @@ -56,6 +56,6 @@ class AddedToServer extends Notification implements ShouldQueue ->greeting('Hello ' . $this->server->user . '!') ->line('You have been added as a subuser for the following server, allowing you certain control over the server.') ->line('Server Name: ' . $this->server->name) - ->action('Visit Server', route('server.index', $this->server->uuidShort)); + ->action('Visit Server', url('/server/' . $this->server->uuidShort)); } } diff --git a/app/Policies/ServerPolicy.php b/app/Policies/ServerPolicy.php index 9b4db6f0..bc3fa7ac 100644 --- a/app/Policies/ServerPolicy.php +++ b/app/Policies/ServerPolicy.php @@ -1,45 +1,56 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Policies; -use Cache; -use Carbon; +use Carbon\Carbon; use Pterodactyl\Models\User; use Pterodactyl\Models\Server; +use Illuminate\Contracts\Cache\Repository as CacheRepository; class ServerPolicy { + /** + * @var \Illuminate\Contracts\Cache\Repository + */ + private $cache; + + /** + * ServerPolicy constructor. + * + * @param \Illuminate\Contracts\Cache\Repository $cache + */ + public function __construct(CacheRepository $cache) + { + $this->cache = $cache; + } + /** * Checks if the user has the given permission on/for the server. * - * @param \Pterodactyl\Models\User $user + * @param \Pterodactyl\Models\User $user * @param \Pterodactyl\Models\Server $server - * @param string $permission + * @param string $permission * @return bool */ protected function checkPermission(User $user, Server $server, $permission) { - $permissions = Cache::remember('ServerPolicy.' . $user->uuid . $server->uuid, Carbon::now()->addSeconds(5), function () use ($user, $server) { - return $user->permissions()->server($server)->get()->transform(function ($item) { - return $item->permission; - })->values(); + $key = sprintf('ServerPolicy.%s.%s', $user->uuid, $server->uuid); + + $permissions = $this->cache->remember($key, Carbon::now()->addSeconds(5), function () use ($user, $server) { + /** @var \Pterodactyl\Models\Subuser|null $subuser */ + $subuser = $server->subusers()->where('user_id', $user->id)->first(); + + return $subuser ? $subuser->permissions : []; }); - return $permissions->search($permission, true) !== false; + return in_array($permission, $permissions); } /** * Runs before any of the functions are called. Used to determine if user is root admin, if so, ignore permissions. * - * @param \Pterodactyl\Models\User $user - * @param string $ability + * @param \Pterodactyl\Models\User $user + * @param string $ability * @param \Pterodactyl\Models\Server $server * @return bool */ @@ -58,7 +69,7 @@ class ServerPolicy * policy permission. * * @param string $name - * @param mixed $arguments + * @param mixed $arguments */ public function __call($name, $arguments) { diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 1adede6c..929de15f 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -8,9 +8,9 @@ use Pterodactyl\Models\User; use Pterodactyl\Models\Server; use Pterodactyl\Models\Subuser; use Illuminate\Support\Facades\Schema; -use Igaster\LaravelTheme\Facades\Theme; use Illuminate\Support\ServiceProvider; use Pterodactyl\Observers\UserObserver; +use Pterodactyl\Extensions\Themes\Theme; use Pterodactyl\Observers\ServerObserver; use Pterodactyl\Observers\SubuserObserver; @@ -29,7 +29,6 @@ class AppServiceProvider extends ServiceProvider View::share('appVersion', $this->versionData()['version'] ?? 'undefined'); View::share('appIsGit', $this->versionData()['is_git'] ?? false); - Theme::setSetting('cache-version', md5($this->versionData()['version'] ?? 'undefined')); } /** @@ -42,6 +41,10 @@ class AppServiceProvider extends ServiceProvider if (! config('pterodactyl.load_environment_only', false) && $this->app->environment() !== 'testing') { $this->app->register(SettingsServiceProvider::class); } + + $this->app->singleton('extensions.themes', function () { + return new Theme; + }); } /** diff --git a/app/Providers/BackupsServiceProvider.php b/app/Providers/BackupsServiceProvider.php new file mode 100644 index 00000000..999dfa90 --- /dev/null +++ b/app/Providers/BackupsServiceProvider.php @@ -0,0 +1,28 @@ +app->singleton(BackupManager::class, function ($app) { + return new BackupManager($app); + }); + } + + /** + * @return string[] + */ + public function provides() + { + return [BackupManager::class]; + } +} diff --git a/app/Providers/MacroServiceProvider.php b/app/Providers/MacroServiceProvider.php deleted file mode 100644 index 9eae42b8..00000000 --- a/app/Providers/MacroServiceProvider.php +++ /dev/null @@ -1,28 +0,0 @@ - 0.9) { - $size = $size / 1024; - $i++; - } - - return round($size, ($i < 2) ? 0 : $precision) . ' ' . $units[$i]; - }); - } -} diff --git a/app/Providers/RepositoryServiceProvider.php b/app/Providers/RepositoryServiceProvider.php index aa5fbbaa..8a0434f5 100644 --- a/app/Providers/RepositoryServiceProvider.php +++ b/app/Providers/RepositoryServiceProvider.php @@ -3,15 +3,11 @@ namespace Pterodactyl\Providers; use Illuminate\Support\ServiceProvider; -use Pterodactyl\Repositories\Daemon\FileRepository; -use Pterodactyl\Repositories\Daemon\PowerRepository; use Pterodactyl\Repositories\Eloquent\EggRepository; use Pterodactyl\Repositories\Eloquent\NestRepository; use Pterodactyl\Repositories\Eloquent\NodeRepository; -use Pterodactyl\Repositories\Eloquent\PackRepository; use Pterodactyl\Repositories\Eloquent\TaskRepository; use Pterodactyl\Repositories\Eloquent\UserRepository; -use Pterodactyl\Repositories\Daemon\CommandRepository; use Pterodactyl\Repositories\Eloquent\ApiKeyRepository; use Pterodactyl\Repositories\Eloquent\ServerRepository; use Pterodactyl\Repositories\Eloquent\SessionRepository; @@ -20,15 +16,11 @@ use Pterodactyl\Repositories\Eloquent\DatabaseRepository; use Pterodactyl\Repositories\Eloquent\LocationRepository; use Pterodactyl\Repositories\Eloquent\ScheduleRepository; use Pterodactyl\Repositories\Eloquent\SettingsRepository; -use Pterodactyl\Repositories\Eloquent\DaemonKeyRepository; use Pterodactyl\Repositories\Eloquent\AllocationRepository; -use Pterodactyl\Repositories\Eloquent\PermissionRepository; use Pterodactyl\Contracts\Repository\EggRepositoryInterface; -use Pterodactyl\Repositories\Daemon\ConfigurationRepository; use Pterodactyl\Repositories\Eloquent\EggVariableRepository; use Pterodactyl\Contracts\Repository\NestRepositoryInterface; use Pterodactyl\Contracts\Repository\NodeRepositoryInterface; -use Pterodactyl\Contracts\Repository\PackRepositoryInterface; use Pterodactyl\Contracts\Repository\TaskRepositoryInterface; use Pterodactyl\Contracts\Repository\UserRepositoryInterface; use Pterodactyl\Repositories\Eloquent\DatabaseHostRepository; @@ -41,18 +33,10 @@ use Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface; use Pterodactyl\Contracts\Repository\LocationRepositoryInterface; use Pterodactyl\Contracts\Repository\ScheduleRepositoryInterface; use Pterodactyl\Contracts\Repository\SettingsRepositoryInterface; -use Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface; use Pterodactyl\Contracts\Repository\AllocationRepositoryInterface; -use Pterodactyl\Contracts\Repository\PermissionRepositoryInterface; -use Pterodactyl\Contracts\Repository\Daemon\FileRepositoryInterface; use Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface; -use Pterodactyl\Contracts\Repository\Daemon\PowerRepositoryInterface; use Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface; -use Pterodactyl\Contracts\Repository\Daemon\CommandRepositoryInterface; use Pterodactyl\Contracts\Repository\ServerVariableRepositoryInterface; -use Pterodactyl\Contracts\Repository\Daemon\ConfigurationRepositoryInterface; -use Pterodactyl\Repositories\Daemon\ServerRepository as DaemonServerRepository; -use Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface as DaemonServerRepositoryInterface; class RepositoryServiceProvider extends ServiceProvider { @@ -64,7 +48,6 @@ class RepositoryServiceProvider extends ServiceProvider // Eloquent Repositories $this->app->bind(AllocationRepositoryInterface::class, AllocationRepository::class); $this->app->bind(ApiKeyRepositoryInterface::class, ApiKeyRepository::class); - $this->app->bind(DaemonKeyRepositoryInterface::class, DaemonKeyRepository::class); $this->app->bind(DatabaseRepositoryInterface::class, DatabaseRepository::class); $this->app->bind(DatabaseHostRepositoryInterface::class, DatabaseHostRepository::class); $this->app->bind(EggRepositoryInterface::class, EggRepository::class); @@ -72,8 +55,6 @@ class RepositoryServiceProvider extends ServiceProvider $this->app->bind(LocationRepositoryInterface::class, LocationRepository::class); $this->app->bind(NestRepositoryInterface::class, NestRepository::class); $this->app->bind(NodeRepositoryInterface::class, NodeRepository::class); - $this->app->bind(PackRepositoryInterface::class, PackRepository::class); - $this->app->bind(PermissionRepositoryInterface::class, PermissionRepository::class); $this->app->bind(ScheduleRepositoryInterface::class, ScheduleRepository::class); $this->app->bind(ServerRepositoryInterface::class, ServerRepository::class); $this->app->bind(ServerVariableRepositoryInterface::class, ServerVariableRepository::class); @@ -82,12 +63,5 @@ class RepositoryServiceProvider extends ServiceProvider $this->app->bind(SubuserRepositoryInterface::class, SubuserRepository::class); $this->app->bind(TaskRepositoryInterface::class, TaskRepository::class); $this->app->bind(UserRepositoryInterface::class, UserRepository::class); - - // Daemon Repositories - $this->app->bind(ConfigurationRepositoryInterface::class, ConfigurationRepository::class); - $this->app->bind(CommandRepositoryInterface::class, CommandRepository::class); - $this->app->bind(DaemonServerRepositoryInterface::class, DaemonServerRepository::class); - $this->app->bind(FileRepositoryInterface::class, FileRepository::class); - $this->app->bind(PowerRepositoryInterface::class, PowerRepository::class); } } diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index f0e97811..0ea33b5d 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -22,35 +22,38 @@ class RouteServiceProvider extends ServiceProvider public function map() { Route::middleware(['web', 'auth', 'csrf']) - ->namespace($this->namespace . '\Base') - ->group(base_path('routes/base.php')); + ->namespace($this->namespace . '\Base') + ->group(base_path('routes/base.php')); Route::middleware(['web', 'auth', 'admin', 'csrf'])->prefix('/admin') - ->namespace($this->namespace . '\Admin') - ->group(base_path('routes/admin.php')); + ->namespace($this->namespace . '\Admin') + ->group(base_path('routes/admin.php')); Route::middleware(['web', 'csrf'])->prefix('/auth') - ->namespace($this->namespace . '\Auth') - ->group(base_path('routes/auth.php')); + ->namespace($this->namespace . '\Auth') + ->group(base_path('routes/auth.php')); - Route::middleware(['web', 'csrf', 'auth', 'server', 'subuser.auth', 'node.maintenance'])->prefix('/server/{server}') - ->namespace($this->namespace . '\Server') - ->group(base_path('routes/server.php')); + Route::middleware(['web', 'csrf', 'auth', 'server', 'node.maintenance']) + ->prefix('/api/server/{server}') + ->namespace($this->namespace . '\Server') + ->group(base_path('routes/server.php')); - Route::middleware(['api'])->prefix('/api/application') + Route::middleware([ + sprintf('throttle:%s,%s', config('http.rate_limit.application'), config('http.rate_limit.application_period')), + 'api', + ])->prefix('/api/application') ->namespace($this->namespace . '\Api\Application') ->group(base_path('routes/api-application.php')); - Route::middleware(['client-api'])->prefix('/api/client') + Route::middleware([ + sprintf('throttle:%s,%s', config('http.rate_limit.client'), config('http.rate_limit.client_period')), + 'client-api', + ])->prefix('/api/client') ->namespace($this->namespace . '\Api\Client') ->group(base_path('routes/api-client.php')); Route::middleware(['daemon'])->prefix('/api/remote') ->namespace($this->namespace . '\Api\Remote') ->group(base_path('routes/api-remote.php')); - - Route::middleware(['web', 'daemon-old'])->prefix('/daemon') - ->namespace($this->namespace . '\Daemon') - ->group(base_path('routes/daemon.php')); } } diff --git a/app/Providers/SettingsServiceProvider.php b/app/Providers/SettingsServiceProvider.php index 2cc26e66..abd88c04 100644 --- a/app/Providers/SettingsServiceProvider.php +++ b/app/Providers/SettingsServiceProvider.php @@ -21,6 +21,7 @@ class SettingsServiceProvider extends ServiceProvider protected $keys = [ 'app:name', 'app:locale', + 'app:analytics', 'recaptcha:enabled', 'recaptcha:secret_key', 'recaptcha:website_key', @@ -60,9 +61,9 @@ class SettingsServiceProvider extends ServiceProvider /** * Boot the service provider. * - * @param \Illuminate\Contracts\Config\Repository $config - * @param \Illuminate\Contracts\Encryption\Encrypter $encrypter - * @param \Psr\Log\LoggerInterface $log + * @param \Illuminate\Contracts\Config\Repository $config + * @param \Illuminate\Contracts\Encryption\Encrypter $encrypter + * @param \Psr\Log\LoggerInterface $log * @param \Pterodactyl\Contracts\Repository\SettingsRepositoryInterface $settings */ public function boot(ConfigRepository $config, Encrypter $encrypter, Log $log, SettingsRepositoryInterface $settings) diff --git a/app/Providers/ViewComposerServiceProvider.php b/app/Providers/ViewComposerServiceProvider.php index ab8c9e16..9f484e00 100644 --- a/app/Providers/ViewComposerServiceProvider.php +++ b/app/Providers/ViewComposerServiceProvider.php @@ -3,8 +3,7 @@ namespace Pterodactyl\Providers; use Illuminate\Support\ServiceProvider; -use Pterodactyl\Http\ViewComposers\ServerListComposer; -use Pterodactyl\Http\ViewComposers\Server\ServerDataComposer; +use Pterodactyl\Http\ViewComposers\AssetComposer; class ViewComposerServiceProvider extends ServiceProvider { @@ -13,9 +12,6 @@ class ViewComposerServiceProvider extends ServiceProvider */ public function boot() { - $this->app->make('view')->composer('server.*', ServerDataComposer::class); - - // Add data to make the sidebar work when viewing a server. - $this->app->make('view')->composer(['server.*'], ServerListComposer::class); + $this->app->make('view')->composer('*', AssetComposer::class); } } diff --git a/app/Repositories/Concerns/Searchable.php b/app/Repositories/Concerns/Searchable.php deleted file mode 100644 index 26ed6544..00000000 --- a/app/Repositories/Concerns/Searchable.php +++ /dev/null @@ -1,64 +0,0 @@ -setSearchTerm($term); - } - - /** - * Set the search term to use when requesting all records from - * the model. - * - * @param string|null $term - * @return $this - */ - public function setSearchTerm(string $term = null) - { - if (empty($term)) { - return $this; - } - - $clone = clone $this; - $clone->searchTerm = $term; - - return $clone; - } - - /** - * Determine if a valid search term is set on this repository. - * - * @return bool - */ - public function hasSearchTerm(): bool - { - return ! empty($this->searchTerm); - } - - /** - * Return the search term. - * - * @return string|null - */ - public function getSearchTerm() - { - return $this->searchTerm; - } -} diff --git a/app/Repositories/Daemon/BaseRepository.php b/app/Repositories/Daemon/BaseRepository.php deleted file mode 100644 index 186b5917..00000000 --- a/app/Repositories/Daemon/BaseRepository.php +++ /dev/null @@ -1,154 +0,0 @@ -app = $app; - $this->nodeRepository = $nodeRepository; - } - - /** - * Set the node model to be used for this daemon connection. - * - * @param \Pterodactyl\Models\Node $node - * @return $this - */ - public function setNode(Node $node) - { - $this->node = $node; - - return $this; - } - - /** - * Return the node model being used. - * - * @return \Pterodactyl\Models\Node|null - */ - public function getNode() - { - return $this->node; - } - - /** - * Set the Server model to use when requesting information from the Daemon. - * - * @param \Pterodactyl\Models\Server $server - * @return $this - */ - public function setServer(Server $server) - { - $this->server = $server; - - return $this; - } - - /** - * Return the Server model. - * - * @return \Pterodactyl\Models\Server|null - */ - public function getServer() - { - return $this->server; - } - - /** - * Set the token to be used in the X-Access-Token header for requests to the daemon. - * - * @param string $token - * @return $this - */ - public function setToken(string $token) - { - $this->token = $token; - - return $this; - } - - /** - * Return the access token being used for requests. - * - * @return string|null - */ - public function getToken() - { - return $this->token; - } - - /** - * Return an instance of the Guzzle HTTP Client to be used for requests. - * - * @param array $headers - * @return \GuzzleHttp\Client - */ - public function getHttpClient(array $headers = []): Client - { - // If no node is set, load the relationship onto the Server model - // and pass that to the setNode function. - if (! $this->getNode() instanceof Node) { - if (! $this->getServer() instanceof Server) { - throw new RuntimeException('An instance of ' . Node::class . ' or ' . Server::class . ' must be set on this repository in order to return a client.'); - } - - $this->getServer()->loadMissing('node'); - $this->setNode($this->getServer()->getRelation('node')); - } - - if ($this->getServer() instanceof Server) { - $headers['X-Access-Server'] = $this->getServer()->uuid; - } - - $headers['X-Access-Token'] = $this->getToken() ?? $this->getNode()->daemonSecret; - - return new Client([ - 'verify' => config('app.env') === 'production', - 'base_uri' => sprintf('%s://%s:%s/v1/', $this->getNode()->scheme, $this->getNode()->fqdn, $this->getNode()->daemonListen), - 'timeout' => config('pterodactyl.guzzle.timeout'), - 'connect_timeout' => config('pterodactyl.guzzle.connect_timeout'), - 'headers' => $headers, - ]); - } -} diff --git a/app/Repositories/Daemon/CommandRepository.php b/app/Repositories/Daemon/CommandRepository.php deleted file mode 100644 index cd123cd8..00000000 --- a/app/Repositories/Daemon/CommandRepository.php +++ /dev/null @@ -1,25 +0,0 @@ -getHttpClient()->request('POST', 'server/command', [ - 'json' => [ - 'command' => $command, - ], - ]); - } -} diff --git a/app/Repositories/Daemon/ConfigurationRepository.php b/app/Repositories/Daemon/ConfigurationRepository.php deleted file mode 100644 index 3905335a..00000000 --- a/app/Repositories/Daemon/ConfigurationRepository.php +++ /dev/null @@ -1,46 +0,0 @@ -getNode(); - $structure = [ - 'web' => [ - 'listen' => $node->daemonListen, - 'ssl' => [ - 'enabled' => (! $node->behind_proxy && $node->scheme === 'https'), - ], - ], - 'sftp' => [ - 'path' => $node->daemonBase, - 'port' => $node->daemonSFTP, - ], - 'remote' => [ - 'base' => config('app.url'), - ], - 'uploads' => [ - 'size_limit' => $node->upload_size, - ], - 'keys' => [ - $node->daemonSecret, - ], - ]; - - return $this->getHttpClient()->request('PATCH', 'config', [ - 'json' => array_merge($structure, $overrides), - ]); - } -} diff --git a/app/Repositories/Daemon/FileRepository.php b/app/Repositories/Daemon/FileRepository.php deleted file mode 100644 index 46117f3c..00000000 --- a/app/Repositories/Daemon/FileRepository.php +++ /dev/null @@ -1,116 +0,0 @@ -getHttpClient()->request('GET', sprintf( - 'server/file/stat/%s', - rawurlencode($file['dirname'] . $file['basename']) - )); - - return json_decode($response->getBody()); - } - - /** - * Return the contents of a given file if it can be edited in the Panel. - * - * @param string $path - * @return string - * - * @throws \GuzzleHttp\Exception\GuzzleException - */ - public function getContent(string $path): string - { - $file = str_replace('\\', '/', pathinfo($path)); - $file['dirname'] = in_array($file['dirname'], ['.', './', '/']) ? null : trim($file['dirname'], '/') . '/'; - - $response = $this->getHttpClient()->request('GET', sprintf( - 'server/file/f/%s', - rawurlencode($file['dirname'] . $file['basename']) - )); - - return object_get(json_decode($response->getBody()), 'content'); - } - - /** - * Save new contents to a given file. - * - * @param string $path - * @param string $content - * @return \Psr\Http\Message\ResponseInterface - * - * @throws \GuzzleHttp\Exception\GuzzleException - */ - public function putContent(string $path, string $content): ResponseInterface - { - $file = str_replace('\\', '/', pathinfo($path)); - $file['dirname'] = in_array($file['dirname'], ['.', './', '/']) ? null : trim($file['dirname'], '/') . '/'; - - return $this->getHttpClient()->request('POST', 'server/file/save', [ - 'json' => [ - 'path' => rawurlencode($file['dirname'] . $file['basename']), - 'content' => $content, - ], - ]); - } - - /** - * Return a directory listing for a given path. - * - * @param string $path - * @return array - * - * @throws \GuzzleHttp\Exception\GuzzleException - */ - public function getDirectory(string $path): array - { - $response = $this->getHttpClient()->request('GET', sprintf('server/directory/%s', rawurlencode($path))); - - $contents = json_decode($response->getBody()); - $files = $folders = []; - - foreach ($contents as $value) { - if ($value->directory) { - array_push($folders, [ - 'entry' => $value->name, - 'directory' => trim($path, '/'), - 'size' => null, - 'date' => strtotime($value->modified), - 'mime' => $value->mime, - ]); - } elseif ($value->file) { - array_push($files, [ - 'entry' => $value->name, - 'directory' => trim($path, '/'), - 'extension' => str_replace('\\', '/', pathinfo($value->name, PATHINFO_EXTENSION)), - 'size' => human_readable($value->size), - 'date' => strtotime($value->modified), - 'mime' => $value->mime, - ]); - } - } - - return [ - 'files' => $files, - 'folders' => $folders, - ]; - } -} diff --git a/app/Repositories/Daemon/PowerRepository.php b/app/Repositories/Daemon/PowerRepository.php deleted file mode 100644 index d7ce8d5e..00000000 --- a/app/Repositories/Daemon/PowerRepository.php +++ /dev/null @@ -1,36 +0,0 @@ -getHttpClient()->request('PUT', 'server/power', [ - 'json' => [ - 'action' => $signal, - ], - ]); - default: - throw new InvalidPowerSignalException('The signal "' . $signal . '" is not defined and could not be processed.'); - } - } -} diff --git a/app/Repositories/Daemon/ServerRepository.php b/app/Repositories/Daemon/ServerRepository.php deleted file mode 100644 index f1bd445f..00000000 --- a/app/Repositories/Daemon/ServerRepository.php +++ /dev/null @@ -1,134 +0,0 @@ - $value) { - $structure[$key] = value($value); - } - - return $this->getHttpClient()->request('POST', 'servers', [ - 'json' => $structure, - ]); - } - - /** - * Update server details on the daemon. - * - * @param array $data - * @return \Psr\Http\Message\ResponseInterface - * @throws \GuzzleHttp\Exception\GuzzleException - */ - public function update(array $data): ResponseInterface - { - return $this->getHttpClient()->request('PATCH', 'server', [ - 'json' => $data, - ]); - } - - /** - * Mark a server to be reinstalled on the system. - * - * @param array|null $data - * @return \Psr\Http\Message\ResponseInterface - * @throws \GuzzleHttp\Exception\GuzzleException - */ - public function reinstall(array $data = null): ResponseInterface - { - return $this->getHttpClient()->request('POST', 'server/reinstall', [ - 'json' => $data ?? [], - ]); - } - - /** - * Mark a server as needing a container rebuild the next time the server is booted. - * - * @return \Psr\Http\Message\ResponseInterface - * @throws \GuzzleHttp\Exception\GuzzleException - */ - public function rebuild(): ResponseInterface - { - return $this->getHttpClient()->request('POST', 'server/rebuild'); - } - - /** - * Suspend a server on the daemon. - * - * @return \Psr\Http\Message\ResponseInterface - * @throws \GuzzleHttp\Exception\GuzzleException - */ - public function suspend(): ResponseInterface - { - return $this->getHttpClient()->request('POST', 'server/suspend'); - } - - /** - * Un-suspend a server on the daemon. - * - * @return \Psr\Http\Message\ResponseInterface - * @throws \GuzzleHttp\Exception\GuzzleException - */ - public function unsuspend(): ResponseInterface - { - return $this->getHttpClient()->request('POST', 'server/unsuspend'); - } - - /** - * Delete a server on the daemon. - * - * @return \Psr\Http\Message\ResponseInterface - * @throws \GuzzleHttp\Exception\GuzzleException - */ - public function delete(): ResponseInterface - { - return $this->getHttpClient()->request('DELETE', 'servers'); - } - - /** - * Return details on a specific server. - * - * @return \Psr\Http\Message\ResponseInterface - * @throws \GuzzleHttp\Exception\GuzzleException - */ - public function details(): ResponseInterface - { - return $this->getHttpClient()->request('GET', 'server'); - } - - /** - * Revoke an access key on the daemon before the time is expired. - * - * @param string|array $key - * @return \Psr\Http\Message\ResponseInterface - * - * @throws \GuzzleHttp\Exception\GuzzleException - */ - public function revokeAccessKey($key): ResponseInterface - { - if (is_array($key)) { - return $this->getHttpClient()->request('POST', 'keys/batch-delete', [ - 'json' => ['keys' => $key], - ]); - } - - Assert::stringNotEmpty($key, 'First argument passed to revokeAccessKey must be a non-empty string or array, received %s.'); - - return $this->getHttpClient()->request('DELETE', 'keys/' . $key); - } -} diff --git a/app/Repositories/Eloquent/AllocationRepository.php b/app/Repositories/Eloquent/AllocationRepository.php index a9721ac0..15a5db81 100644 --- a/app/Repositories/Eloquent/AllocationRepository.php +++ b/app/Repositories/Eloquent/AllocationRepository.php @@ -2,10 +2,8 @@ namespace Pterodactyl\Repositories\Eloquent; -use Illuminate\Support\Collection; use Pterodactyl\Models\Allocation; use Illuminate\Database\Eloquent\Builder; -use Illuminate\Contracts\Pagination\LengthAwarePaginator; use Pterodactyl\Contracts\Repository\AllocationRepositoryInterface; class AllocationRepository extends EloquentRepository implements AllocationRepositoryInterface @@ -20,55 +18,6 @@ class AllocationRepository extends EloquentRepository implements AllocationRepos return Allocation::class; } - /** - * Set an array of allocation IDs to be assigned to a specific server. - * - * @param int|null $server - * @param array $ids - * @return int - */ - public function assignAllocationsToServer(int $server = null, array $ids): int - { - return $this->getBuilder()->whereIn('id', $ids)->update(['server_id' => $server]); - } - - /** - * Return all of the allocations for a specific node. - * - * @param int $node - * @return \Illuminate\Support\Collection - */ - public function getAllocationsForNode(int $node): Collection - { - return $this->getBuilder()->where('node_id', $node)->get($this->getColumns()); - } - - /** - * Return all of the allocations for a node in a paginated format. - * - * @param int $node - * @param int $perPage - * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator - */ - public function getPaginatedAllocationsForNode(int $node, int $perPage = 100): LengthAwarePaginator - { - return $this->getBuilder()->where('node_id', $node)->paginate($perPage, $this->getColumns()); - } - - /** - * Return all of the unique IPs that exist for a given node. - * - * @param int $node - * @return \Illuminate\Support\Collection - */ - public function getUniqueAllocationIpsForNode(int $node): Collection - { - return $this->getBuilder()->where('node_id', $node) - ->groupBy('ip') - ->orderByRaw('INET_ATON(ip) ASC') - ->get($this->getColumns()); - } - /** * Return all of the allocations that exist for a node that are not currently * allocated. @@ -78,22 +27,12 @@ class AllocationRepository extends EloquentRepository implements AllocationRepos */ public function getUnassignedAllocationIds(int $node): array { - $results = $this->getBuilder()->select('id')->whereNull('server_id')->where('node_id', $node)->get(); - - return $results->pluck('id')->toArray(); - } - - /** - * Get an array of all allocations that are currently assigned to a given server. - * - * @param int $server - * @return array - */ - public function getAssignedAllocationIds(int $server): array - { - $results = $this->getBuilder()->select('id')->where('server_id', $server)->get(); - - return $results->pluck('id')->toArray(); + return Allocation::query()->select('id') + ->whereNull('server_id') + ->where('node_id', $node) + ->get() + ->pluck('id') + ->toArray(); } /** @@ -107,21 +46,19 @@ class AllocationRepository extends EloquentRepository implements AllocationRepos * @param array $nodes * @return array */ - public function getDiscardableDedicatedAllocations(array $nodes = []): array + protected function getDiscardableDedicatedAllocations(array $nodes = []): array { - $instance = $this->getBuilder()->select( - $this->getBuilder()->raw('CONCAT_WS("-", node_id, ip) as result') - ); + $query = Allocation::query()->selectRaw('CONCAT_WS("-", node_id, ip) as result'); if (! empty($nodes)) { - $instance->whereIn('node_id', $nodes); + $query->whereIn('node_id', $nodes); } - $results = $instance->whereNotNull('server_id') - ->groupBy($this->getBuilder()->raw('CONCAT(node_id, ip)')) - ->get(); - - return $results->pluck('result')->toArray(); + return $query->whereNotNull('server_id') + ->groupByRaw('CONCAT(node_id, ip)') + ->get() + ->pluck('result') + ->toArray(); } /** @@ -129,23 +66,23 @@ class AllocationRepository extends EloquentRepository implements AllocationRepos * * @param array $nodes * @param array $ports - * @param bool $dedicated + * @param bool $dedicated * @return \Pterodactyl\Models\Allocation|null */ public function getRandomAllocation(array $nodes, array $ports, bool $dedicated = false) { - $instance = $this->getBuilder()->whereNull('server_id'); + $query = Allocation::query()->whereNull('server_id'); if (! empty($nodes)) { - $instance->whereIn('node_id', $nodes); + $query->whereIn('node_id', $nodes); } if (! empty($ports)) { - $instance->where(function (Builder $query) use ($ports) { + $query->where(function (Builder $inner) use ($ports) { $whereIn = []; foreach ($ports as $port) { if (is_array($port)) { - $query->orWhereBetween('port', $port); + $inner->orWhereBetween('port', $port); continue; } @@ -153,7 +90,7 @@ class AllocationRepository extends EloquentRepository implements AllocationRepos } if (! empty($whereIn)) { - $query->orWhereIn('port', $whereIn); + $inner->orWhereIn('port', $whereIn); } }); } @@ -164,12 +101,12 @@ class AllocationRepository extends EloquentRepository implements AllocationRepos $discard = $this->getDiscardableDedicatedAllocations($nodes); if (! empty($discard)) { - $instance->whereNotIn( + $query->whereNotIn( $this->getBuilder()->raw('CONCAT_WS("-", node_id, ip)'), $discard ); } } - return $instance->inRandomOrder()->first(); + return $query->inRandomOrder()->first(); } } diff --git a/app/Repositories/Eloquent/ApiKeyRepository.php b/app/Repositories/Eloquent/ApiKeyRepository.php index 7ba0c998..55d55954 100644 --- a/app/Repositories/Eloquent/ApiKeyRepository.php +++ b/app/Repositories/Eloquent/ApiKeyRepository.php @@ -49,7 +49,7 @@ class ApiKeyRepository extends EloquentRepository implements ApiKeyRepositoryInt * Delete an account API key from the panel for a specific user. * * @param \Pterodactyl\Models\User $user - * @param string $identifier + * @param string $identifier * @return int */ public function deleteAccountKey(User $user, string $identifier): int @@ -64,7 +64,7 @@ class ApiKeyRepository extends EloquentRepository implements ApiKeyRepositoryInt * Delete an application API key from the panel for a specific user. * * @param \Pterodactyl\Models\User $user - * @param string $identifier + * @param string $identifier * @return int */ public function deleteApplicationKey(User $user, string $identifier): int diff --git a/app/Repositories/Eloquent/BackupRepository.php b/app/Repositories/Eloquent/BackupRepository.php new file mode 100644 index 00000000..adbbd9c9 --- /dev/null +++ b/app/Repositories/Eloquent/BackupRepository.php @@ -0,0 +1,35 @@ +getBuilder() + ->withTrashed() + ->where('server_id', $server) + ->where('is_successful', true) + ->where('created_at', '>=', Carbon::now()->subMinutes($minutes)->toDateTimeString()) + ->get() + ->toBase(); + } +} diff --git a/app/Repositories/Eloquent/DaemonKeyRepository.php b/app/Repositories/Eloquent/DaemonKeyRepository.php deleted file mode 100644 index c53f8a47..00000000 --- a/app/Repositories/Eloquent/DaemonKeyRepository.php +++ /dev/null @@ -1,87 +0,0 @@ -relationLoaded('server') || $refresh) { - $key->load('server'); - } - - if (! $key->relationLoaded('user') || $refresh) { - $key->load('user'); - } - - return $key; - } - - /** - * Return a daemon key with the associated server relation attached. - * - * @param string $key - * @return \Pterodactyl\Models\DaemonKey - * - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - */ - public function getKeyWithServer(string $key): DaemonKey - { - Assert::notEmpty($key, 'Expected non-empty string as first argument passed to ' . __METHOD__); - - try { - return $this->getBuilder()->with('server')->where('secret', '=', $key)->firstOrFail($this->getColumns()); - } catch (ModelNotFoundException $exception) { - throw new RecordNotFoundException; - } - } - - /** - * Get all of the keys for a specific user including the information needed - * from their server relation for revocation on the daemon. - * - * @param \Pterodactyl\Models\User $user - * @return \Illuminate\Support\Collection - */ - public function getKeysForRevocation(User $user): Collection - { - return $this->getBuilder()->with('node')->where('user_id', $user->id)->get($this->getColumns()); - } - - /** - * Delete an array of daemon keys from the database. Used primarily in - * conjunction with getKeysForRevocation. - * - * @param array $ids - * @return bool|int - */ - public function deleteKeys(array $ids) - { - return $this->getBuilder()->whereIn('id', $ids)->delete(); - } -} diff --git a/app/Repositories/Eloquent/DatabaseRepository.php b/app/Repositories/Eloquent/DatabaseRepository.php index 9f78efb7..46b3916d 100644 --- a/app/Repositories/Eloquent/DatabaseRepository.php +++ b/app/Repositories/Eloquent/DatabaseRepository.php @@ -25,7 +25,7 @@ class DatabaseRepository extends EloquentRepository implements DatabaseRepositor /** * DatabaseRepository constructor. * - * @param \Illuminate\Foundation\Application $application + * @param \Illuminate\Foundation\Application $application * @param \Illuminate\Database\DatabaseManager $database */ public function __construct(Application $application, DatabaseManager $database) @@ -76,7 +76,7 @@ class DatabaseRepository extends EloquentRepository implements DatabaseRepositor */ public function getDatabasesForServer(int $server): Collection { - return $this->getBuilder()->where('server_id', $server)->get($this->getColumns()); + return $this->getBuilder()->with('host')->where('server_id', $server)->get($this->getColumns()); } /** @@ -93,31 +93,6 @@ class DatabaseRepository extends EloquentRepository implements DatabaseRepositor ->paginate($count, $this->getColumns()); } - /** - * Create a new database if it does not already exist on the host with - * the provided details. - * - * @param array $data - * @return \Pterodactyl\Models\Database - * - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\DuplicateDatabaseNameException - */ - public function createIfNotExists(array $data): Database - { - $count = $this->getBuilder()->where([ - ['server_id', '=', array_get($data, 'server_id')], - ['database_host_id', '=', array_get($data, 'database_host_id')], - ['database', '=', array_get($data, 'database')], - ])->count(); - - if ($count > 0) { - throw new DuplicateDatabaseNameException('A database with those details already exists for the specified server.'); - } - - return $this->create($data); - } - /** * Create a new database on a given connection. * @@ -135,11 +110,16 @@ class DatabaseRepository extends EloquentRepository implements DatabaseRepositor * @param string $username * @param string $remote * @param string $password + * @param $max_connections * @return bool */ - public function createUser(string $username, string $remote, string $password): bool + public function createUser(string $username, string $remote, string $password, $max_connections): bool { - return $this->run(sprintf('CREATE USER `%s`@`%s` IDENTIFIED BY \'%s\'', $username, $remote, $password)); + if (! $max_connections) { + return $this->run(sprintf('CREATE USER `%s`@`%s` IDENTIFIED BY \'%s\'', $username, $remote, $password)); + } else { + return $this->run(sprintf('CREATE USER `%s`@`%s` IDENTIFIED BY \'%s\' WITH MAX_USER_CONNECTIONS %s', $username, $remote, $password, $max_connections)); + } } /** diff --git a/app/Repositories/Eloquent/EggRepository.php b/app/Repositories/Eloquent/EggRepository.php index a3a96efb..a716a5ec 100644 --- a/app/Repositories/Eloquent/EggRepository.php +++ b/app/Repositories/Eloquent/EggRepository.php @@ -52,7 +52,7 @@ class EggRepository extends EloquentRepository implements EggRepositoryInterface * Return an egg with the scriptFrom and configFrom relations loaded onto the model. * * @param int|string $value - * @param string $column + * @param string $column * @return \Pterodactyl\Models\Egg * * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException diff --git a/app/Repositories/Eloquent/EloquentRepository.php b/app/Repositories/Eloquent/EloquentRepository.php index cf093179..bd813302 100644 --- a/app/Repositories/Eloquent/EloquentRepository.php +++ b/app/Repositories/Eloquent/EloquentRepository.php @@ -2,9 +2,11 @@ namespace Pterodactyl\Repositories\Eloquent; +use Illuminate\Http\Request; use Webmozart\Assert\Assert; use Illuminate\Support\Collection; use Pterodactyl\Repositories\Repository; +use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Query\Expression; use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Contracts\Pagination\LengthAwarePaginator; @@ -15,6 +17,53 @@ use Pterodactyl\Contracts\Repository\Attributes\SearchableInterface; abstract class EloquentRepository extends Repository implements RepositoryInterface { + /** + * @var bool + */ + protected $useRequestFilters = false; + + /** + * Determines if the repository function should use filters off the request object + * present when returning results. This allows repository methods to be called in API + * context's such that we can pass through ?filter[name]=Dane&sort=desc for example. + * + * @param bool $usingFilters + * @return $this + */ + public function usingRequestFilters($usingFilters = true) + { + $this->useRequestFilters = $usingFilters; + + return $this; + } + + /** + * Returns the request instance. + * + * @return \Illuminate\Http\Request + */ + protected function request() + { + return $this->app->make(Request::class); + } + + /** + * Paginate the response data based on the page para. + * + * @param \Illuminate\Database\Eloquent\Builder $instance + * @param int $default + * + * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator + */ + protected function paginate(Builder $instance, int $default = 50) + { + if (! $this->useRequestFilters) { + return $instance->paginate($default); + } + + return $instance->paginate($this->request()->query('per_page', $default)); + } + /** * Return an instance of the eloquent model bound to this * repository instance. @@ -40,8 +89,8 @@ abstract class EloquentRepository extends Repository implements RepositoryInterf * Create a new record in the database and return the associated model. * * @param array $fields - * @param bool $validate - * @param bool $force + * @param bool $validate + * @param bool $force * @return \Illuminate\Database\Eloquent\Model|bool * * @throws \Pterodactyl\Exceptions\Model\DataValidationException @@ -121,7 +170,7 @@ abstract class EloquentRepository extends Repository implements RepositoryInterf /** * Delete a given record from the database. * - * @param int $id + * @param int $id * @param bool $destroy * @return int */ @@ -134,7 +183,7 @@ abstract class EloquentRepository extends Repository implements RepositoryInterf * Delete records matching the given attributes. * * @param array $attributes - * @param bool $force + * @param bool $force * @return int */ public function deleteWhere(array $attributes, bool $force = false): int @@ -147,10 +196,10 @@ abstract class EloquentRepository extends Repository implements RepositoryInterf /** * Update a given ID with the passed array of fields. * - * @param int $id + * @param int $id * @param array $fields - * @param bool $validate - * @param bool $force + * @param bool $validate + * @param bool $force * @return \Illuminate\Database\Eloquent\Model|bool * * @throws \Pterodactyl\Exceptions\Model\DataValidationException @@ -177,13 +226,25 @@ abstract class EloquentRepository extends Repository implements RepositoryInterf return ($this->withFresh) ? $instance->fresh() : $saved; } + /** + * Update a model using the attributes passed. + * + * @param array|\Closure $attributes + * @param array $values + * @return int + */ + public function updateWhere($attributes, array $values) + { + return $this->getBuilder()->where($attributes)->update($values); + } + /** * Perform a mass update where matching records are updated using whereIn. * This does not perform any model data validation. * * @param string $column - * @param array $values - * @param array $fields + * @param array $values + * @param array $fields * @return int */ public function updateWhereIn(string $column, array $values, array $fields): int @@ -198,8 +259,8 @@ abstract class EloquentRepository extends Repository implements RepositoryInterf * * @param array $where * @param array $fields - * @param bool $validate - * @param bool $force + * @param bool $validate + * @param bool $force * @return \Illuminate\Database\Eloquent\Model * * @throws \Pterodactyl\Exceptions\Model\DataValidationException @@ -224,15 +285,11 @@ abstract class EloquentRepository extends Repository implements RepositoryInterf * Return all records associated with the given model. * * @return \Illuminate\Support\Collection + * @deprecated Just use the model */ public function all(): Collection { - $instance = $this->getBuilder(); - if (is_subclass_of(get_called_class(), SearchableInterface::class) && $this->hasSearchTerm()) { - $instance = $instance->search($this->getSearchTerm()); - } - - return $instance->get($this->getColumns()); + return $this->getBuilder()->get($this->getColumns()); } /** @@ -243,12 +300,7 @@ abstract class EloquentRepository extends Repository implements RepositoryInterf */ public function paginated(int $perPage): LengthAwarePaginator { - $instance = $this->getBuilder(); - if (is_subclass_of(get_called_class(), SearchableInterface::class) && $this->hasSearchTerm()) { - $instance = $instance->search($this->getSearchTerm()); - } - - return $instance->paginate($perPage, $this->getColumns()); + return $this->getBuilder()->paginate($perPage, $this->getColumns()); } /** @@ -301,6 +353,7 @@ abstract class EloquentRepository extends Repository implements RepositoryInterf * Get the amount of entries in the database. * * @return int + * @deprecated just use the count method off a model */ public function count(): int { diff --git a/app/Repositories/Eloquent/LocationRepository.php b/app/Repositories/Eloquent/LocationRepository.php index 47d5e321..6d14d5aa 100644 --- a/app/Repositories/Eloquent/LocationRepository.php +++ b/app/Repositories/Eloquent/LocationRepository.php @@ -4,15 +4,12 @@ namespace Pterodactyl\Repositories\Eloquent; use Pterodactyl\Models\Location; use Illuminate\Support\Collection; -use Pterodactyl\Repositories\Concerns\Searchable; use Illuminate\Database\Eloquent\ModelNotFoundException; use Pterodactyl\Exceptions\Repository\RecordNotFoundException; use Pterodactyl\Contracts\Repository\LocationRepositoryInterface; class LocationRepository extends EloquentRepository implements LocationRepositoryInterface { - use Searchable; - /** * Return the model backing this repository. * diff --git a/app/Repositories/Eloquent/MountRepository.php b/app/Repositories/Eloquent/MountRepository.php new file mode 100644 index 00000000..286e0791 --- /dev/null +++ b/app/Repositories/Eloquent/MountRepository.php @@ -0,0 +1,67 @@ +getBuilder()->withCount('eggs', 'nodes')->get($this->getColumns()); + } + + /** + * Return all of the mounts and their respective relations. + * + * @param string $id + * @return mixed + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function getWithRelations(string $id): Mount + { + try { + return $this->getBuilder()->with('eggs', 'nodes')->findOrFail($id, $this->getColumns()); + } catch (ModelNotFoundException $exception) { + throw new RecordNotFoundException; + } + } + + /** + * Return mounts available to a server (ignoring if they are or are not mounted). + * + * @param Server $server + * @return \Illuminate\Support\Collection + */ + public function getMountListForServer(Server $server): Collection + { + return $this->getBuilder() + ->whereHas('eggs', function ($q) use ($server) { + $q->where('id', '=', $server->egg_id); + }) + ->whereHas('nodes', function ($q) use ($server) { + $q->where('id', '=', $server->node_id); + }) + ->get($this->getColumns()); + } +} diff --git a/app/Repositories/Eloquent/NestRepository.php b/app/Repositories/Eloquent/NestRepository.php index 9c0fcf73..96b3df36 100644 --- a/app/Repositories/Eloquent/NestRepository.php +++ b/app/Repositories/Eloquent/NestRepository.php @@ -26,7 +26,7 @@ class NestRepository extends EloquentRepository implements NestRepositoryInterfa } /** - * Return a nest or all nests with their associated eggs, variables, and packs. + * Return a nest or all nests with their associated eggs and variables. * * @param int $id * @return \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\Nest @@ -35,7 +35,7 @@ class NestRepository extends EloquentRepository implements NestRepositoryInterfa */ public function getWithEggs(int $id = null) { - $instance = $this->getBuilder()->with('eggs.packs', 'eggs.variables'); + $instance = $this->getBuilder()->with('eggs', 'eggs.variables'); if (! is_null($id)) { $instance = $instance->find($id, $this->getColumns()); @@ -50,7 +50,7 @@ class NestRepository extends EloquentRepository implements NestRepositoryInterfa } /** - * Return a nest or all nests and the count of eggs, packs, and servers for that nest. + * Return a nest or all nests and the count of eggs and servers for that nest. * * @param int|null $id * @return \Pterodactyl\Models\Nest|\Illuminate\Database\Eloquent\Collection @@ -59,7 +59,7 @@ class NestRepository extends EloquentRepository implements NestRepositoryInterfa */ public function getWithCounts(int $id = null) { - $instance = $this->getBuilder()->withCount(['eggs', 'packs', 'servers']); + $instance = $this->getBuilder()->withCount(['eggs', 'servers']); if (! is_null($id)) { $instance = $instance->find($id, $this->getColumns()); diff --git a/app/Repositories/Eloquent/NodeRepository.php b/app/Repositories/Eloquent/NodeRepository.php index 3b86f2ee..9c852fb5 100644 --- a/app/Repositories/Eloquent/NodeRepository.php +++ b/app/Repositories/Eloquent/NodeRepository.php @@ -2,17 +2,14 @@ namespace Pterodactyl\Repositories\Eloquent; -use Generator; use Pterodactyl\Models\Node; use Illuminate\Support\Collection; -use Pterodactyl\Repositories\Concerns\Searchable; +use Illuminate\Support\LazyCollection; use Illuminate\Contracts\Pagination\LengthAwarePaginator; use Pterodactyl\Contracts\Repository\NodeRepositoryInterface; class NodeRepository extends EloquentRepository implements NodeRepositoryInterface { - use Searchable; - /** * Return the model backing this repository. * @@ -31,27 +28,31 @@ class NodeRepository extends EloquentRepository implements NodeRepositoryInterfa */ public function getUsageStats(Node $node): array { - $stats = $this->getBuilder()->select( - $this->getBuilder()->raw('IFNULL(SUM(servers.memory), 0) as sum_memory, IFNULL(SUM(servers.disk), 0) as sum_disk') - )->join('servers', 'servers.node_id', '=', 'nodes.id')->where('node_id', $node->id)->first(); + $stats = $this->getBuilder() + ->selectRaw('IFNULL(SUM(servers.memory), 0) as sum_memory, IFNULL(SUM(servers.disk), 0) as sum_disk') + ->join('servers', 'servers.node_id', '=', 'nodes.id') + ->where('node_id', '=', $node->id) + ->first(); - return collect(['disk' => $stats->sum_disk, 'memory' => $stats->sum_memory])->mapWithKeys(function ($value, $key) use ($node) { - $maxUsage = $node->{$key}; - if ($node->{$key . '_overallocate'} > 0) { - $maxUsage = $node->{$key} * (1 + ($node->{$key . '_overallocate'} / 100)); - } + return Collection::make(['disk' => $stats->sum_disk, 'memory' => $stats->sum_memory]) + ->mapWithKeys(function ($value, $key) use ($node) { + $maxUsage = $node->{$key}; + if ($node->{$key . '_overallocate'} > 0) { + $maxUsage = $node->{$key} * (1 + ($node->{$key . '_overallocate'} / 100)); + } - $percent = ($value / $maxUsage) * 100; + $percent = ($value / $maxUsage) * 100; - return [ - $key => [ - 'value' => number_format($value), - 'max' => number_format($maxUsage), - 'percent' => $percent, - 'css' => ($percent <= self::THRESHOLD_PERCENTAGE_LOW) ? 'green' : (($percent > self::THRESHOLD_PERCENTAGE_MEDIUM) ? 'red' : 'yellow'), - ], - ]; - })->toArray(); + return [ + $key => [ + 'value' => number_format($value), + 'max' => number_format($maxUsage), + 'percent' => $percent, + 'css' => ($percent <= self::THRESHOLD_PERCENTAGE_LOW) ? 'green' : (($percent > self::THRESHOLD_PERCENTAGE_MEDIUM) ? 'red' : 'yellow'), + ], + ]; + }) + ->toArray(); } /** @@ -81,27 +82,11 @@ class NodeRepository extends EloquentRepository implements NodeRepositoryInterfa })->toArray(); } - /** - * Return all available nodes with a searchable interface. - * - * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator - */ - public function getNodeListingData(): LengthAwarePaginator - { - $instance = $this->getBuilder()->with('location')->withCount('servers'); - - if ($this->hasSearchTerm()) { - $instance->search($this->getSearchTerm()); - } - - return $instance->paginate(25, $this->getColumns()); - } - /** * Return a single node with location and server information. * * @param \Pterodactyl\Models\Node $node - * @param bool $refresh + * @param bool $refresh * @return \Pterodactyl\Models\Node */ public function loadLocationAndServerCount(Node $node, bool $refresh = false): Node @@ -126,13 +111,18 @@ class NodeRepository extends EloquentRepository implements NodeRepositoryInterfa * any servers that are also attached to those allocations. * * @param \Pterodactyl\Models\Node $node - * @param bool $refresh + * @param bool $refresh * @return \Pterodactyl\Models\Node */ public function loadNodeAllocations(Node $node, bool $refresh = false): Node { $node->setRelation('allocations', - $node->allocations()->orderByRaw('server_id IS NOT NULL DESC, server_id IS NULL')->orderByRaw('INET_ATON(ip) ASC')->orderBy('port', 'asc')->with('server:id,name')->paginate(50) + $node->allocations() + ->orderByRaw('server_id IS NOT NULL DESC, server_id IS NULL') + ->orderByRaw('INET_ATON(ip) ASC') + ->orderBy('port', 'asc') + ->with('server:id,name') + ->paginate(50) ); return $node; @@ -166,26 +156,19 @@ class NodeRepository extends EloquentRepository implements NodeRepositoryInterfa } /** - * Return the IDs of all nodes that exist in the provided locations and have the space - * available to support the additional disk and memory provided. + * Returns a node with the given id with the Node's resource usage. * - * @param array $locations - * @param int $disk - * @param int $memory - * @return \Generator + * @param int $node_id + * @return Node */ - public function getNodesWithResourceUse(array $locations, int $disk, int $memory): Generator + public function getNodeWithResourceUsage(int $node_id): Node { $instance = $this->getBuilder() - ->select(['nodes.id', 'nodes.memory', 'nodes.disk', 'nodes.memory_overallocate', 'nodes.disk_overallocate']) + ->select(['nodes.id', 'nodes.fqdn', 'nodes.scheme', 'nodes.daemon_token', 'nodes.daemonListen', 'nodes.memory', 'nodes.disk', 'nodes.memory_overallocate', 'nodes.disk_overallocate']) ->selectRaw('IFNULL(SUM(servers.memory), 0) as sum_memory, IFNULL(SUM(servers.disk), 0) as sum_disk') ->leftJoin('servers', 'servers.node_id', '=', 'nodes.id') - ->where('nodes.public', 1); + ->where('nodes.id', $node_id); - if (! empty($locations)) { - $instance->whereIn('nodes.location_id', $locations); - } - - return $instance->groupBy('nodes.id')->cursor(); + return $instance->first(); } } diff --git a/app/Repositories/Eloquent/PackRepository.php b/app/Repositories/Eloquent/PackRepository.php deleted file mode 100644 index 922e6415..00000000 --- a/app/Repositories/Eloquent/PackRepository.php +++ /dev/null @@ -1,53 +0,0 @@ -load(['servers.node', 'servers.user']); - } - - $pack->loadMissing(['servers.node', 'servers.user']); - - return $pack; - } - - /** - * Return a paginated listing of packs with their associated egg and server count. - * - * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator - */ - public function paginateWithEggAndServerCount(): LengthAwarePaginator - { - return $this->getBuilder()->with('egg')->withCount('servers') - ->search($this->getSearchTerm()) - ->paginate(50, $this->getColumns()); - } -} diff --git a/app/Repositories/Eloquent/PermissionRepository.php b/app/Repositories/Eloquent/PermissionRepository.php index ad2fa638..e2d0b8cb 100644 --- a/app/Repositories/Eloquent/PermissionRepository.php +++ b/app/Repositories/Eloquent/PermissionRepository.php @@ -2,7 +2,7 @@ namespace Pterodactyl\Repositories\Eloquent; -use Pterodactyl\Models\Permission; +use Exception; use Pterodactyl\Contracts\Repository\PermissionRepositoryInterface; class PermissionRepository extends EloquentRepository implements PermissionRepositoryInterface @@ -11,9 +11,10 @@ class PermissionRepository extends EloquentRepository implements PermissionRepos * Return the model backing this repository. * * @return string + * @throws \Exception */ public function model() { - return Permission::class; + throw new Exception('This functionality is not implemented.'); } } diff --git a/app/Repositories/Eloquent/RecoveryTokenRepository.php b/app/Repositories/Eloquent/RecoveryTokenRepository.php new file mode 100644 index 00000000..5dfeeacf --- /dev/null +++ b/app/Repositories/Eloquent/RecoveryTokenRepository.php @@ -0,0 +1,16 @@ +getBuilder()->with('node', 'user', 'allocation')->search($this->getSearchTerm()); - - return $instance->paginate($paginate, $this->getColumns()); - } - /** * Load the egg relations onto the server model. * * @param \Pterodactyl\Models\Server $server - * @param bool $refresh + * @param bool $refresh * @return \Pterodactyl\Models\Server */ public function loadEggRelations(Server $server, bool $refresh = false): Server @@ -65,11 +47,31 @@ class ServerRepository extends EloquentRepository implements ServerRepositoryInt */ public function getDataForRebuild(int $server = null, int $node = null): Collection { - $instance = $this->getBuilder()->with(['allocation', 'allocations', 'pack', 'egg', 'node']); + $instance = $this->getBuilder()->with(['allocation', 'allocations', 'egg', 'node']); if (! is_null($server) && is_null($node)) { $instance = $instance->where('id', '=', $server); - } elseif (is_null($server) && ! is_null($node)) { + } else if (is_null($server) && ! is_null($node)) { + $instance = $instance->where('node_id', '=', $node); + } + + return $instance->get($this->getColumns()); + } + + /** + * Return a collection of servers with their associated data for reinstall operations. + * + * @param int|null $server + * @param int|null $node + * @return \Illuminate\Support\Collection + */ + public function getDataForReinstall(int $server = null, int $node = null): Collection + { + $instance = $this->getBuilder()->with(['allocation', 'allocations', 'egg', 'node']); + + if (! is_null($server) && is_null($node)) { + $instance = $instance->where('id', '=', $server); + } else if (is_null($server) && ! is_null($node)) { $instance = $instance->where('node_id', '=', $node); } @@ -101,7 +103,7 @@ class ServerRepository extends EloquentRepository implements ServerRepositoryInt * return the server from the database. * * @param \Pterodactyl\Models\Server $server - * @param bool $refresh + * @param bool $refresh * @return \Pterodactyl\Models\Server */ public function getPrimaryAllocation(Server $server, bool $refresh = false): Server @@ -113,51 +115,16 @@ class ServerRepository extends EloquentRepository implements ServerRepositoryInt return $server; } - /** - * Return all of the server variables possible and default to the variable - * default if there is no value defined for the specific server requested. - * - * @param int $id - * @param bool $returnAsObject - * @return array|object - * - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - */ - public function getVariablesWithValues(int $id, bool $returnAsObject = false) - { - try { - $instance = $this->getBuilder()->with('variables', 'egg.variables')->find($id, $this->getColumns()); - } catch (ModelNotFoundException $exception) { - throw new RecordNotFoundException; - } - - $data = []; - $instance->getRelation('egg')->getRelation('variables')->each(function ($item) use (&$data, $instance) { - $display = $instance->getRelation('variables')->where('variable_id', $item->id)->pluck('variable_value')->first(); - - $data[$item->env_variable] = $display ?? $item->default_value; - }); - - if ($returnAsObject) { - return (object) [ - 'data' => $data, - 'server' => $instance, - ]; - } - - return $data; - } - /** * Return enough data to be used for the creation of a server via the daemon. * * @param \Pterodactyl\Models\Server $server - * @param bool $refresh + * @param bool $refresh * @return \Pterodactyl\Models\Server */ public function getDataForCreation(Server $server, bool $refresh = false): Server { - foreach (['allocation', 'allocations', 'pack', 'egg'] as $relation) { + foreach (['allocation', 'allocations', 'egg'] as $relation) { if (! $server->relationLoaded($relation) || $refresh) { $server->load($relation); } @@ -170,7 +137,7 @@ class ServerRepository extends EloquentRepository implements ServerRepositoryInt * Load associated databases onto the server model. * * @param \Pterodactyl\Models\Server $server - * @param bool $refresh + * @param bool $refresh * @return \Pterodactyl\Models\Server */ public function loadDatabaseRelations(Server $server, bool $refresh = false): Server @@ -184,11 +151,11 @@ class ServerRepository extends EloquentRepository implements ServerRepositoryInt /** * Get data for use when updating a server on the Daemon. Returns an array of - * the egg and pack UUID which are used for build and rebuild. Only loads relations + * the egg which is used for build and rebuild. Only loads relations * if they are missing, or refresh is set to true. * * @param \Pterodactyl\Models\Server $server - * @param bool $refresh + * @param bool $refresh * @return array */ public function getDaemonServiceData(Server $server, bool $refresh = false): array @@ -197,53 +164,11 @@ class ServerRepository extends EloquentRepository implements ServerRepositoryInt $server->load('egg'); } - if (! $server->relationLoaded('pack') || $refresh) { - $server->load('pack'); - } - return [ 'egg' => $server->getRelation('egg')->uuid, - 'pack' => is_null($server->getRelation('pack')) ? null : $server->getRelation('pack')->uuid, ]; } - /** - * Return a paginated list of servers that a user can access at a given level. - * - * @param \Pterodactyl\Models\User $user - * @param int $level - * @param bool|int $paginate - * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator|\Illuminate\Database\Eloquent\Collection - */ - public function filterUserAccessServers(User $user, int $level, $paginate = 25) - { - $instance = $this->getBuilder()->select($this->getColumns())->with(['user', 'node', 'allocation']); - - // If access level is set to owner, only display servers - // that the user owns. - if ($level === User::FILTER_LEVEL_OWNER) { - $instance->where('owner_id', $user->id); - } - - // If set to all, display all servers they can access, including - // those they access as an admin. If set to subuser, only return - // the servers they can access because they are owner, or marked - // as a subuser of the server. - elseif (($level === User::FILTER_LEVEL_ALL && ! $user->root_admin) || $level === User::FILTER_LEVEL_SUBUSER) { - $instance->whereIn('id', $this->getUserAccessServers($user->id)); - } - - // If set to admin, only display the servers a user can access - // as an administrator (leaves out owned and subuser of). - elseif ($level === User::FILTER_LEVEL_ADMIN && $user->root_admin) { - $instance->whereNotIn('id', $this->getUserAccessServers($user->id)); - } - - $instance->search($this->getSearchTerm()); - - return $paginate ? $instance->paginate($paginate) : $instance->get(); - } - /** * Return a server by UUID. * @@ -254,12 +179,16 @@ class ServerRepository extends EloquentRepository implements ServerRepositoryInt */ public function getByUuid(string $uuid): Server { - Assert::notEmpty($uuid, 'Expected non-empty string as first argument passed to ' . __METHOD__); - try { - return $this->getBuilder()->with('nest', 'node')->where(function ($query) use ($uuid) { - $query->where('uuidShort', $uuid)->orWhere('uuid', $uuid); - })->firstOrFail($this->getColumns()); + /** @var \Pterodactyl\Models\Server $model */ + $model = $this->getBuilder() + ->with('nest', 'node') + ->where(function (Builder $query) use ($uuid) { + $query->where('uuidShort', $uuid)->orWhere('uuid', $uuid); + }) + ->firstOrFail($this->getColumns()); + + return $model; } catch (ModelNotFoundException $exception) { throw new RecordNotFoundException; } @@ -270,8 +199,8 @@ class ServerRepository extends EloquentRepository implements ServerRepositoryInt * * @param int[] $servers * @param int[] $nodes - * @param bool $returnCount - * @return int|\Generator + * @param bool $returnCount + * @return int|\Illuminate\Support\LazyCollection */ public function getServersForPowerAction(array $servers = [], array $nodes = [], bool $returnCount = false) { @@ -279,9 +208,9 @@ class ServerRepository extends EloquentRepository implements ServerRepositoryInt if (! empty($nodes) && ! empty($servers)) { $instance->whereIn('id', $servers)->orWhereIn('node_id', $nodes); - } elseif (empty($nodes) && ! empty($servers)) { + } else if (empty($nodes) && ! empty($servers)) { $instance->whereIn('id', $servers); - } elseif (! empty($nodes) && empty($servers)) { + } else if (! empty($nodes) && empty($servers)) { $instance->whereIn('node_id', $nodes); } @@ -316,20 +245,6 @@ class ServerRepository extends EloquentRepository implements ServerRepositoryInt return ! $this->getBuilder()->where('uuid', '=', $uuid)->orWhere('uuidShort', '=', $short)->exists(); } - /** - * Return an array of server IDs that a given user can access based - * on owner and subuser permissions. - * - * @param int $user - * @return int[] - */ - private function getUserAccessServers(int $user): array - { - return $this->getBuilder()->select('id')->where('owner_id', $user)->union( - $this->app->make(SubuserRepository::class)->getBuilder()->select('server_id')->where('user_id', $user) - )->pluck('id')->all(); - } - /** * Get the amount of servers that are suspended. * @@ -355,4 +270,22 @@ class ServerRepository extends EloquentRepository implements ServerRepositoryInt ->where('node_id', '=', $node) ->paginate($limit); } + + /** + * Returns every server that exists for a given node. + * + * This is different from {@see loadAllServersForNode} because + * it does not paginate the response. + * + * @param int $node + * + * @return \Illuminate\Database\Eloquent\Builder[]|\Illuminate\Database\Eloquent\Collection + */ + public function loadEveryServerForNode(int $node) + { + return $this->getBuilder() + ->with('nest') + ->where('node_id', '=', $node) + ->get(); + } } diff --git a/app/Repositories/Eloquent/SessionRepository.php b/app/Repositories/Eloquent/SessionRepository.php index a4922645..54c4ff62 100644 --- a/app/Repositories/Eloquent/SessionRepository.php +++ b/app/Repositories/Eloquent/SessionRepository.php @@ -32,9 +32,9 @@ class SessionRepository extends EloquentRepository implements SessionRepositoryI /** * Delete a session for a given user. * - * @param int $user + * @param int $user * @param string $session - * @return null|int + * @return int|null */ public function deleteUserSession(int $user, string $session) { diff --git a/app/Repositories/Eloquent/SettingsRepository.php b/app/Repositories/Eloquent/SettingsRepository.php index 0d25a1b8..42cc7efe 100644 --- a/app/Repositories/Eloquent/SettingsRepository.php +++ b/app/Repositories/Eloquent/SettingsRepository.php @@ -30,7 +30,7 @@ class SettingsRepository extends EloquentRepository implements SettingsRepositor /** * Store a new persistent setting in the database. * - * @param string $key + * @param string $key * @param string|null $value * * @throws \Pterodactyl\Exceptions\Model\DataValidationException @@ -49,7 +49,7 @@ class SettingsRepository extends EloquentRepository implements SettingsRepositor * Retrieve a persistent setting from the database. * * @param string $key - * @param mixed $default + * @param mixed $default * @return mixed */ public function get(string $key, $default = null) diff --git a/app/Repositories/Eloquent/SubuserRepository.php b/app/Repositories/Eloquent/SubuserRepository.php index 0296e0db..c0fb930a 100644 --- a/app/Repositories/Eloquent/SubuserRepository.php +++ b/app/Repositories/Eloquent/SubuserRepository.php @@ -22,7 +22,7 @@ class SubuserRepository extends EloquentRepository implements SubuserRepositoryI * Return a subuser with the associated server relationship. * * @param \Pterodactyl\Models\Subuser $subuser - * @param bool $refresh + * @param bool $refresh * @return \Pterodactyl\Models\Subuser */ public function loadServerAndUserRelations(Subuser $subuser, bool $refresh = false): Subuser @@ -42,7 +42,7 @@ class SubuserRepository extends EloquentRepository implements SubuserRepositoryI * Return a subuser with the associated permissions relationship. * * @param \Pterodactyl\Models\Subuser $subuser - * @param bool $refresh + * @param bool $refresh * @return \Pterodactyl\Models\Subuser */ public function getWithPermissions(Subuser $subuser, bool $refresh = false): Subuser diff --git a/app/Repositories/Eloquent/TaskRepository.php b/app/Repositories/Eloquent/TaskRepository.php index 0c1202f5..3b43221e 100644 --- a/app/Repositories/Eloquent/TaskRepository.php +++ b/app/Repositories/Eloquent/TaskRepository.php @@ -41,7 +41,7 @@ class TaskRepository extends EloquentRepository implements TaskRepositoryInterfa * * @param int $schedule * @param int $index - * @return null|\Pterodactyl\Models\Task + * @return \Pterodactyl\Models\Task|null */ public function getNextTask(int $schedule, int $index) { diff --git a/app/Repositories/Eloquent/UserRepository.php b/app/Repositories/Eloquent/UserRepository.php index b69df198..72a88efb 100644 --- a/app/Repositories/Eloquent/UserRepository.php +++ b/app/Repositories/Eloquent/UserRepository.php @@ -3,15 +3,10 @@ namespace Pterodactyl\Repositories\Eloquent; use Pterodactyl\Models\User; -use Illuminate\Support\Collection; -use Pterodactyl\Repositories\Concerns\Searchable; -use Illuminate\Contracts\Pagination\LengthAwarePaginator; use Pterodactyl\Contracts\Repository\UserRepositoryInterface; class UserRepository extends EloquentRepository implements UserRepositoryInterface { - use Searchable; - /** * Return the model backing this repository. * @@ -21,37 +16,4 @@ class UserRepository extends EloquentRepository implements UserRepositoryInterfa { return User::class; } - - /** - * Return all users with counts of servers and subusers of servers. - * - * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator - */ - public function getAllUsersWithCounts(): LengthAwarePaginator - { - return $this->getBuilder()->withCount('servers', 'subuserOf') - ->search($this->getSearchTerm()) - ->paginate(50, $this->getColumns()); - } - - /** - * Return all matching models for a user in a format that can be used for dropdowns. - * - * @param string|null $query - * @return \Illuminate\Support\Collection - */ - public function filterUsersByQuery(?string $query): Collection - { - $this->setColumns([ - 'id', 'email', 'username', 'name_first', 'name_last', - ]); - - $instance = $this->getBuilder()->search($query)->get($this->getColumns()); - - return $instance->transform(function ($item) { - $item->md5 = md5(strtolower($item->email)); - - return $item; - }); - } } diff --git a/app/Repositories/Wings/DaemonBackupRepository.php b/app/Repositories/Wings/DaemonBackupRepository.php new file mode 100644 index 00000000..418b5dd6 --- /dev/null +++ b/app/Repositories/Wings/DaemonBackupRepository.php @@ -0,0 +1,81 @@ +adapter = $adapter; + + return $this; + } + + /** + * Tells the remote Daemon to begin generating a backup for the server. + * + * @param \Pterodactyl\Models\Backup $backup + * @param string|null $presignedUrl + * @return \Psr\Http\Message\ResponseInterface + * + * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException + */ + public function backup(Backup $backup, string $presignedUrl = null): ResponseInterface + { + Assert::isInstanceOf($this->server, Server::class); + + try { + return $this->getHttpClient()->post( + sprintf('/api/servers/%s/backup', $this->server->uuid), + [ + 'json' => [ + 'adapter' => $this->adapter ?? config('backups.default'), + 'uuid' => $backup->uuid, + 'ignored_files' => $backup->ignored_files, + 'presigned_url' => $presignedUrl, + ], + ] + ); + } catch (TransferException $exception) { + throw new DaemonConnectionException($exception); + } + } + + /** + * Deletes a backup from the daemon. + * + * @param \Pterodactyl\Models\Backup $backup + * @return \Psr\Http\Message\ResponseInterface + * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException + */ + public function delete(Backup $backup): ResponseInterface + { + Assert::isInstanceOf($this->server, Server::class); + + try { + return $this->getHttpClient()->delete( + sprintf('/api/servers/%s/backup/%s', $this->server->uuid, $backup->uuid) + ); + } catch (TransferException $exception) { + throw new DaemonConnectionException($exception); + } + } +} diff --git a/app/Repositories/Wings/DaemonCommandRepository.php b/app/Repositories/Wings/DaemonCommandRepository.php new file mode 100644 index 00000000..38f2fb47 --- /dev/null +++ b/app/Repositories/Wings/DaemonCommandRepository.php @@ -0,0 +1,36 @@ +server, Server::class); + + try { + return $this->getHttpClient()->post( + sprintf('/api/servers/%s/commands', $this->server->uuid), + [ + 'json' => ['commands' => is_array($command) ? $command : [$command]], + ] + ); + } catch (TransferException $exception) { + throw new DaemonConnectionException($exception); + } + } +} diff --git a/app/Repositories/Wings/DaemonConfigurationRepository.php b/app/Repositories/Wings/DaemonConfigurationRepository.php new file mode 100644 index 00000000..ffd498cb --- /dev/null +++ b/app/Repositories/Wings/DaemonConfigurationRepository.php @@ -0,0 +1,47 @@ +getHttpClient()->get('/api/system'); + } catch (TransferException $exception) { + throw new DaemonConnectionException($exception); + } + + return json_decode($response->getBody()->__toString(), true); + } + + /** + * Updates the configuration information for a daemon. Updates the information for + * this instance using a passed-in model. This allows us to change plenty of information + * in the model, and still use the old, pre-update model to actually make the HTTP request. + * + * @param \Pterodactyl\Models\Node $node + * @return \Psr\Http\Message\ResponseInterface + * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException + */ + public function update(Node $node) + { + try { + return $this->getHttpClient()->post( + '/api/update', ['json' => $node->getConfiguration()] + ); + } catch (TransferException $exception) { + throw new DaemonConnectionException($exception); + } + } +} diff --git a/app/Repositories/Wings/DaemonFileRepository.php b/app/Repositories/Wings/DaemonFileRepository.php new file mode 100644 index 00000000..1ae42458 --- /dev/null +++ b/app/Repositories/Wings/DaemonFileRepository.php @@ -0,0 +1,272 @@ +server, Server::class); + + try { + $response = $this->getHttpClient()->get( + sprintf('/api/servers/%s/files/contents', $this->server->uuid), + [ + 'query' => ['file' => $path], + ] + ); + } catch (TransferException $exception) { + throw new DaemonConnectionException($exception); + } + + $length = (int) $response->getHeader('Content-Length')[0] ?? 0; + + if ($notLargerThan && $length > $notLargerThan) { + throw new FileSizeTooLargeException; + } + + return $response->getBody()->__toString(); + } + + /** + * Save new contents to a given file. This works for both creating and updating + * a file. + * + * @param string $path + * @param string $content + * @return \Psr\Http\Message\ResponseInterface + * + * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException + */ + public function putContent(string $path, string $content): ResponseInterface + { + Assert::isInstanceOf($this->server, Server::class); + + try { + return $this->getHttpClient()->post( + sprintf('/api/servers/%s/files/write', $this->server->uuid), + [ + 'query' => ['file' => $path], + 'body' => $content, + ] + ); + } catch (TransferException $exception) { + throw new DaemonConnectionException($exception); + } + } + + /** + * Return a directory listing for a given path. + * + * @param string $path + * @return array + * + * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException + */ + public function getDirectory(string $path): array + { + Assert::isInstanceOf($this->server, Server::class); + + try { + $response = $this->getHttpClient()->get( + sprintf('/api/servers/%s/files/list-directory', $this->server->uuid), + [ + 'query' => ['directory' => $path], + ] + ); + } catch (TransferException $exception) { + throw new DaemonConnectionException($exception); + } + + return json_decode($response->getBody(), true); + } + + /** + * Creates a new directory for the server in the given $path. + * + * @param string $name + * @param string $path + * @return \Psr\Http\Message\ResponseInterface + * + * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException + */ + public function createDirectory(string $name, string $path): ResponseInterface + { + Assert::isInstanceOf($this->server, Server::class); + + try { + return $this->getHttpClient()->post( + sprintf('/api/servers/%s/files/create-directory', $this->server->uuid), + [ + 'json' => [ + 'name' => $name, + 'path' => $path, + ], + ] + ); + } catch (TransferException $exception) { + throw new DaemonConnectionException($exception); + } + } + + /** + * Renames or moves a file on the remote machine. + * + * @param string|null $root + * @param array $files + * @return \Psr\Http\Message\ResponseInterface + * + * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException + */ + public function renameFiles(?string $root, array $files): ResponseInterface + { + Assert::isInstanceOf($this->server, Server::class); + + try { + return $this->getHttpClient()->put( + sprintf('/api/servers/%s/files/rename', $this->server->uuid), + [ + 'json' => [ + 'root' => $root ?? '/', + 'files' => $files, + ], + ] + ); + } catch (TransferException $exception) { + throw new DaemonConnectionException($exception); + } + } + + /** + * Copy a given file and give it a unique name. + * + * @param string $location + * @return \Psr\Http\Message\ResponseInterface + * + * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException + */ + public function copyFile(string $location): ResponseInterface + { + Assert::isInstanceOf($this->server, Server::class); + + try { + return $this->getHttpClient()->post( + sprintf('/api/servers/%s/files/copy', $this->server->uuid), + [ + 'json' => [ + 'location' => $location, + ], + ] + ); + } catch (TransferException $exception) { + throw new DaemonConnectionException($exception); + } + } + + /** + * Delete a file or folder for the server. + * + * @param string|null $root + * @param array $files + * @return \Psr\Http\Message\ResponseInterface + * + * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException + */ + public function deleteFiles(?string $root, array $files): ResponseInterface + { + Assert::isInstanceOf($this->server, Server::class); + + try { + return $this->getHttpClient()->post( + sprintf('/api/servers/%s/files/delete', $this->server->uuid), + [ + 'json' => [ + 'root' => $root ?? '/', + 'files' => $files, + ], + ] + ); + } catch (TransferException $exception) { + throw new DaemonConnectionException($exception); + } + } + + /** + * Compress the given files or folders in the given root. + * + * @param string|null $root + * @param array $files + * @return array + * + * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException + */ + public function compressFiles(?string $root, array $files): array + { + Assert::isInstanceOf($this->server, Server::class); + + try { + $response = $this->getHttpClient()->post( + sprintf('/api/servers/%s/files/compress', $this->server->uuid), + [ + 'json' => [ + 'root' => $root ?? '/', + 'files' => $files, + ], + // Wait for up to 15 minutes for the archive to be completed when calling this endpoint + // since it will likely take quite awhile for large directories. + 'timeout' => 60 * 15, + ] + ); + } catch (TransferException $exception) { + throw new DaemonConnectionException($exception); + } + + return json_decode($response->getBody(), true); + } + + /** + * Decompresses a given archive file. + * + * @param string|null $root + * @param string $file + * @return \Psr\Http\Message\ResponseInterface + * + * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException + */ + public function decompressFile(?string $root, string $file): ResponseInterface + { + Assert::isInstanceOf($this->server, Server::class); + + try { + return $this->getHttpClient()->post( + sprintf('/api/servers/%s/files/decompress', $this->server->uuid), + [ + 'json' => [ + 'root' => $root ?? '/', + 'file' => $file, + ], + ] + ); + } catch (TransferException $exception) { + throw new DaemonConnectionException($exception); + } + } +} diff --git a/app/Repositories/Wings/DaemonPowerRepository.php b/app/Repositories/Wings/DaemonPowerRepository.php new file mode 100644 index 00000000..ccbf169f --- /dev/null +++ b/app/Repositories/Wings/DaemonPowerRepository.php @@ -0,0 +1,34 @@ +server, Server::class); + + try { + return $this->getHttpClient()->post( + sprintf('/api/servers/%s/power', $this->server->uuid), + ['json' => ['action' => $action]] + ); + } catch (TransferException $exception) { + throw new DaemonConnectionException($exception); + } + } +} diff --git a/app/Repositories/Wings/DaemonRepository.php b/app/Repositories/Wings/DaemonRepository.php new file mode 100644 index 00000000..4ebe249c --- /dev/null +++ b/app/Repositories/Wings/DaemonRepository.php @@ -0,0 +1,88 @@ +app = $application; + } + + /** + * Set the server model this request is stemming from. + * + * @param \Pterodactyl\Models\Server $server + * @return $this + */ + public function setServer(Server $server) + { + $this->server = $server; + + $this->setNode($this->server->node); + + return $this; + } + + /** + * Set the node model this request is stemming from. + * + * @param \Pterodactyl\Models\Node $node + * @return $this + */ + public function setNode(Node $node) + { + $this->node = $node; + + return $this; + } + + /** + * Return an instance of the Guzzle HTTP Client to be used for requests. + * + * @param array $headers + * @return \GuzzleHttp\Client + */ + public function getHttpClient(array $headers = []): Client + { + Assert::isInstanceOf($this->node, Node::class); + + return new Client([ + 'verify' => $this->app->environment('production'), + 'base_uri' => $this->node->getConnectionAddress(), + 'timeout' => config('pterodactyl.guzzle.timeout'), + 'connect_timeout' => config('pterodactyl.guzzle.connect_timeout'), + 'headers' => array_merge($headers, [ + 'Authorization' => 'Bearer ' . $this->node->getDecryptedKey(), + 'Accept' => 'application/json', + 'Content-Type' => 'application/json', + ]), + ]); + } +} diff --git a/app/Repositories/Wings/DaemonServerRepository.php b/app/Repositories/Wings/DaemonServerRepository.php new file mode 100644 index 00000000..abb5dae4 --- /dev/null +++ b/app/Repositories/Wings/DaemonServerRepository.php @@ -0,0 +1,147 @@ +server, Server::class); + + try { + $response = $this->getHttpClient()->get( + sprintf('/api/servers/%s', $this->server->uuid) + ); + } catch (TransferException $exception) { + throw new DaemonConnectionException($exception, false); + } + + return json_decode($response->getBody()->__toString(), true); + } + + /** + * Creates a new server on the Wings daemon. + * + * @param array $data + * + * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException + */ + public function create(array $data): void + { + Assert::isInstanceOf($this->server, Server::class); + + try { + $this->getHttpClient()->post( + '/api/servers', [ + 'json' => $data, + ] + ); + } catch (TransferException $exception) { + throw new DaemonConnectionException($exception); + } + } + + /** + * Updates details about a server on the Daemon. + * + * @param array $data + * + * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException + */ + public function update(array $data): void + { + Assert::isInstanceOf($this->server, Server::class); + + try { + $this->getHttpClient()->patch('/api/servers/' . $this->server->uuid, ['json' => $data]); + } catch (TransferException $exception) { + throw new DaemonConnectionException($exception); + } + } + + /** + * Delete a server from the daemon, forcibly if passed. + * + * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException + */ + public function delete(): void + { + Assert::isInstanceOf($this->server, Server::class); + + try { + $this->getHttpClient()->delete('/api/servers/' . $this->server->uuid); + } catch (TransferException $exception) { + throw new DaemonConnectionException($exception); + } + } + + /** + * Reinstall a server on the daemon. + * + * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException + */ + public function reinstall(): void + { + Assert::isInstanceOf($this->server, Server::class); + + try { + $this->getHttpClient()->post(sprintf( + '/api/servers/%s/reinstall', $this->server->uuid + )); + } catch (TransferException $exception) { + throw new DaemonConnectionException($exception); + } + } + + /** + * By default this function will suspend a server instance on the daemon. However, passing + * "true" as the first argument will unsuspend the server. + * + * @param bool $unsuspend + * + * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException + */ + public function suspend(bool $unsuspend = false): void + { + Assert::isInstanceOf($this->server, Server::class); + + try { + $this->getHttpClient()->patch( + '/api/servers/' . $this->server->uuid, + ['json' => ['suspended' => ! $unsuspend]] + ); + } catch (TransferException $exception) { + throw new DaemonConnectionException($exception); + } + } + + /** + * Requests the daemon to create a full archive of the server. + * Once the daemon is finished they will send a POST request to + * "/api/remote/servers/{uuid}/archive" with a boolean. + * + * @throws DaemonConnectionException + */ + public function requestArchive(): void + { + Assert::isInstanceOf($this->server, Server::class); + + try { + $this->getHttpClient()->post(sprintf( + '/api/servers/%s/archive', $this->server->uuid + )); + } catch (TransferException $exception) { + throw new DaemonConnectionException($exception); + } + } +} diff --git a/app/Repositories/Wings/DaemonTransferRepository.php b/app/Repositories/Wings/DaemonTransferRepository.php new file mode 100644 index 00000000..e8a51419 --- /dev/null +++ b/app/Repositories/Wings/DaemonTransferRepository.php @@ -0,0 +1,35 @@ +getHttpClient()->post('/api/transfer', [ + 'json' => [ + 'server_id' => $server->uuid, + 'url' => $node->getConnectionAddress() . sprintf('/api/servers/%s/archive', $server->uuid), + 'token' => 'Bearer ' . $token, + 'server' => $data, + ], + ]); + } catch (TransferException $exception) { + throw new DaemonConnectionException($exception); + } + } +} diff --git a/app/Rules/Username.php b/app/Rules/Username.php index 335c3c18..1cceae5f 100644 --- a/app/Rules/Username.php +++ b/app/Rules/Username.php @@ -18,7 +18,7 @@ class Username implements Rule * Allowed characters: a-z0-9_-. * * @param string $attribute - * @param mixed $value + * @param mixed $value * @return bool */ public function passes($attribute, $value): bool diff --git a/app/Services/Acl/Api/AdminAcl.php b/app/Services/Acl/Api/AdminAcl.php index 6dfa8797..96085e15 100644 --- a/app/Services/Acl/Api/AdminAcl.php +++ b/app/Services/Acl/Api/AdminAcl.php @@ -34,7 +34,6 @@ class AdminAcl const RESOURCE_EGGS = 'eggs'; const RESOURCE_DATABASE_HOSTS = 'database_hosts'; const RESOURCE_SERVER_DATABASES = 'server_databases'; - const RESOURCE_PACKS = 'packs'; /** * Determine if an API key has permission to perform a specific read/write operation. @@ -57,8 +56,8 @@ class AdminAcl * at a specific action level. * * @param \Pterodactyl\Models\ApiKey $key - * @param string $resource - * @param int $action + * @param string $resource + * @param int $action * @return bool */ public static function check(ApiKey $key, string $resource, int $action = self::READ) diff --git a/app/Services/Allocations/AssignmentService.php b/app/Services/Allocations/AssignmentService.php index 905bb776..99ae40f2 100644 --- a/app/Services/Allocations/AssignmentService.php +++ b/app/Services/Allocations/AssignmentService.php @@ -34,7 +34,7 @@ class AssignmentService * AssignmentService constructor. * * @param \Pterodactyl\Contracts\Repository\AllocationRepositoryInterface $repository - * @param \Illuminate\Database\ConnectionInterface $connection + * @param \Illuminate\Database\ConnectionInterface $connection */ public function __construct(AllocationRepositoryInterface $repository, ConnectionInterface $connection) { @@ -46,7 +46,7 @@ class AssignmentService * Insert allocations into the database and link them to a specific node. * * @param \Pterodactyl\Models\Node $node - * @param array $data + * @param array $data * * @throws \Pterodactyl\Exceptions\Service\Allocation\CidrOutOfRangeException * @throws \Pterodactyl\Exceptions\Service\Allocation\PortOutOfRangeException diff --git a/app/Services/Allocations/SetDefaultAllocationService.php b/app/Services/Allocations/SetDefaultAllocationService.php deleted file mode 100644 index 6e901031..00000000 --- a/app/Services/Allocations/SetDefaultAllocationService.php +++ /dev/null @@ -1,110 +0,0 @@ -connection = $connection; - $this->daemonRepository = $daemonRepository; - $this->repository = $repository; - $this->serverRepository = $serverRepository; - } - - /** - * Update the default allocation for a server only if that allocation is currently - * assigned to the specified server. - * - * @param int|\Pterodactyl\Models\Server $server - * @param int $allocation - * @return \Pterodactyl\Models\Allocation - * - * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - * @throws \Pterodactyl\Exceptions\Service\Allocation\AllocationDoesNotBelongToServerException - */ - public function handle($server, int $allocation): Allocation - { - if (! $server instanceof Server) { - $server = $this->serverRepository->find($server); - } - - $allocations = $this->repository->findWhere([['server_id', '=', $server->id]]); - $model = $allocations->filter(function ($model) use ($allocation) { - return $model->id === $allocation; - })->first(); - - if (! $model instanceof Allocation) { - throw new AllocationDoesNotBelongToServerException; - } - - $this->connection->beginTransaction(); - $this->serverRepository->withoutFreshModel()->update($server->id, ['allocation_id' => $model->id]); - - // Update on the daemon. - try { - $this->daemonRepository->setServer($server)->update([ - 'build' => [ - 'default' => [ - 'ip' => $model->ip, - 'port' => $model->port, - ], - 'ports|overwrite' => $allocations->groupBy('ip')->map(function ($item) { - return $item->pluck('port'); - })->toArray(), - ], - ]); - - $this->connection->commit(); - } catch (RequestException $exception) { - $this->connection->rollBack(); - throw new DaemonConnectionException($exception); - } - - return $model; - } -} diff --git a/app/Services/Api/KeyCreationService.php b/app/Services/Api/KeyCreationService.php index b5143162..a1547897 100644 --- a/app/Services/Api/KeyCreationService.php +++ b/app/Services/Api/KeyCreationService.php @@ -27,7 +27,7 @@ class KeyCreationService * ApiKeyService constructor. * * @param \Pterodactyl\Contracts\Repository\ApiKeyRepositoryInterface $repository - * @param \Illuminate\Contracts\Encryption\Encrypter $encrypter + * @param \Illuminate\Contracts\Encryption\Encrypter $encrypter */ public function __construct(ApiKeyRepositoryInterface $repository, Encrypter $encrypter) { @@ -72,8 +72,6 @@ class KeyCreationService $data = array_merge($data, $permissions); } - $instance = $this->repository->create($data, true, true); - - return $instance; + return $this->repository->create($data, true, true); } } diff --git a/app/Services/Backups/DeleteBackupService.php b/app/Services/Backups/DeleteBackupService.php new file mode 100644 index 00000000..cdb61844 --- /dev/null +++ b/app/Services/Backups/DeleteBackupService.php @@ -0,0 +1,106 @@ +repository = $repository; + $this->daemonBackupRepository = $daemonBackupRepository; + $this->connection = $connection; + $this->manager = $manager; + } + + /** + * Deletes a backup from the system. + * + * @param \Pterodactyl\Models\Backup $backup + * @throws \Throwable + */ + public function handle(Backup $backup) + { + if ($backup->disk === Backup::ADAPTER_AWS_S3) { + $this->deleteFromS3($backup); + + return; + } + + $this->connection->transaction(function () use ($backup) { + try { + $this->daemonBackupRepository->setServer($backup->server)->delete($backup); + } catch (DaemonConnectionException $exception) { + $previous = $exception->getPrevious(); + // Don't fail the request if the Daemon responds with a 404, just assume the backup + // doesn't actually exist and remove it's reference from the Panel as well. + if (! $previous instanceof ClientException || $previous->getResponse()->getStatusCode() !== Response::HTTP_NOT_FOUND) { + throw $exception; + } + } + + $this->repository->delete($backup->id); + }); + } + + /** + * Deletes a backup from an S3 disk. + * + * @param \Pterodactyl\Models\Backup $backup + * @throws \Throwable + */ + protected function deleteFromS3(Backup $backup) + { + $this->connection->transaction(function () use ($backup) { + $this->repository->delete($backup->id); + + /** @var \League\Flysystem\AwsS3v3\AwsS3Adapter $adapter */ + $adapter = $this->manager->adapter(Backup::ADAPTER_AWS_S3); + + $adapter->getClient()->deleteObject([ + 'Bucket' => $adapter->getBucket(), + 'Key' => sprintf('%s/%s.tar.gz', $backup->server->uuid, $backup->uuid), + ]); + }); + } +} diff --git a/app/Services/Backups/InitiateBackupService.php b/app/Services/Backups/InitiateBackupService.php new file mode 100644 index 00000000..304386a6 --- /dev/null +++ b/app/Services/Backups/InitiateBackupService.php @@ -0,0 +1,163 @@ +repository = $repository; + $this->connection = $connection; + $this->daemonBackupRepository = $daemonBackupRepository; + $this->backupManager = $backupManager; + } + + /** + * Sets the files to be ignored by this backup. + * + * @param string[]|null $ignored + * @return $this + */ + public function setIgnoredFiles(?array $ignored) + { + if (is_array($ignored)) { + foreach ($ignored as $value) { + Assert::string($value); + } + } + + // Set the ignored files to be any values that are not empty in the array. Don't use + // the PHP empty function here incase anything that is "empty" by default (0, false, etc.) + // were passed as a file or folder name. + $this->ignoredFiles = is_null($ignored) ? [] : array_filter($ignored, function ($value) { + return strlen($value) > 0; + }); + + return $this; + } + + /** + * Initiates the backup process for a server on the daemon. + * + * @param \Pterodactyl\Models\Server $server + * @param string|null $name + * @return \Pterodactyl\Models\Backup + * + * @throws \Throwable + * @throws \Pterodactyl\Exceptions\Service\Backup\TooManyBackupsException + * @throws \Symfony\Component\HttpKernel\Exception\TooManyRequestsHttpException + */ + public function handle(Server $server, string $name = null): Backup + { + // Do not allow the user to continue if this server is already at its limit. + if (! $server->backup_limit || $server->backups()->where('is_successful', true)->count() >= $server->backup_limit) { + throw new TooManyBackupsException($server->backup_limit); + } + + $previous = $this->repository->getBackupsGeneratedDuringTimespan($server->id, 10); + if ($previous->count() >= 2) { + throw new TooManyRequestsHttpException( + CarbonImmutable::now()->diffInSeconds($previous->last()->created_at->addMinutes(10)), + 'Only two backups may be generated within a 10 minute span of time.' + ); + } + + return $this->connection->transaction(function () use ($server, $name) { + /** @var \Pterodactyl\Models\Backup $backup */ + $backup = $this->repository->create([ + 'server_id' => $server->id, + 'uuid' => Uuid::uuid4()->toString(), + 'name' => trim($name) ?: sprintf('Backup at %s', CarbonImmutable::now()->toDateTimeString()), + 'ignored_files' => is_array($this->ignoredFiles) ? array_values($this->ignoredFiles) : [], + 'disk' => $this->backupManager->getDefaultAdapter(), + ], true, true); + + $url = $this->getS3PresignedUrl(sprintf('%s/%s.tar.gz', $server->uuid, $backup->uuid)); + + $this->daemonBackupRepository->setServer($server) + ->setBackupAdapter($this->backupManager->getDefaultAdapter()) + ->backup($backup, $url); + + return $backup; + }); + } + + /** + * Generates a presigned URL for the wings daemon to upload the completed archive + * to. We use a 30 minute expiration on these URLs to avoid issues with large backups + * that may take some time to complete. + * + * @param string $path + * @return string|null + */ + protected function getS3PresignedUrl(string $path) + { + $adapter = $this->backupManager->adapter(); + if (! $adapter instanceof AwsS3Adapter) { + return null; + } + + $client = $adapter->getClient(); + + $request = $client->createPresignedRequest( + $client->getCommand('PutObject', [ + 'Bucket' => $adapter->getBucket(), + 'Key' => $path, + 'ContentType' => 'application/x-gzip', + ]), + CarbonImmutable::now()->addMinutes(30) + ); + + return $request->getUri()->__toString(); + } +} diff --git a/app/Services/DaemonKeys/DaemonKeyCreationService.php b/app/Services/DaemonKeys/DaemonKeyCreationService.php deleted file mode 100644 index 23aa1c0a..00000000 --- a/app/Services/DaemonKeys/DaemonKeyCreationService.php +++ /dev/null @@ -1,87 +0,0 @@ -. - * - * 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. - */ - -namespace Pterodactyl\Services\DaemonKeys; - -use Carbon\Carbon; -use Illuminate\Contracts\Config\Repository as ConfigRepository; -use Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface; - -class DaemonKeyCreationService -{ - /** - * @var \Carbon\Carbon - */ - protected $carbon; - - /** - * @var \Illuminate\Contracts\Config\Repository - */ - protected $config; - - /** - * @var \Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface - */ - protected $repository; - - /** - * DaemonKeyCreationService constructor. - * - * @param \Carbon\Carbon $carbon - * @param \Illuminate\Contracts\Config\Repository $config - * @param \Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface $repository - */ - public function __construct( - Carbon $carbon, - ConfigRepository $config, - DaemonKeyRepositoryInterface $repository - ) { - $this->carbon = $carbon; - $this->config = $config; - $this->repository = $repository; - } - - /** - * Create a new daemon key to be used when connecting to a daemon. - * - * @param int $server - * @param int $user - * @return string - * - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - */ - public function handle(int $server, int $user) - { - $secret = DaemonKeyRepositoryInterface::INTERNAL_KEY_IDENTIFIER . str_random(40); - - $this->repository->withoutFreshModel()->create([ - 'user_id' => $user, - 'server_id' => $server, - 'secret' => $secret, - 'expires_at' => $this->carbon->now()->addMinutes($this->config->get('pterodactyl.api.key_expire_time'))->toDateTimeString(), - ]); - - return $secret; - } -} diff --git a/app/Services/DaemonKeys/DaemonKeyDeletionService.php b/app/Services/DaemonKeys/DaemonKeyDeletionService.php deleted file mode 100644 index 054122d8..00000000 --- a/app/Services/DaemonKeys/DaemonKeyDeletionService.php +++ /dev/null @@ -1,124 +0,0 @@ -. - * - * 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. - */ - -namespace Pterodactyl\Services\DaemonKeys; - -use Webmozart\Assert\Assert; -use Pterodactyl\Models\Server; -use Psr\Log\LoggerInterface as Writer; -use GuzzleHttp\Exception\RequestException; -use Illuminate\Database\ConnectionInterface; -use Pterodactyl\Exceptions\DisplayException; -use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; -use Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface; -use Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface as DaemonServerRepositoryInterface; - -class DaemonKeyDeletionService -{ - /** - * @var \Illuminate\Database\ConnectionInterface - */ - protected $connection; - - /** - * @var \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface - */ - protected $daemonRepository; - - /** - * @var \Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface - */ - protected $repository; - - /** - * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface - */ - protected $serverRepository; - - /** - * @var \Psr\Log\LoggerInterface - */ - protected $writer; - - /** - * DaemonKeyDeletionService constructor. - * - * @param \Illuminate\Database\ConnectionInterface $connection - * @param \Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface $repository - * @param \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface $daemonRepository - * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $serverRepository - * @param \Psr\Log\LoggerInterface $writer - */ - public function __construct( - ConnectionInterface $connection, - DaemonKeyRepositoryInterface $repository, - DaemonServerRepositoryInterface $daemonRepository, - ServerRepositoryInterface $serverRepository, - Writer $writer - ) { - $this->connection = $connection; - $this->daemonRepository = $daemonRepository; - $this->repository = $repository; - $this->serverRepository = $serverRepository; - $this->writer = $writer; - } - - /** - * @param \Pterodactyl\Models\Server|int $server - * @param int $user - * - * @throws \Pterodactyl\Exceptions\DisplayException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - */ - public function handle($server, $user) - { - Assert::integerish($user, 'Second argument passed to handle must be an integer, received %s.'); - - if (! $server instanceof Server) { - $server = $this->serverRepository->find($server); - } - - $this->connection->beginTransaction(); - $key = $this->repository->findFirstWhere([ - ['user_id', '=', $user], - ['server_id', '=', $server->id], - ]); - - $this->repository->delete($key->id); - - try { - $this->daemonRepository->setServer($server)->revokeAccessKey($key->secret); - } catch (RequestException $exception) { - $response = $exception->getResponse(); - $this->connection->rollBack(); - $this->writer->warning($exception); - - throw new DisplayException(trans('admin/server.exceptions.daemon_exception', [ - 'code' => is_null($response) ? 'E_CONN_REFUSED' : $response->getStatusCode(), - ])); - } - - $this->connection->commit(); - } -} diff --git a/app/Services/DaemonKeys/DaemonKeyProviderService.php b/app/Services/DaemonKeys/DaemonKeyProviderService.php deleted file mode 100644 index a386d286..00000000 --- a/app/Services/DaemonKeys/DaemonKeyProviderService.php +++ /dev/null @@ -1,121 +0,0 @@ -. - * - * 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. - */ - -namespace Pterodactyl\Services\DaemonKeys; - -use Carbon\Carbon; -use Pterodactyl\Models\User; -use Pterodactyl\Models\Server; -use Pterodactyl\Exceptions\Repository\RecordNotFoundException; -use Pterodactyl\Contracts\Repository\SubuserRepositoryInterface; -use Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface; - -class DaemonKeyProviderService -{ - /** - * @var \Pterodactyl\Services\DaemonKeys\DaemonKeyCreationService - */ - private $keyCreationService; - - /** - * @var \Pterodactyl\Services\DaemonKeys\DaemonKeyUpdateService - */ - private $keyUpdateService; - - /** - * @var \Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface - */ - private $repository; - - /** - * @var \Pterodactyl\Contracts\Repository\SubuserRepositoryInterface - */ - private $subuserRepository; - - /** - * GetDaemonKeyService constructor. - * - * @param \Pterodactyl\Services\DaemonKeys\DaemonKeyCreationService $keyCreationService - * @param \Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface $repository - * @param \Pterodactyl\Services\DaemonKeys\DaemonKeyUpdateService $keyUpdateService - * @param \Pterodactyl\Contracts\Repository\SubuserRepositoryInterface $subuserRepository - */ - public function __construct( - DaemonKeyCreationService $keyCreationService, - DaemonKeyRepositoryInterface $repository, - DaemonKeyUpdateService $keyUpdateService, - SubuserRepositoryInterface $subuserRepository - ) { - $this->keyCreationService = $keyCreationService; - $this->keyUpdateService = $keyUpdateService; - $this->repository = $repository; - $this->subuserRepository = $subuserRepository; - } - - /** - * Get the access key for a user on a specific server. - * - * @param \Pterodactyl\Models\Server $server - * @param \Pterodactyl\Models\User $user - * @param bool $updateIfExpired - * @return string - * - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - */ - public function handle(Server $server, User $user, $updateIfExpired = true): string - { - try { - $key = $this->repository->findFirstWhere([ - ['user_id', '=', $user->id], - ['server_id', '=', $server->id], - ]); - } catch (RecordNotFoundException $exception) { - // If key doesn't exist but we are an admin or the server owner, - // create it. - if ($user->root_admin || $user->id === $server->owner_id) { - return $this->keyCreationService->handle($server->id, $user->id); - } - - // Check if user is a subuser for this server. Ideally they should always have - // a record associated with them in the database, but we should still handle - // that potentiality here. - // - // If no subuser is found, a RecordNotFoundException will be thrown, thus handling - // the parent error as well. - $subuser = $this->subuserRepository->findFirstWhere([ - ['user_id', '=', $user->id], - ['server_id', '=', $server->id], - ]); - - return $this->keyCreationService->handle($subuser->server_id, $subuser->user_id); - } - - if (! $updateIfExpired || Carbon::now()->diffInSeconds($key->expires_at, false) > 0) { - return $key->secret; - } - - return $this->keyUpdateService->handle($key->id); - } -} diff --git a/app/Services/DaemonKeys/DaemonKeyUpdateService.php b/app/Services/DaemonKeys/DaemonKeyUpdateService.php deleted file mode 100644 index 750d833d..00000000 --- a/app/Services/DaemonKeys/DaemonKeyUpdateService.php +++ /dev/null @@ -1,88 +0,0 @@ -. - * - * 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. - */ - -namespace Pterodactyl\Services\DaemonKeys; - -use Carbon\Carbon; -use Webmozart\Assert\Assert; -use Illuminate\Contracts\Config\Repository as ConfigRepository; -use Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface; - -class DaemonKeyUpdateService -{ - /** - * @var \Carbon\Carbon - */ - protected $carbon; - - /** - * @var \Illuminate\Contracts\Config\Repository - */ - protected $config; - - /** - * @var \Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface - */ - protected $repository; - - /** - * DaemonKeyUpdateService constructor. - * - * @param \Carbon\Carbon $carbon - * @param \Illuminate\Contracts\Config\Repository $config - * @param \Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface $repository - */ - public function __construct( - Carbon $carbon, - ConfigRepository $config, - DaemonKeyRepositoryInterface $repository - ) { - $this->carbon = $carbon; - $this->config = $config; - $this->repository = $repository; - } - - /** - * Update a daemon key to expire the previous one. - * - * @param int $key - * @return string - * - * @throws \RuntimeException - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - */ - public function handle($key) - { - Assert::integerish($key, 'First argument passed to handle must be an integer, received %s.'); - - $secret = DaemonKeyRepositoryInterface::INTERNAL_KEY_IDENTIFIER . str_random(40); - $this->repository->withoutFreshModel()->update($key, [ - 'secret' => $secret, - 'expires_at' => $this->carbon->now()->addMinutes($this->config->get('pterodactyl.api.key_expire_time'))->toDateTimeString(), - ]); - - return $secret; - } -} diff --git a/app/Services/DaemonKeys/RevokeMultipleDaemonKeysService.php b/app/Services/DaemonKeys/RevokeMultipleDaemonKeysService.php deleted file mode 100644 index 7059be88..00000000 --- a/app/Services/DaemonKeys/RevokeMultipleDaemonKeysService.php +++ /dev/null @@ -1,89 +0,0 @@ -daemonRepository = $daemonRepository; - $this->repository = $repository; - } - - /** - * Grab all of the keys that exist for a single user and delete them from all - * daemon's that they are assigned to. If connection fails, this function will - * return an error. - * - * @param \Pterodactyl\Models\User $user - * @param bool $ignoreConnectionErrors - */ - public function handle(User $user, bool $ignoreConnectionErrors = false) - { - $keys = $this->repository->getKeysForRevocation($user); - - $keys->groupBy('node.id')->each(function ($group, $nodeId) use ($ignoreConnectionErrors) { - try { - $this->daemonRepository->setNode(collect($group)->first()->getRelation('node'))->revokeAccessKey(collect($group)->pluck('secret')->toArray()); - } catch (RequestException $exception) { - if (! $ignoreConnectionErrors) { - throw new DaemonConnectionException($exception); - } - - $this->setConnectionException($nodeId, $exception); - } - - $this->repository->deleteKeys(collect($group)->pluck('id')->toArray()); - }); - } - - /** - * Returns an array of exceptions that were returned by the handle function. - * - * @return RequestException[] - */ - public function getExceptions() - { - return $this->exceptions; - } - - /** - * Add an exception for a node to the array. - * - * @param int $node - * @param \GuzzleHttp\Exception\RequestException $exception - */ - protected function setConnectionException(int $node, RequestException $exception) - { - $this->exceptions[$node] = $exception; - } -} diff --git a/app/Services/Databases/DatabaseManagementService.php b/app/Services/Databases/DatabaseManagementService.php index 103befa5..832e4bdf 100644 --- a/app/Services/Databases/DatabaseManagementService.php +++ b/app/Services/Databases/DatabaseManagementService.php @@ -2,19 +2,34 @@ namespace Pterodactyl\Services\Databases; +use Exception; +use InvalidArgumentException; +use Pterodactyl\Models\Server; use Pterodactyl\Models\Database; use Pterodactyl\Helpers\Utilities; -use Illuminate\Database\DatabaseManager; +use Illuminate\Database\ConnectionInterface; +use Symfony\Component\VarDumper\Cloner\Data; use Illuminate\Contracts\Encryption\Encrypter; use Pterodactyl\Extensions\DynamicDatabaseConnection; -use Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface; +use Pterodactyl\Repositories\Eloquent\DatabaseRepository; +use Pterodactyl\Exceptions\Repository\DuplicateDatabaseNameException; +use Pterodactyl\Exceptions\Service\Database\TooManyDatabasesException; +use Pterodactyl\Exceptions\Service\Database\DatabaseClientFeatureNotEnabledException; class DatabaseManagementService { /** - * @var \Illuminate\Database\DatabaseManager + * The regex used to validate that the database name passed through to the function is + * in the expected format. + * + * @see \Pterodactyl\Services\Databases\DatabaseManagementService::generateUniqueDatabaseName() */ - private $database; + private const MATCH_NAME_REGEX = '/^(s[\d]+_)(.*)$/'; + + /** + * @var \Illuminate\Database\ConnectionInterface + */ + private $connection; /** * @var \Pterodactyl\Extensions\DynamicDatabaseConnection @@ -27,107 +42,187 @@ class DatabaseManagementService private $encrypter; /** - * @var \Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface + * @var \Pterodactyl\Repositories\Eloquent\DatabaseRepository */ private $repository; /** + * Determines if the service should validate the user's ability to create an additional + * database for this server. In almost all cases this should be true, but to keep things + * flexible you can also set it to false and create more databases than the server is + * allocated. + * * @var bool */ - protected $useRandomHost = false; + protected $validateDatabaseLimit = true; /** * CreationService constructor. * - * @param \Illuminate\Database\DatabaseManager $database - * @param \Pterodactyl\Extensions\DynamicDatabaseConnection $dynamic - * @param \Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface $repository - * @param \Illuminate\Contracts\Encryption\Encrypter $encrypter + * @param \Illuminate\Database\ConnectionInterface $connection + * @param \Pterodactyl\Extensions\DynamicDatabaseConnection $dynamic + * @param \Pterodactyl\Repositories\Eloquent\DatabaseRepository $repository + * @param \Illuminate\Contracts\Encryption\Encrypter $encrypter */ public function __construct( - DatabaseManager $database, + ConnectionInterface $connection, DynamicDatabaseConnection $dynamic, - DatabaseRepositoryInterface $repository, + DatabaseRepository $repository, Encrypter $encrypter ) { - $this->database = $database; + $this->connection = $connection; $this->dynamic = $dynamic; $this->encrypter = $encrypter; $this->repository = $repository; } + /** + * Generates a unique database name for the given server. This name should be passed through when + * calling this handle function for this service, otherwise the database will be created with + * whatever name is provided. + * + * @param string $name + * @param int $serverId + * @return string + */ + public static function generateUniqueDatabaseName(string $name, int $serverId): string + { + // Max of 48 characters, including the s123_ that we append to the front. + return sprintf('s%d_%s', $serverId, substr($name, 0, 48 - strlen("s{$serverId}_"))); + } + + /** + * Set wether or not this class should validate that the server has enough slots + * left before creating the new database. + * + * @param bool $validate + * @return $this + */ + public function setValidateDatabaseLimit(bool $validate): self + { + $this->validateDatabaseLimit = $validate; + + return $this; + } + /** * Create a new database that is linked to a specific host. * - * @param int $server + * @param \Pterodactyl\Models\Server $server * @param array $data * @return \Pterodactyl\Models\Database * - * @throws \Exception + * @throws \Throwable + * @throws \Pterodactyl\Exceptions\Service\Database\TooManyDatabasesException + * @throws \Pterodactyl\Exceptions\Service\Database\DatabaseClientFeatureNotEnabledException */ - public function create($server, array $data) + public function create(Server $server, array $data) { - $data['server_id'] = $server; - $data['database'] = sprintf('s%d_%s', $server, $data['database']); - $data['username'] = sprintf('u%d_%s', $server, str_random(10)); - $data['password'] = $this->encrypter->encrypt( - Utilities::randomStringWithSpecialCharacters(24) - ); + if (! config('pterodactyl.client_features.databases.enabled')) { + throw new DatabaseClientFeatureNotEnabledException; + } + + if ($this->validateDatabaseLimit) { + // If the server has a limit assigned and we've already reached that limit, throw back + // an exception and kill the process. + if (! is_null($server->database_limit) && $server->databases()->count() >= $server->database_limit) { + throw new TooManyDatabasesException; + } + } + + // Protect against developer mistakes... + if (empty($data['database']) || ! preg_match(self::MATCH_NAME_REGEX, $data['database'])) { + throw new InvalidArgumentException( + 'The database name passed to DatabaseManagementService::handle MUST be prefixed with "s{server_id}_".' + ); + } + + $data = array_merge($data, [ + 'server_id' => $server->id, + 'username' => sprintf('u%d_%s', $server->id, str_random(10)), + 'password' => $this->encrypter->encrypt( + Utilities::randomStringWithSpecialCharacters(24) + ), + ]); + + $database = null; - $this->database->beginTransaction(); try { - $database = $this->repository->createIfNotExists($data); - $this->dynamic->set('dynamic', $data['database_host_id']); + return $this->connection->transaction(function () use ($data, &$database) { + $database = $this->createModel($data); - $this->repository->createDatabase($database->database); - $this->repository->createUser( - $database->username, - $database->remote, - $this->encrypter->decrypt($database->password) - ); - $this->repository->assignUserToDatabase( - $database->database, - $database->username, - $database->remote - ); - $this->repository->flush(); + $this->dynamic->set('dynamic', $data['database_host_id']); - $this->database->commit(); - } catch (\Exception $ex) { + $this->repository->createDatabase($database->database); + $this->repository->createUser( + $database->username, $database->remote, $this->encrypter->decrypt($database->password), $database->max_connections + ); + $this->repository->assignUserToDatabase($database->database, $database->username, $database->remote); + $this->repository->flush(); + + return $database; + }); + } catch (Exception $exception) { try { - if (isset($database) && $database instanceof Database) { + if ($database instanceof Database) { $this->repository->dropDatabase($database->database); $this->repository->dropUser($database->username, $database->remote); $this->repository->flush(); } - } catch (\Exception $exTwo) { - // ignore an exception + } catch (Exception $deletionException) { + // Do nothing here. We've already encountered an issue before this point so no + // reason to prioritize this error over the initial one. } - $this->database->rollBack(); - throw $ex; + throw $exception; } - - return $database; } /** * Delete a database from the given host server. * - * @param int $id + * @param \Pterodactyl\Models\Database $database * @return bool|null * - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Exception */ - public function delete($id) + public function delete(Database $database) { - $database = $this->repository->find($id); $this->dynamic->set('dynamic', $database->database_host_id); $this->repository->dropDatabase($database->database); $this->repository->dropUser($database->username, $database->remote); $this->repository->flush(); - return $this->repository->delete($id); + return $database->delete(); + } + + /** + * Create the database if there is not an identical match in the DB. While you can technically + * have the same name across multiple hosts, for the sake of keeping this logic easy to understand + * and avoiding user confusion we will ignore the specific host and just look across all hosts. + * + * @param array $data + * @return \Pterodactyl\Models\Database + * + * @throws \Pterodactyl\Exceptions\Repository\DuplicateDatabaseNameException + * @throws \Throwable + */ + protected function createModel(array $data): Database + { + $exists = Database::query()->where('server_id', $data['server_id']) + ->where('database', $data['database']) + ->exists(); + + if ($exists) { + throw new DuplicateDatabaseNameException( + 'A database with that name already exists for this server.' + ); + } + + $database = (new Database)->forceFill($data); + $database->saveOrFail(); + + return $database; } } diff --git a/app/Services/Databases/DatabasePasswordService.php b/app/Services/Databases/DatabasePasswordService.php index ad5882c4..2a9ebcab 100644 --- a/app/Services/Databases/DatabasePasswordService.php +++ b/app/Services/Databases/DatabasePasswordService.php @@ -34,10 +34,10 @@ class DatabasePasswordService /** * DatabasePasswordService constructor. * - * @param \Illuminate\Database\ConnectionInterface $connection + * @param \Illuminate\Database\ConnectionInterface $connection * @param \Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface $repository - * @param \Pterodactyl\Extensions\DynamicDatabaseConnection $dynamic - * @param \Illuminate\Contracts\Encryption\Encrypter $encrypter + * @param \Pterodactyl\Extensions\DynamicDatabaseConnection $dynamic + * @param \Illuminate\Contracts\Encryption\Encrypter $encrypter */ public function __construct( ConnectionInterface $connection, @@ -71,7 +71,7 @@ class DatabasePasswordService ]); $this->repository->dropUser($database->username, $database->remote); - $this->repository->createUser($database->username, $database->remote, $password); + $this->repository->createUser($database->username, $database->remote, $password, $database->max_connections); $this->repository->assignUserToDatabase($database->database, $database->username, $database->remote); $this->repository->flush(); }); diff --git a/app/Services/Databases/DeployServerDatabaseService.php b/app/Services/Databases/DeployServerDatabaseService.php index c8b5ed17..4bc72a1f 100644 --- a/app/Services/Databases/DeployServerDatabaseService.php +++ b/app/Services/Databases/DeployServerDatabaseService.php @@ -2,89 +2,60 @@ namespace Pterodactyl\Services\Databases; +use Webmozart\Assert\Assert; use Pterodactyl\Models\Server; use Pterodactyl\Models\Database; -use Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface; -use Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface; -use Pterodactyl\Exceptions\Service\Database\TooManyDatabasesException; +use Pterodactyl\Models\DatabaseHost; use Pterodactyl\Exceptions\Service\Database\NoSuitableDatabaseHostException; -use Pterodactyl\Exceptions\Service\Database\DatabaseClientFeatureNotEnabledException; class DeployServerDatabaseService { - /** - * @var \Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface - */ - private $databaseHostRepository; - /** * @var \Pterodactyl\Services\Databases\DatabaseManagementService */ private $managementService; - /** - * @var \Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface - */ - private $repository; - /** * ServerDatabaseCreationService constructor. * - * @param \Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface $repository - * @param \Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface $databaseHostRepository - * @param \Pterodactyl\Services\Databases\DatabaseManagementService $managementService + * @param \Pterodactyl\Services\Databases\DatabaseManagementService $managementService */ - public function __construct( - DatabaseRepositoryInterface $repository, - DatabaseHostRepositoryInterface $databaseHostRepository, - DatabaseManagementService $managementService - ) { - $this->databaseHostRepository = $databaseHostRepository; + public function __construct(DatabaseManagementService $managementService) + { $this->managementService = $managementService; - $this->repository = $repository; } /** * @param \Pterodactyl\Models\Server $server - * @param array $data + * @param array $data * @return \Pterodactyl\Models\Database * + * @throws \Throwable + * @throws \Pterodactyl\Exceptions\Service\Database\TooManyDatabasesException * @throws \Pterodactyl\Exceptions\Service\Database\DatabaseClientFeatureNotEnabledException - * @throws \Exception */ public function handle(Server $server, array $data): Database { - if (! config('pterodactyl.client_features.databases.enabled')) { - throw new DatabaseClientFeatureNotEnabledException; - } - - $databases = $this->repository->findCountWhere([['server_id', '=', $server->id]]); - if (! is_null($server->database_limit) && $databases >= $server->database_limit) { - throw new TooManyDatabasesException; - } - - $allowRandom = config('pterodactyl.client_features.databases.allow_random'); - $hosts = $this->databaseHostRepository->setColumns(['id'])->findWhere([ - ['node_id', '=', $server->node_id], - ]); - - if ($hosts->isEmpty() && ! $allowRandom) { - throw new NoSuitableDatabaseHostException; - } + Assert::notEmpty($data['database'] ?? null); + Assert::notEmpty($data['remote'] ?? null); + $hosts = DatabaseHost::query()->get()->toBase(); if ($hosts->isEmpty()) { - $hosts = $this->databaseHostRepository->setColumns(['id'])->all(); - if ($hosts->isEmpty()) { + throw new NoSuitableDatabaseHostException; + } else { + $nodeHosts = $hosts->where('node_id', $server->node_id)->toBase(); + + if ($nodeHosts->isEmpty() && ! config('pterodactyl.client_features.databases.allow_random')) { throw new NoSuitableDatabaseHostException; } } - $host = $hosts->random(); - - return $this->managementService->create($server->id, [ - 'database_host_id' => $host->id, - 'database' => array_get($data, 'database'), - 'remote' => array_get($data, 'remote'), + return $this->managementService->create($server, [ + 'database_host_id' => $nodeHosts->isEmpty() + ? $hosts->random()->id + : $nodeHosts->random()->id, + 'database' => DatabaseManagementService::generateUniqueDatabaseName($data['database'], $server->id), + 'remote' => $data['remote'], ]); } } diff --git a/app/Services/Databases/Hosts/HostCreationService.php b/app/Services/Databases/Hosts/HostCreationService.php index d6ea2f48..cbe3024f 100644 --- a/app/Services/Databases/Hosts/HostCreationService.php +++ b/app/Services/Databases/Hosts/HostCreationService.php @@ -39,11 +39,11 @@ class HostCreationService /** * HostCreationService constructor. * - * @param \Illuminate\Database\ConnectionInterface $connection - * @param \Illuminate\Database\DatabaseManager $databaseManager + * @param \Illuminate\Database\ConnectionInterface $connection + * @param \Illuminate\Database\DatabaseManager $databaseManager * @param \Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface $repository - * @param \Pterodactyl\Extensions\DynamicDatabaseConnection $dynamic - * @param \Illuminate\Contracts\Encryption\Encrypter $encrypter + * @param \Pterodactyl\Extensions\DynamicDatabaseConnection $dynamic + * @param \Illuminate\Contracts\Encryption\Encrypter $encrypter */ public function __construct( ConnectionInterface $connection, diff --git a/app/Services/Databases/Hosts/HostDeletionService.php b/app/Services/Databases/Hosts/HostDeletionService.php index b69c8dcf..f366305c 100644 --- a/app/Services/Databases/Hosts/HostDeletionService.php +++ b/app/Services/Databases/Hosts/HostDeletionService.php @@ -21,7 +21,7 @@ class HostDeletionService /** * HostDeletionService constructor. * - * @param \Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface $databaseRepository + * @param \Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface $databaseRepository * @param \Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface $repository */ public function __construct( diff --git a/app/Services/Databases/Hosts/HostUpdateService.php b/app/Services/Databases/Hosts/HostUpdateService.php index cb6312d2..9965abd9 100644 --- a/app/Services/Databases/Hosts/HostUpdateService.php +++ b/app/Services/Databases/Hosts/HostUpdateService.php @@ -46,11 +46,11 @@ class HostUpdateService /** * DatabaseHostService constructor. * - * @param \Illuminate\Database\ConnectionInterface $connection - * @param \Illuminate\Database\DatabaseManager $databaseManager + * @param \Illuminate\Database\ConnectionInterface $connection + * @param \Illuminate\Database\DatabaseManager $databaseManager * @param \Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface $repository - * @param \Pterodactyl\Extensions\DynamicDatabaseConnection $dynamic - * @param \Illuminate\Contracts\Encryption\Encrypter $encrypter + * @param \Pterodactyl\Extensions\DynamicDatabaseConnection $dynamic + * @param \Illuminate\Contracts\Encryption\Encrypter $encrypter */ public function __construct( ConnectionInterface $connection, @@ -69,7 +69,7 @@ class HostUpdateService /** * Update a database host and persist to the database. * - * @param int $hostId + * @param int $hostId * @param array $data * @return \Pterodactyl\Models\DatabaseHost * diff --git a/app/Services/Deployment/FindViableNodesService.php b/app/Services/Deployment/FindViableNodesService.php index 6d6832c2..d89c73f5 100644 --- a/app/Services/Deployment/FindViableNodesService.php +++ b/app/Services/Deployment/FindViableNodesService.php @@ -3,16 +3,12 @@ namespace Pterodactyl\Services\Deployment; use Webmozart\Assert\Assert; -use Pterodactyl\Contracts\Repository\NodeRepositoryInterface; +use Pterodactyl\Models\Node; +use Illuminate\Support\LazyCollection; use Pterodactyl\Exceptions\Service\Deployment\NoViableNodeException; class FindViableNodesService { - /** - * @var \Pterodactyl\Contracts\Repository\NodeRepositoryInterface - */ - private $repository; - /** * @var array */ @@ -28,16 +24,6 @@ class FindViableNodesService */ protected $memory; - /** - * FindViableNodesService constructor. - * - * @param \Pterodactyl\Contracts\Repository\NodeRepositoryInterface $repository - */ - public function __construct(NodeRepositoryInterface $repository) - { - $this->repository = $repository; - } - /** * Set the locations that should be searched through to locate available nodes. * @@ -46,6 +32,8 @@ class FindViableNodesService */ public function setLocations(array $locations): self { + Assert::allInteger($locations, 'An array of location IDs should be provided when calling setLocations.'); + $this->locations = $locations; return $this; @@ -90,32 +78,34 @@ class FindViableNodesService * are tossed out, as are any nodes marked as non-public, meaning automatic * deployments should not be done against them. * - * @return int[] + * @return \Pterodactyl\Models\Node[]|\Illuminate\Support\Collection * @throws \Pterodactyl\Exceptions\Service\Deployment\NoViableNodeException */ - public function handle(): array + public function handle() { - Assert::integer($this->disk, 'Calls to ' . __METHOD__ . ' must have the disk space set as an integer, received %s'); - Assert::integer($this->memory, 'Calls to ' . __METHOD__ . ' must have the memory usage set as an integer, received %s'); + Assert::integer($this->disk, 'Disk space must be an int, got %s'); + Assert::integer($this->memory, 'Memory usage must be an int, got %s'); - $nodes = $this->repository->getNodesWithResourceUse($this->locations, $this->disk, $this->memory); - $viable = []; + $query = Node::query()->select('nodes.*') + ->selectRaw('IFNULL(SUM(servers.memory), 0) as sum_memory') + ->selectRaw('IFNULL(SUM(servers.disk), 0) as sum_disk') + ->leftJoin('servers', 'servers.node_id', '=', 'nodes.id') + ->where('nodes.public', 1); - foreach ($nodes as $node) { - $memoryLimit = $node->memory * (1 + ($node->memory_overallocate / 100)); - $diskLimit = $node->disk * (1 + ($node->disk_overallocate / 100)); - - if (($node->sum_memory + $this->memory) > $memoryLimit || ($node->sum_disk + $this->disk) > $diskLimit) { - continue; - } - - $viable[] = $node->id; + if (! empty($this->locations)) { + $query = $query->whereIn('nodes.location_id', $this->locations); } - if (empty($viable)) { + $results = $query->groupBy('nodes.id') + ->havingRaw('(IFNULL(SUM(servers.memory), 0) + ?) <= (nodes.memory * (1 + (nodes.memory_overallocate / 100)))', [ $this->memory ]) + ->havingRaw('(IFNULL(SUM(servers.disk), 0) + ?) <= (nodes.disk * (1 + (nodes.disk_overallocate / 100)))', [ $this->disk ]) + ->get() + ->toBase(); + + if ($results->isEmpty()) { throw new NoViableNodeException(trans('exceptions.deployment.no_viable_nodes')); } - return $viable; + return $results; } } diff --git a/app/Services/Eggs/EggConfigurationService.php b/app/Services/Eggs/EggConfigurationService.php index a73e3f6a..6f4eae68 100644 --- a/app/Services/Eggs/EggConfigurationService.php +++ b/app/Services/Eggs/EggConfigurationService.php @@ -1,54 +1,266 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Services\Eggs; -use Pterodactyl\Models\Egg; +use Illuminate\Support\Arr; +use Illuminate\Support\Str; +use Pterodactyl\Models\Server; use Pterodactyl\Contracts\Repository\EggRepositoryInterface; +use Pterodactyl\Services\Servers\ServerConfigurationStructureService; class EggConfigurationService { + private const NOT_MATCHED = '__no_match'; + /** * @var \Pterodactyl\Contracts\Repository\EggRepositoryInterface */ - protected $repository; + private $repository; + + /** + * @var \Pterodactyl\Services\Servers\ServerConfigurationStructureService + */ + private $configurationStructureService; /** * EggConfigurationService constructor. * * @param \Pterodactyl\Contracts\Repository\EggRepositoryInterface $repository + * @param \Pterodactyl\Services\Servers\ServerConfigurationStructureService $configurationStructureService */ - public function __construct(EggRepositoryInterface $repository) - { + public function __construct( + EggRepositoryInterface $repository, + ServerConfigurationStructureService $configurationStructureService + ) { $this->repository = $repository; + $this->configurationStructureService = $configurationStructureService; } /** * Return an Egg file to be used by the Daemon. * - * @param int|\Pterodactyl\Models\Egg $egg + * @param \Pterodactyl\Models\Server $server * @return array * * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function handle($egg): array + public function handle(Server $server): array { - if (! $egg instanceof Egg) { - $egg = $this->repository->getWithCopyAttributes($egg); + $configs = $this->replacePlaceholders( + $server, json_decode($server->egg->inherit_config_files) + ); + + return [ + 'startup' => $this->convertStartupToNewFormat(json_decode($server->egg->inherit_config_startup, true)), + 'stop' => $this->convertStopToNewFormat($server->egg->inherit_config_stop), + 'configs' => $configs, + ]; + } + + /** + * Convert the "done" variable into an array if it is not currently one. + * + * @param array $startup + * @return array + */ + protected function convertStartupToNewFormat(array $startup) + { + $done = Arr::get($startup, 'done'); + + return [ + 'done' => is_string($done) ? [$done] : $done, + 'user_interaction' => Arr::get($startup, 'userInteraction') ?? Arr::get($startup, 'user_interaction') ?? [], + 'strip_ansi' => Arr::get($startup, 'strip_ansi') ?? false, + ]; + } + + /** + * Converts a legacy stop string into a new generation stop option for a server. + * + * For most eggs, this ends up just being a command sent to the server console, but + * if the stop command is something starting with a caret (^), it will be converted + * into the associated kill signal for the instance. + * + * @param string $stop + * @return array + */ + protected function convertStopToNewFormat(string $stop): array + { + if (! Str::startsWith($stop, '^')) { + return [ + 'type' => 'command', + 'value' => $stop, + ]; + } + + $signal = substr($stop, 1); + if (strtoupper($signal) === 'C') { + return [ + 'type' => 'stop', + 'value' => null, + ]; } return [ - 'startup' => json_decode($egg->inherit_config_startup), - 'stop' => $egg->inherit_config_stop, - 'configs' => json_decode($egg->inherit_config_files), - 'log' => json_decode($egg->inherit_config_logs), - 'query' => 'none', + 'type' => 'signal', + 'value' => strtoupper($signal), ]; } + + /** + * @param \Pterodactyl\Models\Server $server + * @param object $configs + * @return array + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + protected function replacePlaceholders(Server $server, object $configs) + { + // Get the legacy configuration structure for the server so that we + // can property map the egg placeholders to values. + $structure = $this->configurationStructureService->handle($server, true); + + foreach ($configs as $file => &$data) { + $this->iterate($data->find, $structure); + } + + $response = []; + // Normalize the output of the configuration for the new Wings Daemon to more + // easily ingest, as well as make things more flexible down the road. + foreach ($configs as $file => $data) { + $append = ['file' => $file, 'replace' => []]; + + // I like to think I understand PHP pretty well, but if you don't pass $value + // by reference here, you'll end up with a resursive array loop if the config + // file has two replacements that reference the same item in the configuration + // array for the server. + foreach ($data as $key => &$value) { + if ($key !== 'find') { + $append[$key] = $value; + continue; + } + + foreach ($value as $find => $replace) { + if (is_object($replace)) { + foreach ($replace as $match => $replaceWith) { + $append['replace'][] = [ + 'match' => $find, + 'if_value' => $match, + 'replace_with' => $replaceWith, + ]; + } + + continue; + } + + $append['replace'][] = [ + 'match' => $find, + 'replace_with' => $replace, + ]; + } + } + + $response[] = $append; + } + + return $response; + } + + /** + * @param string $value + * @param array $structure + * @return string|null + */ + protected function matchAndReplaceKeys(string $value, array $structure): ?string + { + preg_match('/{{(?.*)}}/', $value, $matches); + + if (! $key = $matches['key'] ?? null) { + return self::NOT_MATCHED; + } + + // Matched something in {{server.X}} format, now replace that with the actual + // value from the server properties. + // + // The Daemon supports server.X, env.X, and config.X placeholders. + if (! Str::startsWith($key, ['server.', 'env.', 'config.'])) { + return self::NOT_MATCHED; + } + + // The legacy Daemon would set SERVER_MEMORY, SERVER_IP, and SERVER_PORT with their + // respective values on the Daemon side. Ensure that anything referencing those properly + // replaces them with the matching config value. + switch ($key) { + case 'config.docker.interface': + $key = 'config.docker.network.interface'; + break; + case 'server.build.env.SERVER_MEMORY': + case 'env.SERVER_MEMORY': + $key = 'server.build.memory'; + break; + case 'server.build.env.SERVER_IP': + case 'env.SERVER_IP': + $key = 'server.build.default.ip'; + break; + case 'server.build.env.SERVER_PORT': + case 'env.SERVER_PORT': + $key = 'server.build.default.port'; + break; + } + + // We don't want to do anything with config keys since the Daemon will need to handle + // that. For example, the Spigot egg uses "config.docker.interface" to identify the Docker + // interface to proxy through, but the Panel would be unaware of that. + if (Str::startsWith($key, 'config.')) { + return preg_replace('/{{(.*)}}/', "{{{$key}}}", $value); + } + + // Replace anything starting with "server." with the value out of the server configuration + // array that used to be created for the old daemon. + if (Str::startsWith($key, 'server.')) { + $plucked = Arr::get( + $structure, preg_replace('/^server\./', '', $key), '' + ); + + return preg_replace('/{{(.*)}}/', $plucked, $value); + } + + // Finally, replace anything starting with env. with the expected environment + // variable from the server configuration. + $plucked = Arr::get( + $structure, preg_replace('/^env\./', 'build.env.', $key), '' + ); + + return preg_replace('/{{(.*)}}/', $plucked, $value); + } + + /** + * Iterates over a set of "find" values for a given file in the parser configuration. If + * the value of the line match is something iterable, continue iterating, otherwise perform + * a match & replace. + * + * @param mixed $data + * @param array $structure + */ + private function iterate(&$data, array $structure) + { + if (! is_iterable($data) && ! is_object($data)) { + return; + } + + foreach ($data as &$value) { + if (is_iterable($value) || is_object($value)) { + $this->iterate($value, $structure); + + continue; + } + + $response = $this->matchAndReplaceKeys($value, $structure); + if ($response === self::NOT_MATCHED) { + continue; + } + + $value = $response; + } + } } diff --git a/app/Services/Eggs/EggCreationService.php b/app/Services/Eggs/EggCreationService.php index 45fa8895..33a0a919 100644 --- a/app/Services/Eggs/EggCreationService.php +++ b/app/Services/Eggs/EggCreationService.php @@ -31,7 +31,7 @@ class EggCreationService /** * EggCreationService constructor. * - * @param \Illuminate\Contracts\Config\Repository $config + * @param \Illuminate\Contracts\Config\Repository $config * @param \Pterodactyl\Contracts\Repository\EggRepositoryInterface $repository */ public function __construct(ConfigRepository $config, EggRepositoryInterface $repository) diff --git a/app/Services/Eggs/EggDeletionService.php b/app/Services/Eggs/EggDeletionService.php index 5179f6a5..7b30e442 100644 --- a/app/Services/Eggs/EggDeletionService.php +++ b/app/Services/Eggs/EggDeletionService.php @@ -30,7 +30,7 @@ class EggDeletionService * EggDeletionService constructor. * * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $serverRepository - * @param \Pterodactyl\Contracts\Repository\EggRepositoryInterface $repository + * @param \Pterodactyl\Contracts\Repository\EggRepositoryInterface $repository */ public function __construct( ServerRepositoryInterface $serverRepository, diff --git a/app/Services/Eggs/EggUpdateService.php b/app/Services/Eggs/EggUpdateService.php index 14d65517..aa4ec39b 100644 --- a/app/Services/Eggs/EggUpdateService.php +++ b/app/Services/Eggs/EggUpdateService.php @@ -34,7 +34,7 @@ class EggUpdateService * Update a service option. * * @param int|\Pterodactyl\Models\Egg $egg - * @param array $data + * @param array $data * * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException diff --git a/app/Services/Eggs/Scripts/InstallScriptService.php b/app/Services/Eggs/Scripts/InstallScriptService.php index b71c10d8..70621c32 100644 --- a/app/Services/Eggs/Scripts/InstallScriptService.php +++ b/app/Services/Eggs/Scripts/InstallScriptService.php @@ -1,11 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Services\Eggs\Scripts; @@ -34,18 +27,14 @@ class InstallScriptService * Modify the install script for a given Egg. * * @param int|\Pterodactyl\Models\Egg $egg - * @param array $data + * @param array $data * * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException * @throws \Pterodactyl\Exceptions\Service\Egg\InvalidCopyFromException */ - public function handle($egg, array $data) + public function handle(Egg $egg, array $data) { - if (! $egg instanceof Egg) { - $egg = $this->repository->find($egg); - } - if (! is_null(array_get($data, 'copy_script_from'))) { if (! $this->repository->isCopyableScript(array_get($data, 'copy_script_from'), $egg->nest_id)) { throw new InvalidCopyFromException(trans('exceptions.nest.egg.invalid_copy_id')); diff --git a/app/Services/Eggs/Sharing/EggExporterService.php b/app/Services/Eggs/Sharing/EggExporterService.php index 25a7131f..1602fc91 100644 --- a/app/Services/Eggs/Sharing/EggExporterService.php +++ b/app/Services/Eggs/Sharing/EggExporterService.php @@ -1,11 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Services\Eggs\Sharing; diff --git a/app/Services/Eggs/Sharing/EggImporterService.php b/app/Services/Eggs/Sharing/EggImporterService.php index d4e048f9..96c4c72e 100644 --- a/app/Services/Eggs/Sharing/EggImporterService.php +++ b/app/Services/Eggs/Sharing/EggImporterService.php @@ -1,11 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Services\Eggs\Sharing; @@ -44,10 +37,10 @@ class EggImporterService /** * EggImporterService constructor. * - * @param \Illuminate\Database\ConnectionInterface $connection - * @param \Pterodactyl\Contracts\Repository\EggRepositoryInterface $repository + * @param \Illuminate\Database\ConnectionInterface $connection + * @param \Pterodactyl\Contracts\Repository\EggRepositoryInterface $repository * @param \Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface $eggVariableRepository - * @param \Pterodactyl\Contracts\Repository\NestRepositoryInterface $nestRepository + * @param \Pterodactyl\Contracts\Repository\NestRepositoryInterface $nestRepository */ public function __construct( ConnectionInterface $connection, @@ -65,7 +58,7 @@ class EggImporterService * Take an uploaded JSON file and parse it into a new egg. * * @param \Illuminate\Http\UploadedFile $file - * @param int $nest + * @param int $nest * @return \Pterodactyl\Models\Egg * * @throws \Pterodactyl\Exceptions\Model\DataValidationException @@ -76,7 +69,16 @@ class EggImporterService public function handle(UploadedFile $file, int $nest): Egg { if ($file->getError() !== UPLOAD_ERR_OK || ! $file->isFile()) { - throw new InvalidFileUploadException(trans('exceptions.nest.importer.file_error')); + throw new InvalidFileUploadException( + sprintf( + 'The selected file ["%s"] was not in a valid format to import. (is_file: %s is_valid: %s err_code: %s err: %s)', + $file->getFilename(), + $file->isFile() ? 'true' : 'false', + $file->isValid() ? 'true' : 'false', + $file->getError(), + $file->getErrorMessage() + ) + ); } $parsed = json_decode($file->openFile()->fread($file->getSize())); diff --git a/app/Services/Eggs/Sharing/EggUpdateImporterService.php b/app/Services/Eggs/Sharing/EggUpdateImporterService.php index 50342667..91b8d2b2 100644 --- a/app/Services/Eggs/Sharing/EggUpdateImporterService.php +++ b/app/Services/Eggs/Sharing/EggUpdateImporterService.php @@ -2,6 +2,7 @@ namespace Pterodactyl\Services\Eggs\Sharing; +use Pterodactyl\Models\Egg; use Illuminate\Http\UploadedFile; use Illuminate\Database\ConnectionInterface; use Pterodactyl\Contracts\Repository\EggRepositoryInterface; @@ -29,8 +30,8 @@ class EggUpdateImporterService /** * EggUpdateImporterService constructor. * - * @param \Illuminate\Database\ConnectionInterface $connection - * @param \Pterodactyl\Contracts\Repository\EggRepositoryInterface $repository + * @param \Illuminate\Database\ConnectionInterface $connection + * @param \Pterodactyl\Contracts\Repository\EggRepositoryInterface $repository * @param \Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface $variableRepository */ public function __construct( @@ -46,7 +47,7 @@ class EggUpdateImporterService /** * Update an existing Egg using an uploaded JSON file. * - * @param int $egg + * @param \Pterodactyl\Models\Egg $egg * @param \Illuminate\Http\UploadedFile $file * * @throws \Pterodactyl\Exceptions\Model\DataValidationException @@ -54,10 +55,19 @@ class EggUpdateImporterService * @throws \Pterodactyl\Exceptions\Service\Egg\BadJsonFormatException * @throws \Pterodactyl\Exceptions\Service\InvalidFileUploadException */ - public function handle(int $egg, UploadedFile $file) + public function handle(Egg $egg, UploadedFile $file) { if ($file->getError() !== UPLOAD_ERR_OK || ! $file->isFile()) { - throw new InvalidFileUploadException(trans('exceptions.nest.importer.file_error')); + throw new InvalidFileUploadException( + sprintf( + 'The selected file ["%s"] was not in a valid format to import. (is_file: %s is_valid: %s err_code: %s err: %s)', + $file->getFilename(), + $file->isFile() ? 'true' : 'false', + $file->isValid() ? 'true' : 'false', + $file->getError(), + $file->getErrorMessage() + ) + ); } $parsed = json_decode($file->openFile()->fread($file->getSize())); @@ -72,7 +82,7 @@ class EggUpdateImporterService } $this->connection->beginTransaction(); - $this->repository->update($egg, [ + $this->repository->update($egg->id, [ 'author' => object_get($parsed, 'author'), 'name' => object_get($parsed, 'name'), 'description' => object_get($parsed, 'description'), @@ -90,19 +100,19 @@ class EggUpdateImporterService // Update Existing Variables collect($parsed->variables)->each(function ($variable) use ($egg) { $this->variableRepository->withoutFreshModel()->updateOrCreate([ - 'egg_id' => $egg, + 'egg_id' => $egg->id, 'env_variable' => $variable->env_variable, ], collect($variable)->except(['egg_id', 'env_variable'])->toArray()); }); $imported = collect($parsed->variables)->pluck('env_variable')->toArray(); - $existing = $this->variableRepository->setColumns(['id', 'env_variable'])->findWhere([['egg_id', '=', $egg]]); + $existing = $this->variableRepository->setColumns(['id', 'env_variable'])->findWhere([['egg_id', '=', $egg->id]]); // Delete variables not present in the import. collect($existing)->each(function ($variable) use ($egg, $imported) { if (! in_array($variable->env_variable, $imported)) { $this->variableRepository->deleteWhere([ - ['egg_id', '=', $egg], + ['egg_id', '=', $egg->id], ['env_variable', '=', $variable->env_variable], ]); } diff --git a/app/Services/Eggs/Variables/VariableCreationService.php b/app/Services/Eggs/Variables/VariableCreationService.php index 8a989fba..ed7e72a4 100644 --- a/app/Services/Eggs/Variables/VariableCreationService.php +++ b/app/Services/Eggs/Variables/VariableCreationService.php @@ -26,7 +26,7 @@ class VariableCreationService * VariableCreationService constructor. * * @param \Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface $repository - * @param \Illuminate\Contracts\Validation\Factory $validator + * @param \Illuminate\Contracts\Validation\Factory $validator */ public function __construct(EggVariableRepositoryInterface $repository, Factory $validator) { @@ -48,7 +48,7 @@ class VariableCreationService /** * Create a new variable for a given Egg. * - * @param int $egg + * @param int $egg * @param array $data * @return \Pterodactyl\Models\EggVariable * diff --git a/app/Services/Eggs/Variables/VariableUpdateService.php b/app/Services/Eggs/Variables/VariableUpdateService.php index 15bbe2d3..da1a9e98 100644 --- a/app/Services/Eggs/Variables/VariableUpdateService.php +++ b/app/Services/Eggs/Variables/VariableUpdateService.php @@ -2,6 +2,7 @@ namespace Pterodactyl\Services\Eggs\Variables; +use Illuminate\Support\Str; use Pterodactyl\Models\EggVariable; use Illuminate\Contracts\Validation\Factory; use Pterodactyl\Exceptions\DisplayException; @@ -27,7 +28,7 @@ class VariableUpdateService * VariableUpdateService constructor. * * @param \Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface $repository - * @param \Illuminate\Contracts\Validation\Factory $validator + * @param \Illuminate\Contracts\Validation\Factory $validator */ public function __construct(EggVariableRepositoryInterface $repository, Factory $validator) { @@ -50,7 +51,7 @@ class VariableUpdateService * Update a specific egg variable. * * @param \Pterodactyl\Models\EggVariable $variable - * @param array $data + * @param array $data * @return mixed * * @throws \Pterodactyl\Exceptions\DisplayException @@ -81,7 +82,11 @@ class VariableUpdateService } if (! empty($data['rules'] ?? '')) { - $this->validateRules($data['rules']); + $this->validateRules( + (is_string($data['rules']) && Str::contains($data['rules'], ';;')) + ? explode(';;', $data['rules']) + : $data['rules'] + ); } $options = array_get($data, 'options') ?? []; diff --git a/app/Services/Helpers/AssetHashService.php b/app/Services/Helpers/AssetHashService.php new file mode 100644 index 00000000..25aa3318 --- /dev/null +++ b/app/Services/Helpers/AssetHashService.php @@ -0,0 +1,145 @@ +application = $application; + $this->filesystem = $filesystem->createLocalDriver(['root' => public_path()]); + } + + /** + * Modify a URL to append the asset hash. + * + * @param string $resource + * @return string + * + * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException + */ + public function url(string $resource): string + { + $file = last(explode('/', $resource)); + $data = Arr::get($this->manifest(), $file) ?? $file; + + return str_replace($file, Arr::get($data, 'src') ?? $file, $resource); + } + + /** + * Return the data integrity hash for a resource. + * + * @param string $resource + * @return string + * + * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException + */ + public function integrity(string $resource): string + { + $file = last(explode('/', $resource)); + $data = array_get($this->manifest(), $file, $file); + + return Arr::get($data, 'integrity') ?? ''; + } + + /** + * Return a built CSS import using the provided URL. + * + * @param string $resource + * @return string + * + * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException + */ + public function css(string $resource): string + { + $attributes = [ + 'href' => $this->url($resource), + 'rel' => 'stylesheet preload', + 'as' => 'style', + 'crossorigin' => 'anonymous', + 'referrerpolicy' => 'no-referrer', + ]; + + if (config('pterodactyl.assets.use_hash')) { + $attributes['integrity'] = $this->integrity($resource); + } + + $output = ' $value) { + $output .= " $key=\"$value\""; + } + + return $output . '>'; + } + + /** + * Return a built JS import using the provided URL. + * + * @param string $resource + * @return string + * + * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException + */ + public function js(string $resource): string + { + $attributes = [ + 'src' => $this->url($resource), + 'crossorigin' => 'anonymous', + ]; + + if (config('pterodactyl.assets.use_hash')) { + $attributes['integrity'] = $this->integrity($resource); + } + + $output = ' $value) { + $output .= " $key=\"$value\""; + } + + return $output . '>'; + } + + /** + * Get the asset manifest and store it in the cache for quicker lookups. + * + * @return array + * + * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException + */ + protected function manifest(): array + { + return self::$manifest ?: self::$manifest = json_decode( + $this->filesystem->get(self::MANIFEST_PATH), true + ); + } +} diff --git a/app/Services/Helpers/SoftwareVersionService.php b/app/Services/Helpers/SoftwareVersionService.php index 0d91bb9a..893c097d 100644 --- a/app/Services/Helpers/SoftwareVersionService.php +++ b/app/Services/Helpers/SoftwareVersionService.php @@ -1,24 +1,22 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Services\Helpers; -use stdClass; use Exception; use GuzzleHttp\Client; +use Carbon\CarbonImmutable; +use Illuminate\Support\Arr; use Illuminate\Contracts\Cache\Repository as CacheRepository; -use Illuminate\Contracts\Config\Repository as ConfigRepository; use Pterodactyl\Exceptions\Service\Helper\CdnVersionFetchingException; class SoftwareVersionService { - const VERSION_CACHE_KEY = 'pterodactyl:versions'; + const VERSION_CACHE_KEY = 'pterodactyl:versioning_data'; + + /** + * @var array + */ + private static $result; /** * @var \Illuminate\Contracts\Cache\Repository @@ -30,28 +28,20 @@ class SoftwareVersionService */ protected $client; - /** - * @var \Illuminate\Contracts\Config\Repository - */ - protected $config; - /** * SoftwareVersionService constructor. * - * @param \Illuminate\Contracts\Cache\Repository $cache - * @param \GuzzleHttp\Client $client - * @param \Illuminate\Contracts\Config\Repository $config + * @param \Illuminate\Contracts\Cache\Repository $cache + * @param \GuzzleHttp\Client $client */ public function __construct( CacheRepository $cache, - Client $client, - ConfigRepository $config + Client $client ) { $this->cache = $cache; $this->client = $client; - $this->config = $config; - $this->cacheVersionData(); + self::$result = $this->cacheVersionData(); } /** @@ -61,7 +51,7 @@ class SoftwareVersionService */ public function getPanel() { - return object_get($this->cache->get(self::VERSION_CACHE_KEY), 'panel', 'error'); + return Arr::get(self::$result, 'panel') ?? 'error'; } /** @@ -71,7 +61,7 @@ class SoftwareVersionService */ public function getDaemon() { - return object_get($this->cache->get(self::VERSION_CACHE_KEY), 'daemon', 'error'); + return Arr::get(self::$result, 'wings') ?? 'error'; } /** @@ -81,7 +71,17 @@ class SoftwareVersionService */ public function getDiscord() { - return object_get($this->cache->get(self::VERSION_CACHE_KEY), 'discord', 'https://pterodactyl.io/discord'); + return Arr::get(self::$result, 'discord') ?? 'https://pterodactyl.io/discord'; + } + + /** + * Get the URL for donations. + * + * @return string + */ + public function getDonations() + { + return Arr::get(self::$result, 'donations') ?? 'https://paypal.me/PterodactylSoftware'; } /** @@ -91,11 +91,11 @@ class SoftwareVersionService */ public function isLatestPanel() { - if ($this->config->get('app.version') === 'canary') { + if (config()->get('app.version') === 'canary') { return true; } - return version_compare($this->config->get('app.version'), $this->getPanel()) >= 0; + return version_compare(config()->get('app.version'), $this->getPanel()) >= 0; } /** @@ -115,20 +115,22 @@ class SoftwareVersionService /** * Keeps the versioning cache up-to-date with the latest results from the CDN. + * + * @return array */ protected function cacheVersionData() { - $this->cache->remember(self::VERSION_CACHE_KEY, $this->config->get('pterodactyl.cdn.cache_time'), function () { + return $this->cache->remember(self::VERSION_CACHE_KEY, CarbonImmutable::now()->addMinutes(config()->get('pterodactyl.cdn.cache_time', 60)), function () { try { - $response = $this->client->request('GET', $this->config->get('pterodactyl.cdn.url')); + $response = $this->client->request('GET', config()->get('pterodactyl.cdn.url')); if ($response->getStatusCode() === 200) { - return json_decode($response->getBody()); + return json_decode($response->getBody(), true); } throw new CdnVersionFetchingException; } catch (Exception $exception) { - return new stdClass(); + return []; } }); } diff --git a/app/Services/Locations/LocationDeletionService.php b/app/Services/Locations/LocationDeletionService.php index 5d1495d1..5fc528ea 100644 --- a/app/Services/Locations/LocationDeletionService.php +++ b/app/Services/Locations/LocationDeletionService.php @@ -31,7 +31,7 @@ class LocationDeletionService * LocationDeletionService constructor. * * @param \Pterodactyl\Contracts\Repository\LocationRepositoryInterface $repository - * @param \Pterodactyl\Contracts\Repository\NodeRepositoryInterface $nodeRepository + * @param \Pterodactyl\Contracts\Repository\NodeRepositoryInterface $nodeRepository */ public function __construct( LocationRepositoryInterface $repository, diff --git a/app/Services/Locations/LocationUpdateService.php b/app/Services/Locations/LocationUpdateService.php index a245e71b..dfb5bcc5 100644 --- a/app/Services/Locations/LocationUpdateService.php +++ b/app/Services/Locations/LocationUpdateService.php @@ -33,7 +33,7 @@ class LocationUpdateService * Update an existing location. * * @param int|\Pterodactyl\Models\Location $location - * @param array $data + * @param array $data * @return \Pterodactyl\Models\Location * * @throws \Pterodactyl\Exceptions\Model\DataValidationException diff --git a/app/Services/Nests/NestCreationService.php b/app/Services/Nests/NestCreationService.php index 43916448..5cac1c11 100644 --- a/app/Services/Nests/NestCreationService.php +++ b/app/Services/Nests/NestCreationService.php @@ -22,7 +22,7 @@ class NestCreationService /** * NestCreationService constructor. * - * @param \Illuminate\Contracts\Config\Repository $config + * @param \Illuminate\Contracts\Config\Repository $config * @param \Pterodactyl\Contracts\Repository\NestRepositoryInterface $repository */ public function __construct(ConfigRepository $config, NestRepositoryInterface $repository) @@ -34,7 +34,7 @@ class NestCreationService /** * Create a new nest on the system. * - * @param array $data + * @param array $data * @param string|null $author * @return \Pterodactyl\Models\Nest * @throws \Pterodactyl\Exceptions\Model\DataValidationException diff --git a/app/Services/Nests/NestDeletionService.php b/app/Services/Nests/NestDeletionService.php index 0f01a5e5..990bbc59 100644 --- a/app/Services/Nests/NestDeletionService.php +++ b/app/Services/Nests/NestDeletionService.php @@ -29,7 +29,7 @@ class NestDeletionService * NestDeletionService constructor. * * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $serverRepository - * @param \Pterodactyl\Contracts\Repository\NestRepositoryInterface $repository + * @param \Pterodactyl\Contracts\Repository\NestRepositoryInterface $repository */ public function __construct( ServerRepositoryInterface $serverRepository, diff --git a/app/Services/Nests/NestUpdateService.php b/app/Services/Nests/NestUpdateService.php index c3cdcdd5..de04e7e2 100644 --- a/app/Services/Nests/NestUpdateService.php +++ b/app/Services/Nests/NestUpdateService.php @@ -31,7 +31,7 @@ class NestUpdateService /** * Update a nest and prevent changing the author once it is set. * - * @param int $nest + * @param int $nest * @param array $data * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException diff --git a/app/Services/Nodes/NodeCreationService.php b/app/Services/Nodes/NodeCreationService.php index 889e81a2..a44c036b 100644 --- a/app/Services/Nodes/NodeCreationService.php +++ b/app/Services/Nodes/NodeCreationService.php @@ -1,33 +1,35 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Services\Nodes; +use Ramsey\Uuid\Uuid; +use Illuminate\Support\Str; +use Pterodactyl\Models\Node; +use Illuminate\Contracts\Encryption\Encrypter; use Pterodactyl\Contracts\Repository\NodeRepositoryInterface; class NodeCreationService { - const DAEMON_SECRET_LENGTH = 36; - /** * @var \Pterodactyl\Contracts\Repository\NodeRepositoryInterface */ protected $repository; + /** + * @var \Illuminate\Contracts\Encryption\Encrypter + */ + private $encrypter; + /** * CreationService constructor. * + * @param \Illuminate\Contracts\Encryption\Encrypter $encrypter * @param \Pterodactyl\Contracts\Repository\NodeRepositoryInterface $repository */ - public function __construct(NodeRepositoryInterface $repository) + public function __construct(Encrypter $encrypter, NodeRepositoryInterface $repository) { $this->repository = $repository; + $this->encrypter = $encrypter; } /** @@ -40,8 +42,10 @@ class NodeCreationService */ public function handle(array $data) { - $data['daemonSecret'] = str_random(self::DAEMON_SECRET_LENGTH); + $data['uuid'] = Uuid::uuid4()->toString(); + $data['daemon_token'] = $this->encrypter->encrypt(Str::random(Node::DAEMON_TOKEN_LENGTH)); + $data['daemon_token_id'] = Str::random(Node::DAEMON_TOKEN_ID_LENGTH); - return $this->repository->create($data); + return $this->repository->create($data, true, true); } } diff --git a/app/Services/Nodes/NodeDeletionService.php b/app/Services/Nodes/NodeDeletionService.php index e4d2ed99..75118fa3 100644 --- a/app/Services/Nodes/NodeDeletionService.php +++ b/app/Services/Nodes/NodeDeletionService.php @@ -35,9 +35,9 @@ class NodeDeletionService /** * DeletionService constructor. * - * @param \Pterodactyl\Contracts\Repository\NodeRepositoryInterface $repository + * @param \Pterodactyl\Contracts\Repository\NodeRepositoryInterface $repository * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $serverRepository - * @param \Illuminate\Contracts\Translation\Translator $translator + * @param \Illuminate\Contracts\Translation\Translator $translator */ public function __construct( NodeRepositoryInterface $repository, diff --git a/app/Services/Nodes/NodeJWTService.php b/app/Services/Nodes/NodeJWTService.php new file mode 100644 index 00000000..6c9dd757 --- /dev/null +++ b/app/Services/Nodes/NodeJWTService.php @@ -0,0 +1,74 @@ +claims = $claims; + + return $this; + } + + public function setExpiresAt(DateTimeInterface $date) + { + $this->expiresAt = $date->getTimestamp(); + + return $this; + } + + /** + * Generate a new JWT for a given node. + * + * @param \Pterodactyl\Models\Node $node + * @param string|null $identifiedBy + * @return \Lcobucci\JWT\Token + */ + public function handle(Node $node, string $identifiedBy) + { + $signer = new Sha256; + + $builder = (new Builder)->issuedBy(config('app.url')) + ->permittedFor($node->getConnectionAddress()) + ->identifiedBy(hash('sha256', $identifiedBy), true) + ->issuedAt(CarbonImmutable::now()->getTimestamp()) + ->canOnlyBeUsedAfter(CarbonImmutable::now()->subMinutes(5)->getTimestamp()); + + if ($this->expiresAt) { + $builder = $builder->expiresAt($this->expiresAt); + } + + foreach ($this->claims as $key => $value) { + $builder = $builder->withClaim($key, $value); + } + + return $builder + ->withClaim('unique_id', Str::random(16)) + ->getToken($signer, new Key($node->getDecryptedKey())); + } +} diff --git a/app/Services/Nodes/NodeUpdateService.php b/app/Services/Nodes/NodeUpdateService.php index 219e2a34..ce9238d7 100644 --- a/app/Services/Nodes/NodeUpdateService.php +++ b/app/Services/Nodes/NodeUpdateService.php @@ -2,14 +2,16 @@ namespace Pterodactyl\Services\Nodes; +use Illuminate\Support\Str; use Pterodactyl\Models\Node; use GuzzleHttp\Exception\ConnectException; -use GuzzleHttp\Exception\RequestException; use Illuminate\Database\ConnectionInterface; -use Pterodactyl\Contracts\Repository\NodeRepositoryInterface; +use Illuminate\Contracts\Encryption\Encrypter; +use Pterodactyl\Repositories\Eloquent\NodeRepository; +use Pterodactyl\Repositories\Daemon\ConfigurationRepository; +use Pterodactyl\Repositories\Wings\DaemonConfigurationRepository; use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException; use Pterodactyl\Exceptions\Service\Node\ConfigurationNotPersistedException; -use Pterodactyl\Contracts\Repository\Daemon\ConfigurationRepositoryInterface; class NodeUpdateService { @@ -19,29 +21,37 @@ class NodeUpdateService private $connection; /** - * @var \Pterodactyl\Contracts\Repository\Daemon\ConfigurationRepositoryInterface + * @var \Pterodactyl\Repositories\Wings\DaemonConfigurationRepository */ - private $configRepository; + private $configurationRepository; /** - * @var \Pterodactyl\Contracts\Repository\NodeRepositoryInterface + * @var \Illuminate\Contracts\Encryption\Encrypter + */ + private $encrypter; + + /** + * @var \Pterodactyl\Repositories\Eloquent\NodeRepository */ private $repository; /** * UpdateService constructor. * - * @param \Illuminate\Database\ConnectionInterface $connection - * @param \Pterodactyl\Contracts\Repository\Daemon\ConfigurationRepositoryInterface $configurationRepository - * @param \Pterodactyl\Contracts\Repository\NodeRepositoryInterface $repository + * @param \Illuminate\Database\ConnectionInterface $connection + * @param \Illuminate\Contracts\Encryption\Encrypter $encrypter + * @param \Pterodactyl\Repositories\Wings\DaemonConfigurationRepository $configurationRepository + * @param \Pterodactyl\Repositories\Eloquent\NodeRepository $repository */ public function __construct( ConnectionInterface $connection, - ConfigurationRepositoryInterface $configurationRepository, - NodeRepositoryInterface $repository + Encrypter $encrypter, + DaemonConfigurationRepository $configurationRepository, + NodeRepository $repository ) { $this->connection = $connection; - $this->configRepository = $configurationRepository; + $this->configurationRepository = $configurationRepository; + $this->encrypter = $encrypter; $this->repository = $repository; } @@ -49,55 +59,51 @@ class NodeUpdateService * Update the configuration values for a given node on the machine. * * @param \Pterodactyl\Models\Node $node - * @param array $data - * @param bool $resetToken + * @param array $data + * @param bool $resetToken * * @return \Pterodactyl\Models\Node - * - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException - * @throws \Pterodactyl\Exceptions\Service\Node\ConfigurationNotPersistedException + * @throws \Throwable */ public function handle(Node $node, array $data, bool $resetToken = false) { if ($resetToken) { - $data['daemonSecret'] = str_random(Node::DAEMON_SECRET_LENGTH); + $data['daemon_token'] = $this->encrypter->encrypt(Str::random(Node::DAEMON_TOKEN_LENGTH)); + $data['daemon_token_id'] = Str::random(Node::DAEMON_TOKEN_ID_LENGTH); } - $this->connection->beginTransaction(); + [$updated, $exception] = $this->connection->transaction(function () use ($data, $node) { + /** @var \Pterodactyl\Models\Node $updated */ + $updated = $this->repository->withFreshModel()->update($node->id, $data, true, true); - /** @var \Pterodactyl\Models\Node $updatedModel */ - $updatedModel = $this->repository->update($node->id, $data); + try { + // If we're changing the FQDN for the node, use the newly provided FQDN for the connection + // address. This should alleviate issues where the node gets pointed to a "valid" FQDN that + // isn't actually running the daemon software, and therefore you can't actually change it + // back. + // + // This makes more sense anyways, because only the Panel uses the FQDN for connecting, the + // node doesn't actually care about this. + // + // @see https://github.com/pterodactyl/panel/issues/1931 + $node->fqdn = $updated->fqdn; - try { - if ($resetToken) { - // We need to clone the new model and set it's authentication token to be the - // old one so we can connect. Then we will pass the new token through as an - // override on the call. - $cloned = $updatedModel->replicate(['daemonSecret']); - $cloned->setAttribute('daemonSecret', $node->getAttribute('daemonSecret')); + $this->configurationRepository->setNode($node)->update($updated); + } catch (DaemonConnectionException $exception) { + if (! is_null($exception->getPrevious()) && $exception->getPrevious() instanceof ConnectException) { + return [$updated, true]; + } - $this->configRepository->setNode($cloned)->update([ - 'keys' => [$data['daemonSecret']], - ]); - } else { - $this->configRepository->setNode($updatedModel)->update(); + throw $exception; } - $this->connection->commit(); - } catch (RequestException $exception) { - // Failed to connect to the Daemon. Let's go ahead and save the configuration - // and let the user know they'll need to manually update. - if ($exception instanceof ConnectException) { - $this->connection->commit(); + return [$updated, false]; + }); - throw new ConfigurationNotPersistedException(trans('exceptions.node.daemon_off_config_updated')); - } - - throw new DaemonConnectionException($exception); + if ($exception) { + throw new ConfigurationNotPersistedException(trans('exceptions.node.daemon_off_config_updated')); } - return $updatedModel; + return $updated; } } diff --git a/app/Services/Packs/ExportPackService.php b/app/Services/Packs/ExportPackService.php deleted file mode 100644 index 24b4db96..00000000 --- a/app/Services/Packs/ExportPackService.php +++ /dev/null @@ -1,97 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -namespace Pterodactyl\Services\Packs; - -use ZipArchive; -use Pterodactyl\Models\Pack; -use Pterodactyl\Contracts\Repository\PackRepositoryInterface; -use Illuminate\Contracts\Filesystem\Factory as FilesystemFactory; -use Pterodactyl\Exceptions\Service\Pack\ZipArchiveCreationException; - -class ExportPackService -{ - /** - * @var \ZipArchive - */ - protected $archive; - - /** - * @var \Pterodactyl\Contracts\Repository\PackRepositoryInterface - */ - protected $repository; - - /** - * @var \Illuminate\Contracts\Filesystem\Factory - */ - protected $storage; - - /** - * ExportPackService constructor. - * - * @param \Illuminate\Contracts\Filesystem\Factory $storage - * @param \Pterodactyl\Contracts\Repository\PackRepositoryInterface $repository - * @param \ZipArchive $archive - */ - public function __construct( - FilesystemFactory $storage, - PackRepositoryInterface $repository, - ZipArchive $archive - ) { - $this->archive = $archive; - $this->repository = $repository; - $this->storage = $storage; - } - - /** - * Prepare a pack for export. - * - * @param int|\Pterodactyl\Models\Pack $pack - * @param bool $files - * @return string - * - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - * @throws \Pterodactyl\Exceptions\Service\Pack\ZipArchiveCreationException - */ - public function handle($pack, $files = false) - { - if (! $pack instanceof Pack) { - $pack = $this->repository->find($pack); - } - - $json = [ - 'name' => $pack->name, - 'version' => $pack->version, - 'description' => $pack->description, - 'selectable' => $pack->selectable, - 'visible' => $pack->visible, - 'locked' => $pack->locked, - ]; - - $filename = tempnam(sys_get_temp_dir(), 'pterodactyl_'); - if ($files) { - if (! $this->archive->open($filename, $this->archive::CREATE)) { - throw new ZipArchiveCreationException; - } - - foreach ($this->storage->disk()->files('packs/' . $pack->uuid) as $file) { - $this->archive->addFile(storage_path('app/' . $file), basename(storage_path('app/' . $file))); - } - - $this->archive->addFromString('import.json', json_encode($json, JSON_PRETTY_PRINT)); - $this->archive->close(); - } else { - $fp = fopen($filename, 'a+'); - fwrite($fp, json_encode($json, JSON_PRETTY_PRINT)); - fclose($fp); - } - - return $filename; - } -} diff --git a/app/Services/Packs/PackCreationService.php b/app/Services/Packs/PackCreationService.php deleted file mode 100644 index 80b452d8..00000000 --- a/app/Services/Packs/PackCreationService.php +++ /dev/null @@ -1,104 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -namespace Pterodactyl\Services\Packs; - -use Ramsey\Uuid\Uuid; -use Illuminate\Http\UploadedFile; -use Illuminate\Database\ConnectionInterface; -use Pterodactyl\Contracts\Repository\PackRepositoryInterface; -use Pterodactyl\Exceptions\Service\InvalidFileUploadException; -use Illuminate\Contracts\Filesystem\Factory as FilesystemFactory; -use Pterodactyl\Exceptions\Service\Pack\InvalidFileMimeTypeException; - -class PackCreationService -{ - const VALID_UPLOAD_TYPES = [ - 'application/gzip', - 'application/x-gzip', - ]; - - /** - * @var \Illuminate\Database\ConnectionInterface - */ - protected $connection; - - /** - * @var \Pterodactyl\Contracts\Repository\PackRepositoryInterface - */ - protected $repository; - - /** - * @var \Illuminate\Contracts\Filesystem\Factory - */ - protected $storage; - - /** - * PackCreationService constructor. - * - * @param \Illuminate\Database\ConnectionInterface $connection - * @param \Illuminate\Contracts\Filesystem\Factory $storage - * @param \Pterodactyl\Contracts\Repository\PackRepositoryInterface $repository - */ - public function __construct( - ConnectionInterface $connection, - FilesystemFactory $storage, - PackRepositoryInterface $repository - ) { - $this->connection = $connection; - $this->repository = $repository; - $this->storage = $storage; - } - - /** - * Add a new service pack to the system. - * - * @param array $data - * @param \Illuminate\Http\UploadedFile|null $file - * @return \Pterodactyl\Models\Pack - * - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Service\Pack\InvalidFileMimeTypeException - * @throws \Pterodactyl\Exceptions\Service\InvalidFileUploadException - */ - public function handle(array $data, UploadedFile $file = null) - { - if (! is_null($file)) { - if (! $file->isValid()) { - throw new InvalidFileUploadException(trans('exceptions.packs.invalid_upload')); - } - - if (! in_array($file->getMimeType(), self::VALID_UPLOAD_TYPES)) { - throw new InvalidFileMimeTypeException(trans('exceptions.packs.invalid_mime', [ - 'type' => implode(', ', self::VALID_UPLOAD_TYPES), - ])); - } - } - - // Transform values to boolean - $data['selectable'] = isset($data['selectable']); - $data['visible'] = isset($data['visible']); - $data['locked'] = isset($data['locked']); - - $this->connection->beginTransaction(); - $pack = $this->repository->create(array_merge( - ['uuid' => Uuid::uuid4()], - $data - )); - - $this->storage->disk()->makeDirectory('packs/' . $pack->uuid); - if (! is_null($file)) { - $file->storeAs('packs/' . $pack->uuid, 'archive.tar.gz'); - } - - $this->connection->commit(); - - return $pack; - } -} diff --git a/app/Services/Packs/PackDeletionService.php b/app/Services/Packs/PackDeletionService.php deleted file mode 100644 index 9d474331..00000000 --- a/app/Services/Packs/PackDeletionService.php +++ /dev/null @@ -1,85 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -namespace Pterodactyl\Services\Packs; - -use Pterodactyl\Models\Pack; -use Illuminate\Database\ConnectionInterface; -use Pterodactyl\Contracts\Repository\PackRepositoryInterface; -use Pterodactyl\Exceptions\Service\HasActiveServersException; -use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; -use Illuminate\Contracts\Filesystem\Factory as FilesystemFactory; - -class PackDeletionService -{ - /** - * @var \Illuminate\Database\ConnectionInterface - */ - protected $connection; - - /** - * @var \Pterodactyl\Contracts\Repository\PackRepositoryInterface - */ - protected $repository; - - /** - * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface - */ - protected $serverRepository; - - /** - * @var \Illuminate\Contracts\Filesystem\Factory - */ - protected $storage; - - /** - * PackDeletionService constructor. - * - * @param \Illuminate\Database\ConnectionInterface $connection - * @param \Illuminate\Contracts\Filesystem\Factory $storage - * @param \Pterodactyl\Contracts\Repository\PackRepositoryInterface $repository - * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $serverRepository - */ - public function __construct( - ConnectionInterface $connection, - FilesystemFactory $storage, - PackRepositoryInterface $repository, - ServerRepositoryInterface $serverRepository - ) { - $this->connection = $connection; - $this->repository = $repository; - $this->serverRepository = $serverRepository; - $this->storage = $storage; - } - - /** - * Delete a pack from the database as well as the archive stored on the server. - * - * @param int|\Pterodactyl\Models\Pack$pack - * - * @throws \Pterodactyl\Exceptions\Service\HasActiveServersException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - */ - public function handle($pack) - { - if (! $pack instanceof Pack) { - $pack = $this->repository->setColumns(['id', 'uuid'])->find($pack); - } - - $count = $this->serverRepository->findCountWhere([['pack_id', '=', $pack->id]]); - if ($count !== 0) { - throw new HasActiveServersException(trans('exceptions.packs.delete_has_servers')); - } - - $this->connection->beginTransaction(); - $this->repository->delete($pack->id); - $this->storage->disk()->deleteDirectory('packs/' . $pack->uuid); - $this->connection->commit(); - } -} diff --git a/app/Services/Packs/PackUpdateService.php b/app/Services/Packs/PackUpdateService.php deleted file mode 100644 index a22a55b9..00000000 --- a/app/Services/Packs/PackUpdateService.php +++ /dev/null @@ -1,75 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -namespace Pterodactyl\Services\Packs; - -use Pterodactyl\Models\Pack; -use Pterodactyl\Contracts\Repository\PackRepositoryInterface; -use Pterodactyl\Exceptions\Service\HasActiveServersException; -use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; - -class PackUpdateService -{ - /** - * @var \Pterodactyl\Contracts\Repository\PackRepositoryInterface - */ - protected $repository; - - /** - * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface - */ - protected $serverRepository; - - /** - * PackUpdateService constructor. - * - * @param \Pterodactyl\Contracts\Repository\PackRepositoryInterface $repository - * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $serverRepository - */ - public function __construct( - PackRepositoryInterface $repository, - ServerRepositoryInterface $serverRepository - ) { - $this->repository = $repository; - $this->serverRepository = $serverRepository; - } - - /** - * Update a pack. - * - * @param int|\Pterodactyl\Models\Pack $pack - * @param array $data - * @return bool - * - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Service\HasActiveServersException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - */ - public function handle($pack, array $data) - { - if (! $pack instanceof Pack) { - $pack = $this->repository->setColumns(['id', 'egg_id'])->find($pack); - } - - if ((int) array_get($data, 'egg_id', $pack->egg_id) !== $pack->egg_id) { - $count = $this->serverRepository->findCountWhere([['pack_id', '=', $pack->id]]); - - if ($count !== 0) { - throw new HasActiveServersException(trans('exceptions.packs.update_has_servers')); - } - } - - // Transform values to boolean - $data['selectable'] = isset($data['selectable']); - $data['visible'] = isset($data['visible']); - $data['locked'] = isset($data['locked']); - - return $this->repository->withoutFreshModel()->update($pack->id, $data); - } -} diff --git a/app/Services/Packs/TemplateUploadService.php b/app/Services/Packs/TemplateUploadService.php deleted file mode 100644 index 6495edd6..00000000 --- a/app/Services/Packs/TemplateUploadService.php +++ /dev/null @@ -1,125 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -namespace Pterodactyl\Services\Packs; - -use ZipArchive; -use Illuminate\Http\UploadedFile; -use Pterodactyl\Exceptions\Service\InvalidFileUploadException; -use Pterodactyl\Exceptions\Service\Pack\ZipExtractionException; -use Pterodactyl\Exceptions\Service\Pack\InvalidFileMimeTypeException; -use Pterodactyl\Exceptions\Service\Pack\UnreadableZipArchiveException; -use Pterodactyl\Exceptions\Service\Pack\InvalidPackArchiveFormatException; - -class TemplateUploadService -{ - const VALID_UPLOAD_TYPES = [ - 'application/zip', - 'text/plain', - 'application/json', - ]; - - /** - * @var \ZipArchive - */ - protected $archive; - - /** - * @var \Pterodactyl\Services\Packs\PackCreationService - */ - protected $creationService; - - /** - * TemplateUploadService constructor. - * - * @param \Pterodactyl\Services\Packs\PackCreationService $creationService - * @param \ZipArchive $archive - */ - public function __construct( - PackCreationService $creationService, - ZipArchive $archive - ) { - $this->archive = $archive; - $this->creationService = $creationService; - } - - /** - * Process an uploaded file to create a new pack from a JSON or ZIP format. - * - * @param int $egg - * @param \Illuminate\Http\UploadedFile $file - * @return \Pterodactyl\Models\Pack - * - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Service\Pack\ZipExtractionException - * @throws \Pterodactyl\Exceptions\Service\InvalidFileUploadException - * @throws \Pterodactyl\Exceptions\Service\Pack\InvalidFileMimeTypeException - * @throws \Pterodactyl\Exceptions\Service\Pack\UnreadableZipArchiveException - * @throws \Pterodactyl\Exceptions\Service\Pack\InvalidPackArchiveFormatException - */ - public function handle($egg, UploadedFile $file) - { - if (! $file->isValid()) { - throw new InvalidFileUploadException(trans('exceptions.packs.invalid_upload')); - } - - if (! in_array($file->getMimeType(), self::VALID_UPLOAD_TYPES)) { - throw new InvalidFileMimeTypeException(trans('exceptions.packs.invalid_mime', [ - 'type' => implode(', ', self::VALID_UPLOAD_TYPES), - ])); - } - - if ($file->getMimeType() === 'application/zip') { - return $this->handleArchive($egg, $file); - } else { - $json = json_decode($file->openFile()->fread($file->getSize()), true); - $json['egg_id'] = $egg; - - return $this->creationService->handle($json); - } - } - - /** - * Process a ZIP file to create a pack and stored archive. - * - * @param int $egg - * @param \Illuminate\Http\UploadedFile $file - * @return \Pterodactyl\Models\Pack - * - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Service\Pack\ZipExtractionException - * @throws \Pterodactyl\Exceptions\Service\InvalidFileUploadException - * @throws \Pterodactyl\Exceptions\Service\Pack\InvalidFileMimeTypeException - * @throws \Pterodactyl\Exceptions\Service\Pack\UnreadableZipArchiveException - * @throws \Pterodactyl\Exceptions\Service\Pack\InvalidPackArchiveFormatException - */ - protected function handleArchive($egg, $file) - { - if (! $this->archive->open($file->getRealPath())) { - throw new UnreadableZipArchiveException(trans('exceptions.packs.unreadable')); - } - - if (! $this->archive->locateName('import.json') || ! $this->archive->locateName('archive.tar.gz')) { - throw new InvalidPackArchiveFormatException(trans('exceptions.packs.invalid_archive_exception')); - } - - $json = json_decode($this->archive->getFromName('import.json'), true); - $json['egg_id'] = $egg; - - $pack = $this->creationService->handle($json); - if (! $this->archive->extractTo(storage_path('app/packs/' . $pack->uuid), 'archive.tar.gz')) { - // @todo delete the pack that was created. - throw new ZipExtractionException(trans('exceptions.packs.zip_extraction')); - } - - $this->archive->close(); - - return $pack; - } -} diff --git a/app/Services/Schedules/ProcessScheduleService.php b/app/Services/Schedules/ProcessScheduleService.php index 4ada2aee..3fa3604a 100644 --- a/app/Services/Schedules/ProcessScheduleService.php +++ b/app/Services/Schedules/ProcessScheduleService.php @@ -29,9 +29,9 @@ class ProcessScheduleService /** * ProcessScheduleService constructor. * - * @param \Illuminate\Contracts\Bus\Dispatcher $dispatcher + * @param \Illuminate\Contracts\Bus\Dispatcher $dispatcher * @param \Pterodactyl\Contracts\Repository\ScheduleRepositoryInterface $scheduleRepository - * @param \Pterodactyl\Contracts\Repository\TaskRepositoryInterface $taskRepository + * @param \Pterodactyl\Contracts\Repository\TaskRepositoryInterface $taskRepository */ public function __construct( Dispatcher $dispatcher, @@ -73,7 +73,7 @@ class ProcessScheduleService $this->taskRepository->update($task->id, ['is_queued' => true]); $this->dispatcher->dispatch( - (new RunTaskJob($task->id, $schedule->id))->delay($task->time_offset) + (new RunTaskJob($task))->delay($task->time_offset) ); } } diff --git a/app/Services/Schedules/ScheduleCreationService.php b/app/Services/Schedules/ScheduleCreationService.php deleted file mode 100644 index c8cbd7fb..00000000 --- a/app/Services/Schedules/ScheduleCreationService.php +++ /dev/null @@ -1,98 +0,0 @@ -connection = $connection; - $this->repository = $repository; - $this->taskCreationService = $taskCreationService; - } - - /** - * Create a new schedule for a specific server. - * - * @param \Pterodactyl\Models\Server $server - * @param array $data - * @param array $tasks - * @return \Pterodactyl\Models\Schedule - * - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Service\Schedule\Task\TaskIntervalTooLongException - */ - public function handle(Server $server, array $data, array $tasks = []) - { - $data = array_merge($data, [ - 'server_id' => $server->id, - 'next_run_at' => $this->getCronTimestamp($data), - ]); - - $this->connection->beginTransaction(); - $schedule = $this->repository->create($data); - - foreach ($tasks as $index => $task) { - $this->taskCreationService->handle($schedule, [ - 'time_interval' => array_get($task, 'time_interval'), - 'time_value' => array_get($task, 'time_value'), - 'sequence_id' => $index + 1, - 'action' => array_get($task, 'action'), - 'payload' => array_get($task, 'payload'), - ], false); - } - - $this->connection->commit(); - - return $schedule; - } - - /** - * Return a DateTime object after parsing the cron data provided. - * - * @param array $data - * @return \DateTime - */ - private function getCronTimestamp(array $data) - { - $formattedCron = sprintf('%s %s %s * %s', - array_get($data, 'cron_minute', '*'), - array_get($data, 'cron_hour', '*'), - array_get($data, 'cron_day_of_month', '*'), - array_get($data, 'cron_day_of_week', '*') - ); - - return CronExpression::factory($formattedCron)->getNextRunDate(); - } -} diff --git a/app/Services/Schedules/ScheduleUpdateService.php b/app/Services/Schedules/ScheduleUpdateService.php deleted file mode 100644 index 1ddbbd24..00000000 --- a/app/Services/Schedules/ScheduleUpdateService.php +++ /dev/null @@ -1,110 +0,0 @@ -connection = $connection; - $this->repository = $repository; - $this->taskCreationService = $taskCreationService; - $this->taskRepository = $taskRepository; - } - - /** - * Update an existing schedule by deleting all current tasks and re-inserting the - * new values. - * - * @param \Pterodactyl\Models\Schedule $schedule - * @param array $data - * @param array $tasks - * @return \Pterodactyl\Models\Schedule - * - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - * @throws \Pterodactyl\Exceptions\Service\Schedule\Task\TaskIntervalTooLongException - */ - public function handle(Schedule $schedule, array $data, array $tasks): Schedule - { - $data = array_merge($data, [ - 'next_run_at' => $this->getCronTimestamp($data), - ]); - - $this->connection->beginTransaction(); - - $schedule = $this->repository->update($schedule->id, $data); - $this->taskRepository->deleteWhere([['schedule_id', '=', $schedule->id]]); - - foreach ($tasks as $index => $task) { - $this->taskCreationService->handle($schedule, [ - 'time_interval' => array_get($task, 'time_interval'), - 'time_value' => array_get($task, 'time_value'), - 'sequence_id' => $index + 1, - 'action' => array_get($task, 'action'), - 'payload' => array_get($task, 'payload'), - ], false); - } - - $this->connection->commit(); - - return $schedule; - } - - /** - * Return a DateTime object after parsing the cron data provided. - * - * @param array $data - * @return \DateTime - */ - private function getCronTimestamp(array $data) - { - $formattedCron = sprintf('%s %s %s * %s', - array_get($data, 'cron_minute', '*'), - array_get($data, 'cron_hour', '*'), - array_get($data, 'cron_day_of_month', '*'), - array_get($data, 'cron_day_of_week', '*') - ); - - return CronExpression::factory($formattedCron)->getNextRunDate(); - } -} diff --git a/app/Services/Schedules/Tasks/TaskCreationService.php b/app/Services/Schedules/Tasks/TaskCreationService.php deleted file mode 100644 index 6cf4fc38..00000000 --- a/app/Services/Schedules/Tasks/TaskCreationService.php +++ /dev/null @@ -1,70 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -namespace Pterodactyl\Services\Schedules\Tasks; - -use Webmozart\Assert\Assert; -use Pterodactyl\Models\Schedule; -use Pterodactyl\Contracts\Repository\TaskRepositoryInterface; -use Pterodactyl\Exceptions\Service\Schedule\Task\TaskIntervalTooLongException; - -class TaskCreationService -{ - const MAX_INTERVAL_TIME_SECONDS = 900; - - /** - * @var \Pterodactyl\Contracts\Repository\TaskRepositoryInterface - */ - protected $repository; - - /** - * TaskCreationService constructor. - * - * @param \Pterodactyl\Contracts\Repository\TaskRepositoryInterface $repository - */ - public function __construct(TaskRepositoryInterface $repository) - { - $this->repository = $repository; - } - - /** - * Create a new task that is assigned to a schedule. - * - * @param int|\Pterodactyl\Models\Schedule $schedule - * @param array $data - * @param bool $returnModel - * @return bool|\Pterodactyl\Models\Task - * - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Service\Schedule\Task\TaskIntervalTooLongException - */ - public function handle($schedule, array $data, $returnModel = true) - { - Assert::true(($schedule instanceof Schedule || is_digit($schedule)), - 'First argument passed to handle must be numeric or instance of \Pterodactyl\Models\Schedule, received %s.' - ); - - $schedule = ($schedule instanceof Schedule) ? $schedule->id : $schedule; - $delay = $data['time_interval'] === 'm' ? $data['time_value'] * 60 : $data['time_value']; - if ($delay > self::MAX_INTERVAL_TIME_SECONDS) { - throw new TaskIntervalTooLongException(trans('exceptions.tasks.chain_interval_too_long')); - } - - $repository = ($returnModel) ? $this->repository : $this->repository->withoutFreshModel(); - $task = $repository->create([ - 'schedule_id' => $schedule, - 'sequence_id' => $data['sequence_id'], - 'action' => $data['action'], - 'payload' => $data['payload'], - 'time_offset' => $delay, - ], false); - - return $task; - } -} diff --git a/app/Services/Servers/BuildModificationService.php b/app/Services/Servers/BuildModificationService.php index a662cd7b..86ff5bb6 100644 --- a/app/Services/Servers/BuildModificationService.php +++ b/app/Services/Servers/BuildModificationService.php @@ -2,15 +2,16 @@ namespace Pterodactyl\Services\Servers; +use Illuminate\Support\Arr; use Pterodactyl\Models\Server; use GuzzleHttp\Exception\RequestException; use Illuminate\Database\ConnectionInterface; use Pterodactyl\Exceptions\DisplayException; +use Pterodactyl\Repositories\Wings\DaemonServerRepository; use Pterodactyl\Exceptions\Repository\RecordNotFoundException; use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; use Pterodactyl\Contracts\Repository\AllocationRepositoryInterface; use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException; -use Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface as DaemonServerRepositoryInterface; class BuildModificationService { @@ -25,7 +26,7 @@ class BuildModificationService private $connection; /** - * @var \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface + * @var \Pterodactyl\Repositories\Wings\DaemonServerRepository */ private $daemonServerRepository; @@ -34,82 +35,84 @@ class BuildModificationService */ private $repository; + /** + * @var \Pterodactyl\Services\Servers\ServerConfigurationStructureService + */ + private $structureService; + /** * BuildModificationService constructor. * - * @param \Pterodactyl\Contracts\Repository\AllocationRepositoryInterface $allocationRepository - * @param \Illuminate\Database\ConnectionInterface $connection - * @param \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface $daemonServerRepository - * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository + * @param \Pterodactyl\Contracts\Repository\AllocationRepositoryInterface $allocationRepository + * @param \Pterodactyl\Services\Servers\ServerConfigurationStructureService $structureService + * @param \Illuminate\Database\ConnectionInterface $connection + * @param \Pterodactyl\Repositories\Wings\DaemonServerRepository $daemonServerRepository + * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository */ public function __construct( AllocationRepositoryInterface $allocationRepository, + ServerConfigurationStructureService $structureService, ConnectionInterface $connection, - DaemonServerRepositoryInterface $daemonServerRepository, + DaemonServerRepository $daemonServerRepository, ServerRepositoryInterface $repository ) { $this->allocationRepository = $allocationRepository; $this->daemonServerRepository = $daemonServerRepository; $this->connection = $connection; $this->repository = $repository; + $this->structureService = $structureService; } /** * Change the build details for a specified server. * * @param \Pterodactyl\Models\Server $server - * @param array $data + * @param array $data * @return \Pterodactyl\Models\Server * * @throws \Pterodactyl\Exceptions\DisplayException - * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Pterodactyl\Exceptions\Model\DataValidationException */ public function handle(Server $server, array $data) { - $build = []; $this->connection->beginTransaction(); $this->processAllocations($server, $data); + if (isset($data['allocation_id']) && $data['allocation_id'] != $server->allocation_id) { try { - $allocation = $this->allocationRepository->findFirstWhere([ + $this->allocationRepository->findFirstWhere([ ['id', '=', $data['allocation_id']], ['server_id', '=', $server->id], ]); } catch (RecordNotFoundException $ex) { throw new DisplayException(trans('admin/server.exceptions.default_allocation_not_found')); } - - $build['default'] = ['ip' => $allocation->ip, 'port' => $allocation->port]; } + /* @var \Pterodactyl\Models\Server $server */ $server = $this->repository->withFreshModel()->update($server->id, [ 'oom_disabled' => array_get($data, 'oom_disabled'), 'memory' => array_get($data, 'memory'), 'swap' => array_get($data, 'swap'), 'io' => array_get($data, 'io'), 'cpu' => array_get($data, 'cpu'), + 'threads' => array_get($data, 'threads'), 'disk' => array_get($data, 'disk'), 'allocation_id' => array_get($data, 'allocation_id'), - 'database_limit' => array_get($data, 'database_limit'), - 'allocation_limit' => array_get($data, 'allocation_limit'), + 'database_limit' => array_get($data, 'database_limit', 0), + 'allocation_limit' => array_get($data, 'allocation_limit', 0), + 'backup_limit' => array_get($data, 'backup_limit', 0), ]); - $allocations = $this->allocationRepository->findWhere([['server_id', '=', $server->id]]); - - $build['oom_disabled'] = $server->oom_disabled; - $build['memory'] = (int) $server->memory; - $build['swap'] = (int) $server->swap; - $build['io'] = (int) $server->io; - $build['cpu'] = (int) $server->cpu; - $build['disk'] = (int) $server->disk; - $build['ports|overwrite'] = $allocations->groupBy('ip')->map(function ($item) { - return $item->pluck('port'); - })->toArray(); + $updateData = $this->structureService->handle($server); try { - $this->daemonServerRepository->setServer($server)->update(['build' => $build]); + $this->daemonServerRepository + ->setServer($server) + ->update(Arr::only($updateData, ['build'])); + $this->connection->commit(); } catch (RequestException $exception) { throw new DaemonConnectionException($exception); @@ -123,7 +126,7 @@ class BuildModificationService * are available for a server. * * @param \Pterodactyl\Models\Server $server - * @param array $data + * @param array $data * * @throws \Pterodactyl\Exceptions\DisplayException */ @@ -156,7 +159,7 @@ class BuildModificationService // Handle removal of allocations from this server. if (array_key_exists('remove_allocations', $data) && ! empty($data['remove_allocations'])) { - $assigned = $this->allocationRepository->getAssignedAllocationIds($server->id); + $assigned = $server->allocations->pluck('id')->toArray(); $updateIds = []; foreach ($data['remove_allocations'] as $allocation) { diff --git a/app/Services/Servers/ContainerRebuildService.php b/app/Services/Servers/ContainerRebuildService.php deleted file mode 100644 index 0dc25b0e..00000000 --- a/app/Services/Servers/ContainerRebuildService.php +++ /dev/null @@ -1,42 +0,0 @@ -repository = $repository; - } - - /** - * Mark a server for rebuild on next boot cycle. - * - * @param \Pterodactyl\Models\Server $server - * - * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException - */ - public function handle(Server $server) - { - try { - $this->repository->setServer($server)->rebuild(); - } catch (RequestException $exception) { - throw new DaemonConnectionException($exception); - } - } -} diff --git a/app/Services/Servers/DetailsModificationService.php b/app/Services/Servers/DetailsModificationService.php index 523185f2..65a5b281 100644 --- a/app/Services/Servers/DetailsModificationService.php +++ b/app/Services/Servers/DetailsModificationService.php @@ -6,8 +6,6 @@ use Pterodactyl\Models\Server; use Illuminate\Database\ConnectionInterface; use Pterodactyl\Traits\Services\ReturnsUpdatedModels; use Pterodactyl\Repositories\Eloquent\ServerRepository; -use Pterodactyl\Services\DaemonKeys\DaemonKeyCreationService; -use Pterodactyl\Services\DaemonKeys\DaemonKeyDeletionService; class DetailsModificationService { @@ -18,16 +16,6 @@ class DetailsModificationService */ private $connection; - /** - * @var \Pterodactyl\Services\DaemonKeys\DaemonKeyCreationService - */ - private $keyCreationService; - - /** - * @var \Pterodactyl\Services\DaemonKeys\DaemonKeyDeletionService - */ - private $keyDeletionService; - /** * @var \Pterodactyl\Repositories\Eloquent\ServerRepository */ @@ -36,20 +24,14 @@ class DetailsModificationService /** * DetailsModificationService constructor. * - * @param \Illuminate\Database\ConnectionInterface $connection - * @param \Pterodactyl\Services\DaemonKeys\DaemonKeyCreationService $keyCreationService - * @param \Pterodactyl\Services\DaemonKeys\DaemonKeyDeletionService $keyDeletionService - * @param \Pterodactyl\Repositories\Eloquent\ServerRepository $repository + * @param \Illuminate\Database\ConnectionInterface $connection + * @param \Pterodactyl\Repositories\Eloquent\ServerRepository $repository */ public function __construct( ConnectionInterface $connection, - DaemonKeyCreationService $keyCreationService, - DaemonKeyDeletionService $keyDeletionService, ServerRepository $repository ) { $this->connection = $connection; - $this->keyCreationService = $keyCreationService; - $this->keyDeletionService = $keyDeletionService; $this->repository = $repository; } @@ -57,10 +39,9 @@ class DetailsModificationService * Update the details for a single server instance. * * @param \Pterodactyl\Models\Server $server - * @param array $data + * @param array $data * @return bool|\Pterodactyl\Models\Server * - * @throws \Pterodactyl\Exceptions\DisplayException * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ @@ -75,11 +56,6 @@ class DetailsModificationService 'description' => array_get($data, 'description') ?? '', ], true, true); - if ((int) array_get($data, 'owner_id', 0) !== (int) $server->owner_id) { - $this->keyDeletionService->handle($server, $server->owner_id); - $this->keyCreationService->handle($server->id, array_get($data, 'owner_id')); - } - $this->connection->commit(); return $response; diff --git a/app/Services/Servers/EnvironmentService.php b/app/Services/Servers/EnvironmentService.php index b17076e0..6f7b590f 100644 --- a/app/Services/Servers/EnvironmentService.php +++ b/app/Services/Servers/EnvironmentService.php @@ -3,8 +3,7 @@ namespace Pterodactyl\Services\Servers; use Pterodactyl\Models\Server; -use Illuminate\Contracts\Config\Repository as ConfigRepository; -use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; +use Pterodactyl\Models\EggVariable; class EnvironmentService { @@ -13,33 +12,11 @@ class EnvironmentService */ private $additional = []; - /** - * @var \Illuminate\Contracts\Config\Repository - */ - private $config; - - /** - * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface - */ - private $repository; - - /** - * EnvironmentService constructor. - * - * @param \Illuminate\Contracts\Config\Repository $config - * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository - */ - public function __construct(ConfigRepository $config, ServerRepositoryInterface $repository) - { - $this->config = $config; - $this->repository = $repository; - } - /** * Dynamically configure additional environment variables to be assigned * with a specific server. * - * @param string $key + * @param string $key * @param callable $closure */ public function setEnvironmentKey(string $key, callable $closure) @@ -63,35 +40,33 @@ class EnvironmentService * * @param \Pterodactyl\Models\Server $server * @return array - * - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ public function handle(Server $server): array { - $variables = $this->repository->getVariablesWithValues($server->id); + $variables = $server->variables->toBase()->mapWithKeys(function (EggVariable $variable) { + return [$variable->env_variable => $variable->server_value ?? $variable->default_value]; + }); // Process environment variables defined in this file. This is done first // in order to allow run-time and config defined variables to take // priority over built-in values. foreach ($this->getEnvironmentMappings() as $key => $object) { - $variables[$key] = object_get($server, $object); + $variables->put($key, object_get($server, $object)); } // Process variables set in the configuration file. - foreach ($this->config->get('pterodactyl.environment_variables', []) as $key => $object) { - if (is_callable($object)) { - $variables[$key] = call_user_func($object, $server); - } else { - $variables[$key] = object_get($server, $object); - } + foreach (config('pterodactyl.environment_variables', []) as $key => $object) { + $variables->put( + $key, is_callable($object) ? call_user_func($object, $server) : object_get($server, $object) + ); } // Process dynamically included environment variables. foreach ($this->additional as $key => $closure) { - $variables[$key] = call_user_func($closure, $server); + $variables->put($key, call_user_func($closure, $server)); } - return $variables; + return $variables->toArray(); } /** diff --git a/app/Services/Servers/GetUserPermissionsService.php b/app/Services/Servers/GetUserPermissionsService.php new file mode 100644 index 00000000..e0ea2037 --- /dev/null +++ b/app/Services/Servers/GetUserPermissionsService.php @@ -0,0 +1,37 @@ +root_admin || $user->id === $server->owner_id) { + $permissions = ['*']; + + if ($user->root_admin) { + $permissions[] = 'admin.websocket.errors'; + $permissions[] = 'admin.websocket.install'; + } + + return $permissions; + } + + /** @var \Pterodactyl\Models\Subuser|null $subuserPermissions */ + $subuserPermissions = $server->subusers()->where('user_id', $user->id)->first(); + + return $subuserPermissions ? $subuserPermissions->permissions : []; + } +} diff --git a/app/Services/Servers/ReinstallServerService.php b/app/Services/Servers/ReinstallServerService.php index 85800473..6f5b5608 100644 --- a/app/Services/Servers/ReinstallServerService.php +++ b/app/Services/Servers/ReinstallServerService.php @@ -1,78 +1,54 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Services\Servers; use Pterodactyl\Models\Server; -use GuzzleHttp\Exception\RequestException; use Illuminate\Database\ConnectionInterface; -use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; -use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException; -use Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface as DaemonServerRepositoryInterface; +use Pterodactyl\Repositories\Eloquent\ServerRepository; +use Pterodactyl\Repositories\Wings\DaemonServerRepository; class ReinstallServerService { /** - * @var \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface + * @var \Pterodactyl\Repositories\Wings\DaemonServerRepository */ - protected $daemonServerRepository; + private $daemonServerRepository; /** * @var \Illuminate\Database\ConnectionInterface */ - protected $database; - - /** - * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface - */ - protected $repository; + private $connection; /** * ReinstallService constructor. * - * @param \Illuminate\Database\ConnectionInterface $database - * @param \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface $daemonServerRepository - * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository + * @param \Illuminate\Database\ConnectionInterface $connection + * @param \Pterodactyl\Repositories\Wings\DaemonServerRepository $daemonServerRepository */ public function __construct( - ConnectionInterface $database, - DaemonServerRepositoryInterface $daemonServerRepository, - ServerRepositoryInterface $repository + ConnectionInterface $connection, + DaemonServerRepository $daemonServerRepository ) { $this->daemonServerRepository = $daemonServerRepository; - $this->database = $database; - $this->repository = $repository; + $this->connection = $connection; } /** - * @param int|\Pterodactyl\Models\Server $server + * Reinstall a server on the remote daemon. * - * @throws \Pterodactyl\Exceptions\DisplayException - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @param \Pterodactyl\Models\Server $server + * @return \Pterodactyl\Models\Server + * + * @throws \Throwable */ - public function reinstall($server) + public function handle(Server $server) { - if (! $server instanceof Server) { - $server = $this->repository->find($server); - } + return $this->connection->transaction(function () use ($server) { + $server->forceFill(['installed' => Server::STATUS_INSTALLING])->save(); - $this->database->beginTransaction(); - $this->repository->withoutFreshModel()->update($server->id, [ - 'installed' => 0, - ], true, true); - - try { $this->daemonServerRepository->setServer($server)->reinstall(); - $this->database->commit(); - } catch (RequestException $exception) { - throw new DaemonConnectionException($exception); - } + + return $server->refresh(); + }); } } diff --git a/app/Services/Servers/ServerConfigurationStructureService.php b/app/Services/Servers/ServerConfigurationStructureService.php index 46a71092..fb441217 100644 --- a/app/Services/Servers/ServerConfigurationStructureService.php +++ b/app/Services/Servers/ServerConfigurationStructureService.php @@ -1,64 +1,101 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Services\Servers; +use Pterodactyl\Models\Mount; use Pterodactyl\Models\Server; -use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; class ServerConfigurationStructureService { - const REQUIRED_RELATIONS = ['allocation', 'allocations', 'pack', 'option']; + const REQUIRED_RELATIONS = ['allocation', 'allocations', 'egg']; /** * @var \Pterodactyl\Services\Servers\EnvironmentService */ private $environment; - /** - * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface - */ - private $repository; - /** * ServerConfigurationStructureService constructor. * - * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository - * @param \Pterodactyl\Services\Servers\EnvironmentService $environment + * @param \Pterodactyl\Services\Servers\EnvironmentService $environment */ - public function __construct( - ServerRepositoryInterface $repository, - EnvironmentService $environment - ) { - $this->repository = $repository; + public function __construct(EnvironmentService $environment) + { $this->environment = $environment; } /** * Return a configuration array for a specific server when passed a server model. * + * DO NOT MODIFY THIS FUNCTION. This powers legacy code handling for the new Wings + * daemon, if you modify the structure eggs will break unexpectedly. + * + * @param \Pterodactyl\Models\Server $server + * @param bool $legacy + * @return array + */ + public function handle(Server $server, bool $legacy = false): array + { + $server->loadMissing(self::REQUIRED_RELATIONS); + + return $legacy ? + $this->returnLegacyFormat($server) + : $this->returnCurrentFormat($server); + } + + /** + * Returns the new data format used for the Wings daemon. + * * @param \Pterodactyl\Models\Server $server * @return array - * - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function handle(Server $server): array + protected function returnCurrentFormat(Server $server) { - if (array_diff(self::REQUIRED_RELATIONS, $server->getRelations())) { - $server = $this->repository->getDataForCreation($server); - } - - $pack = $server->getRelation('pack'); - if (! is_null($pack)) { - $pack = $server->getRelation('pack')->uuid; - } + return [ + 'uuid' => $server->uuid, + 'suspended' => $server->suspended, + 'environment' => $this->environment->handle($server), + 'invocation' => $server->startup, + 'skip_egg_scripts' => $server->skip_scripts, + 'build' => [ + 'memory_limit' => $server->memory, + 'swap' => $server->swap, + 'io_weight' => $server->io, + 'cpu_limit' => $server->cpu, + 'threads' => $server->threads, + 'disk_space' => $server->disk, + ], + 'container' => [ + 'image' => $server->image, + 'oom_disabled' => $server->oom_disabled, + 'requires_rebuild' => false, + ], + 'allocations' => [ + 'default' => [ + 'ip' => $server->allocation->ip, + 'port' => $server->allocation->port, + ], + 'mappings' => $server->getAllocationMappings(), + ], + 'mounts' => $server->mounts->map(function (Mount $mount) { + return [ + 'source' => $mount->source, + 'target' => $mount->target, + 'read_only' => $mount->read_only, + ]; + }), + ]; + } + /** + * Returns the legacy server data format to continue support for old egg configurations + * that have not yet been updated. + * + * @param \Pterodactyl\Models\Server $server + * @return array + */ + protected function returnLegacyFormat(Server $server) + { return [ 'uuid' => $server->uuid, 'build' => [ @@ -75,12 +112,12 @@ class ServerConfigurationStructureService 'swap' => (int) $server->swap, 'io' => (int) $server->io, 'cpu' => (int) $server->cpu, + 'threads' => $server->threads, 'disk' => (int) $server->disk, 'image' => $server->image, ], 'service' => [ 'egg' => $server->egg->uuid, - 'pack' => $pack, 'skip_scripts' => $server->skip_scripts, ], 'rebuild' => false, diff --git a/app/Services/Servers/ServerCreationService.php b/app/Services/Servers/ServerCreationService.php index e9287caa..3a1b4573 100644 --- a/app/Services/Servers/ServerCreationService.php +++ b/app/Services/Servers/ServerCreationService.php @@ -3,30 +3,25 @@ namespace Pterodactyl\Services\Servers; use Ramsey\Uuid\Uuid; -use Pterodactyl\Models\Node; +use Illuminate\Support\Arr; +use Pterodactyl\Models\Egg; use Pterodactyl\Models\User; +use Webmozart\Assert\Assert; use Pterodactyl\Models\Server; use Illuminate\Support\Collection; use Pterodactyl\Models\Allocation; -use GuzzleHttp\Exception\RequestException; use Illuminate\Database\ConnectionInterface; use Pterodactyl\Models\Objects\DeploymentObject; +use Pterodactyl\Repositories\Eloquent\EggRepository; +use Pterodactyl\Repositories\Eloquent\ServerRepository; +use Pterodactyl\Repositories\Wings\DaemonServerRepository; use Pterodactyl\Services\Deployment\FindViableNodesService; -use Pterodactyl\Contracts\Repository\EggRepositoryInterface; -use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; +use Pterodactyl\Repositories\Eloquent\ServerVariableRepository; use Pterodactyl\Services\Deployment\AllocationSelectionService; -use Pterodactyl\Contracts\Repository\AllocationRepositoryInterface; use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException; -use Pterodactyl\Contracts\Repository\ServerVariableRepositoryInterface; -use Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface as DaemonServerRepositoryInterface; class ServerCreationService { - /** - * @var \Pterodactyl\Contracts\Repository\AllocationRepositoryInterface - */ - private $allocationRepository; - /** * @var \Pterodactyl\Services\Deployment\AllocationSelectionService */ @@ -42,72 +37,77 @@ class ServerCreationService */ private $connection; - /** - * @var \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface - */ - private $daemonServerRepository; - - /** - * @var \Pterodactyl\Contracts\Repository\EggRepositoryInterface - */ - private $eggRepository; - /** * @var \Pterodactyl\Services\Deployment\FindViableNodesService */ private $findViableNodesService; - /** - * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface - */ - private $repository; - - /** - * @var \Pterodactyl\Contracts\Repository\ServerVariableRepositoryInterface - */ - private $serverVariableRepository; - /** * @var \Pterodactyl\Services\Servers\VariableValidatorService */ private $validatorService; + /** + * @var \Pterodactyl\Repositories\Eloquent\EggRepository + */ + private $eggRepository; + + /** + * @var \Pterodactyl\Repositories\Eloquent\ServerRepository + */ + private $repository; + + /** + * @var \Pterodactyl\Repositories\Eloquent\ServerVariableRepository + */ + private $serverVariableRepository; + + /** + * @var \Pterodactyl\Repositories\Wings\DaemonServerRepository + */ + private $daemonServerRepository; + + /** + * @var \Pterodactyl\Services\Servers\ServerDeletionService + */ + private $serverDeletionService; + /** * CreationService constructor. * - * @param \Pterodactyl\Contracts\Repository\AllocationRepositoryInterface $allocationRepository - * @param \Pterodactyl\Services\Deployment\AllocationSelectionService $allocationSelectionService - * @param \Illuminate\Database\ConnectionInterface $connection - * @param \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface $daemonServerRepository - * @param \Pterodactyl\Contracts\Repository\EggRepositoryInterface $eggRepository - * @param \Pterodactyl\Services\Deployment\FindViableNodesService $findViableNodesService - * @param \Pterodactyl\Services\Servers\ServerConfigurationStructureService $configurationStructureService - * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository - * @param \Pterodactyl\Contracts\Repository\ServerVariableRepositoryInterface $serverVariableRepository - * @param \Pterodactyl\Services\Servers\VariableValidatorService $validatorService + * @param \Pterodactyl\Services\Deployment\AllocationSelectionService $allocationSelectionService + * @param \Illuminate\Database\ConnectionInterface $connection + * @param \Pterodactyl\Repositories\Wings\DaemonServerRepository $daemonServerRepository + * @param \Pterodactyl\Repositories\Eloquent\EggRepository $eggRepository + * @param \Pterodactyl\Services\Deployment\FindViableNodesService $findViableNodesService + * @param \Pterodactyl\Services\Servers\ServerConfigurationStructureService $configurationStructureService + * @param \Pterodactyl\Services\Servers\ServerDeletionService $serverDeletionService + * @param \Pterodactyl\Repositories\Eloquent\ServerRepository $repository + * @param \Pterodactyl\Repositories\Eloquent\ServerVariableRepository $serverVariableRepository + * @param \Pterodactyl\Services\Servers\VariableValidatorService $validatorService */ public function __construct( - AllocationRepositoryInterface $allocationRepository, AllocationSelectionService $allocationSelectionService, ConnectionInterface $connection, - DaemonServerRepositoryInterface $daemonServerRepository, - EggRepositoryInterface $eggRepository, + DaemonServerRepository $daemonServerRepository, + EggRepository $eggRepository, FindViableNodesService $findViableNodesService, ServerConfigurationStructureService $configurationStructureService, - ServerRepositoryInterface $repository, - ServerVariableRepositoryInterface $serverVariableRepository, + ServerDeletionService $serverDeletionService, + ServerRepository $repository, + ServerVariableRepository $serverVariableRepository, VariableValidatorService $validatorService ) { $this->allocationSelectionService = $allocationSelectionService; - $this->allocationRepository = $allocationRepository; $this->configurationStructureService = $configurationStructureService; $this->connection = $connection; - $this->daemonServerRepository = $daemonServerRepository; - $this->eggRepository = $eggRepository; $this->findViableNodesService = $findViableNodesService; + $this->validatorService = $validatorService; + $this->eggRepository = $eggRepository; $this->repository = $repository; $this->serverVariableRepository = $serverVariableRepository; - $this->validatorService = $validatorService; + $this->daemonServerRepository = $daemonServerRepository; + $this->serverDeletionService = $serverDeletionService; } /** @@ -116,22 +116,19 @@ class ServerCreationService * as possible given the input data. For example, if an allocation_id is passed with * no node_id the node_is will be picked from the allocation. * - * @param array $data + * @param array $data * @param \Pterodactyl\Models\Objects\DeploymentObject|null $deployment * @return \Pterodactyl\Models\Server * + * @throws \Throwable * @throws \Pterodactyl\Exceptions\DisplayException * @throws \Illuminate\Validation\ValidationException - * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException * @throws \Pterodactyl\Exceptions\Service\Deployment\NoViableNodeException - * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException * @throws \Pterodactyl\Exceptions\Service\Deployment\NoViableAllocationException */ public function handle(array $data, DeploymentObject $deployment = null): Server { - $this->connection->beginTransaction(); - // If a deployment object has been passed we need to get the allocation // that the server should use, and assign the node from that allocation. if ($deployment instanceof DeploymentObject) { @@ -142,35 +139,46 @@ class ServerCreationService // Auto-configure the node based on the selected allocation // if no node was defined. - if (is_null(array_get($data, 'node_id'))) { - $data['node_id'] = $this->getNodeFromAllocation($data['allocation_id']); + if (empty($data['node_id'])) { + Assert::false(empty($data['allocation_id']), 'Expected a non-empty allocation_id in server creation data.'); + + $data['node_id'] = Allocation::query()->findOrFail($data['allocation_id'])->node_id; } - if (is_null(array_get($data, 'nest_id'))) { - $egg = $this->eggRepository->setColumns(['id', 'nest_id'])->find(array_get($data, 'egg_id')); - $data['nest_id'] = $egg->nest_id; + if (empty($data['nest_id'])) { + Assert::false(empty($data['egg_id']), 'Expected a non-empty egg_id in server creation data.'); + + $data['nest_id'] = Egg::query()->findOrFail($data['egg_id'])->nest_id; } $eggVariableData = $this->validatorService ->setUserLevel(User::USER_LEVEL_ADMIN) - ->handle(array_get($data, 'egg_id'), array_get($data, 'environment', [])); + ->handle(Arr::get($data, 'egg_id'), Arr::get($data, 'environment', [])); - // Create the server and assign any additional allocations to it. - $server = $this->createModel($data); - $this->storeAssignedAllocations($server, $data); - $this->storeEggVariables($server, $eggVariableData); + // Due to the design of the Daemon, we need to persist this server to the disk + // before we can actually create it on the Daemon. + // + // If that connection fails out we will attempt to perform a cleanup by just + // deleting the server itself from the system. + /** @var \Pterodactyl\Models\Server $server */ + $server = $this->connection->transaction(function () use ($data, $eggVariableData) { + // Create the server and assign any additional allocations to it. + $server = $this->createModel($data); - $structure = $this->configurationStructureService->handle($server); + $this->storeAssignedAllocations($server, $data); + $this->storeEggVariables($server, $eggVariableData); + + return $server; + }); try { - $this->daemonServerRepository->setServer($server)->create($structure, [ - 'start_on_completion' => (bool) array_get($data, 'start_on_completion', false), - ]); + $this->daemonServerRepository->setServer($server)->create( + $this->configurationStructureService->handle($server) + ); + } catch (DaemonConnectionException $exception) { + $this->serverDeletionService->withForce(true)->handle($server); - $this->connection->commit(); - } catch (RequestException $exception) { - $this->connection->rollBack(); - throw new DaemonConnectionException($exception); + throw $exception; } return $server; @@ -179,7 +187,7 @@ class ServerCreationService /** * Gets an allocation to use for automatic deployment. * - * @param array $data + * @param array $data * @param \Pterodactyl\Models\Objects\DeploymentObject $deployment * * @return \Pterodactyl\Models\Allocation @@ -190,12 +198,12 @@ class ServerCreationService private function configureDeployment(array $data, DeploymentObject $deployment): Allocation { $nodes = $this->findViableNodesService->setLocations($deployment->getLocations()) - ->setDisk(array_get($data, 'disk')) - ->setMemory(array_get($data, 'memory')) + ->setDisk(Arr::get($data, 'disk')) + ->setMemory(Arr::get($data, 'memory')) ->handle(); return $this->allocationSelectionService->setDedicated($deployment->isDedicated()) - ->setNodes($nodes) + ->setNodes($nodes->pluck('id')->toArray()) ->setPorts($deployment->getPorts()) ->handle(); } @@ -212,39 +220,42 @@ class ServerCreationService { $uuid = $this->generateUniqueUuidCombo(); - return $this->repository->create([ - 'external_id' => array_get($data, 'external_id'), + /** @var \Pterodactyl\Models\Server $model */ + $model = $this->repository->create([ + 'external_id' => Arr::get($data, 'external_id'), 'uuid' => $uuid, 'uuidShort' => substr($uuid, 0, 8), - 'node_id' => array_get($data, 'node_id'), - 'name' => array_get($data, 'name'), - 'description' => array_get($data, 'description') ?? '', - 'skip_scripts' => array_get($data, 'skip_scripts') ?? isset($data['skip_scripts']), + 'node_id' => Arr::get($data, 'node_id'), + 'name' => Arr::get($data, 'name'), + 'description' => Arr::get($data, 'description') ?? '', + 'skip_scripts' => Arr::get($data, 'skip_scripts') ?? isset($data['skip_scripts']), 'suspended' => false, - 'owner_id' => array_get($data, 'owner_id'), - 'memory' => array_get($data, 'memory'), - 'swap' => array_get($data, 'swap'), - 'disk' => array_get($data, 'disk'), - 'io' => array_get($data, 'io'), - 'cpu' => array_get($data, 'cpu'), - 'oom_disabled' => array_get($data, 'oom_disabled', true), - 'allocation_id' => array_get($data, 'allocation_id'), - 'nest_id' => array_get($data, 'nest_id'), - 'egg_id' => array_get($data, 'egg_id'), - 'pack_id' => (! isset($data['pack_id']) || $data['pack_id'] == 0) ? null : $data['pack_id'], - 'startup' => array_get($data, 'startup'), - 'daemonSecret' => str_random(Node::DAEMON_SECRET_LENGTH), - 'image' => array_get($data, 'image'), - 'database_limit' => array_get($data, 'database_limit'), - 'allocation_limit' => array_get($data, 'allocation_limit'), + 'owner_id' => Arr::get($data, 'owner_id'), + 'memory' => Arr::get($data, 'memory'), + 'swap' => Arr::get($data, 'swap'), + 'disk' => Arr::get($data, 'disk'), + 'io' => Arr::get($data, 'io'), + 'cpu' => Arr::get($data, 'cpu'), + 'threads' => Arr::get($data, 'threads'), + 'oom_disabled' => Arr::get($data, 'oom_disabled') ?? true, + 'allocation_id' => Arr::get($data, 'allocation_id'), + 'nest_id' => Arr::get($data, 'nest_id'), + 'egg_id' => Arr::get($data, 'egg_id'), + 'startup' => Arr::get($data, 'startup'), + 'image' => Arr::get($data, 'image'), + 'database_limit' => Arr::get($data, 'database_limit') ?? 0, + 'allocation_limit' => Arr::get($data, 'allocation_limit') ?? 0, + 'backup_limit' => Arr::get($data, 'backup_limit') ?? 0, ]); + + return $model; } /** * Configure the allocations assigned to this server. * * @param \Pterodactyl\Models\Server $server - * @param array $data + * @param array $data */ private function storeAssignedAllocations(Server $server, array $data) { @@ -253,13 +264,15 @@ class ServerCreationService $records = array_merge($records, $data['allocation_additional']); } - $this->allocationRepository->assignAllocationsToServer($server->id, $records); + Allocation::query()->whereIn('id', $records)->update([ + 'server_id' => $server->id, + ]); } /** * Process environment variables passed for this server and store them in the database. * - * @param \Pterodactyl\Models\Server $server + * @param \Pterodactyl\Models\Server $server * @param \Illuminate\Support\Collection $variables */ private function storeEggVariables(Server $server, Collection $variables) @@ -277,21 +290,6 @@ class ServerCreationService } } - /** - * Get the node that an allocation belongs to. - * - * @param int $allocation - * @return int - * - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - */ - private function getNodeFromAllocation(int $allocation): int - { - $allocation = $this->allocationRepository->setColumns(['id', 'node_id'])->find($allocation); - - return $allocation->node_id; - } - /** * Create a unique UUID and UUID-Short combo for a server. * diff --git a/app/Services/Servers/ServerDeletionService.php b/app/Services/Servers/ServerDeletionService.php index faed3b9f..95585873 100644 --- a/app/Services/Servers/ServerDeletionService.php +++ b/app/Services/Servers/ServerDeletionService.php @@ -1,84 +1,53 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Services\Servers; -use Psr\Log\LoggerInterface as Writer; -use GuzzleHttp\Exception\RequestException; +use Exception; +use Illuminate\Http\Response; +use Pterodactyl\Models\Server; +use Illuminate\Support\Facades\Log; use Illuminate\Database\ConnectionInterface; +use Pterodactyl\Repositories\Wings\DaemonServerRepository; use Pterodactyl\Services\Databases\DatabaseManagementService; -use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; -use Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface; use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException; -use Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface as DaemonServerRepositoryInterface; class ServerDeletionService { - /** - * @var \Illuminate\Database\ConnectionInterface - */ - protected $connection; - - /** - * @var \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface - */ - protected $daemonServerRepository; - - /** - * @var \Pterodactyl\Services\Databases\DatabaseManagementService - */ - protected $databaseManagementService; - - /** - * @var \Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface - */ - protected $databaseRepository; - /** * @var bool */ protected $force = false; /** - * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface + * @var \Illuminate\Database\ConnectionInterface */ - protected $repository; + private $connection; /** - * @var \Psr\Log\LoggerInterface + * @var \Pterodactyl\Repositories\Wings\DaemonServerRepository */ - protected $writer; + private $daemonServerRepository; + + /** + * @var \Pterodactyl\Services\Databases\DatabaseManagementService + */ + private $databaseManagementService; /** * DeletionService constructor. * - * @param \Illuminate\Database\ConnectionInterface $connection - * @param \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface $daemonServerRepository - * @param \Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface $databaseRepository - * @param \Pterodactyl\Services\Databases\DatabaseManagementService $databaseManagementService - * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository - * @param \Psr\Log\LoggerInterface $writer + * @param \Illuminate\Database\ConnectionInterface $connection + * @param \Pterodactyl\Repositories\Wings\DaemonServerRepository $daemonServerRepository + * @param \Pterodactyl\Services\Databases\DatabaseManagementService $databaseManagementService */ public function __construct( ConnectionInterface $connection, - DaemonServerRepositoryInterface $daemonServerRepository, - DatabaseRepositoryInterface $databaseRepository, - DatabaseManagementService $databaseManagementService, - ServerRepositoryInterface $repository, - Writer $writer + DaemonServerRepository $daemonServerRepository, + DatabaseManagementService $databaseManagementService ) { - $this->daemonServerRepository = $daemonServerRepository; $this->connection = $connection; + $this->daemonServerRepository = $daemonServerRepository; $this->databaseManagementService = $databaseManagementService; - $this->databaseRepository = $databaseRepository; - $this->repository = $repository; - $this->writer = $writer; } /** @@ -97,34 +66,49 @@ class ServerDeletionService /** * Delete a server from the panel and remove any associated databases from hosts. * - * @param int|\Pterodactyl\Models\Server $server + * @param \Pterodactyl\Models\Server $server * + * @throws \Throwable * @throws \Pterodactyl\Exceptions\DisplayException */ - public function handle($server) + public function handle(Server $server) { try { $this->daemonServerRepository->setServer($server)->delete(); - } catch (RequestException $exception) { - $response = $exception->getResponse(); - - if (is_null($response) || (! is_null($response) && $response->getStatusCode() !== 404)) { - // If not forcing the deletion, throw an exception, otherwise just log it and - // continue with server deletion process in the panel. - if (! $this->force) { - throw new DaemonConnectionException($exception); - } else { - $this->writer->warning($exception); - } + } catch (DaemonConnectionException $exception) { + // If there is an error not caused a 404 error and this isn't a forced delete, + // go ahead and bail out. We specifically ignore a 404 since that can be assumed + // to be a safe error, meaning the server doesn't exist at all on Wings so there + // is no reason we need to bail out from that. + if (! $this->force && $exception->getStatusCode() !== Response::HTTP_NOT_FOUND) { + throw $exception; } + + Log::warning($exception); } - $this->connection->beginTransaction(); - $this->databaseRepository->setColumns('id')->findWhere([['server_id', '=', $server->id]])->each(function ($item) { - $this->databaseManagementService->delete($item->id); - }); + $this->connection->transaction(function () use ($server) { + foreach ($server->databases as $database) { + try { + $this->databaseManagementService->delete($database); + } catch (Exception $exception) { + if (!$this->force) { + throw $exception; + } - $this->repository->delete($server->id); - $this->connection->commit(); + // Oh well, just try to delete the database entry we have from the database + // so that the server itself can be deleted. This will leave it dangling on + // the host instance, but we couldn't delete it anyways so not sure how we would + // handle this better anyways. + // + // @see https://github.com/pterodactyl/panel/issues/2085 + $database->delete(); + + Log::warning($exception); + } + } + + $server->delete(); + }); } } diff --git a/app/Services/Servers/StartupCommandService.php b/app/Services/Servers/StartupCommandService.php new file mode 100644 index 00000000..bf31763c --- /dev/null +++ b/app/Services/Servers/StartupCommandService.php @@ -0,0 +1,28 @@ +memory, $server->allocation->ip, $server->allocation->port]; + + foreach ($server->variables as $variable) { + $find[] = '{{' . $variable->env_variable . '}}'; + $replace[] = ($variable->user_viewable && !$hideAllValues) ? ($variable->server_value ?? $variable->default_value) : '[hidden]'; + } + + return str_replace($find, $replace, $server->startup); + } +} diff --git a/app/Services/Servers/StartupCommandViewService.php b/app/Services/Servers/StartupCommandViewService.php deleted file mode 100644 index d3cda314..00000000 --- a/app/Services/Servers/StartupCommandViewService.php +++ /dev/null @@ -1,56 +0,0 @@ -repository = $repository; - } - - /** - * Generate a startup command for a server and return all of the user-viewable variables - * as well as their assigned values. - * - * @param int $server - * @return \Illuminate\Support\Collection - * - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - */ - public function handle(int $server): Collection - { - $response = $this->repository->getVariablesWithValues($server, true); - $server = $this->repository->getPrimaryAllocation($response->server); - - $find = ['{{SERVER_MEMORY}}', '{{SERVER_IP}}', '{{SERVER_PORT}}']; - $replace = [$server->memory, $server->getRelation('allocation')->ip, $server->getRelation('allocation')->port]; - - $variables = $server->getRelation('egg')->getRelation('variables') - ->each(function ($variable) use (&$find, &$replace, $response) { - $find[] = '{{' . $variable->env_variable . '}}'; - $replace[] = $variable->user_viewable ? $response->data[$variable->env_variable] : '[hidden]'; - })->filter(function ($variable) { - return $variable->user_viewable === 1; - }); - - return collect([ - 'startup' => str_replace($find, $replace, $server->startup), - 'variables' => $variables, - 'server_values' => $response->data, - ]); - } -} diff --git a/app/Services/Servers/StartupModificationService.php b/app/Services/Servers/StartupModificationService.php index 9fa1390d..c409520a 100644 --- a/app/Services/Servers/StartupModificationService.php +++ b/app/Services/Servers/StartupModificationService.php @@ -2,51 +2,23 @@ namespace Pterodactyl\Services\Servers; +use Illuminate\Support\Arr; +use Pterodactyl\Models\Egg; use Pterodactyl\Models\User; use Pterodactyl\Models\Server; -use GuzzleHttp\Exception\RequestException; +use Pterodactyl\Models\ServerVariable; use Illuminate\Database\ConnectionInterface; use Pterodactyl\Traits\Services\HasUserLevels; -use Pterodactyl\Contracts\Repository\EggRepositoryInterface; -use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; -use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException; -use Pterodactyl\Contracts\Repository\ServerVariableRepositoryInterface; -use Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface as DaemonServerRepositoryInterface; class StartupModificationService { use HasUserLevels; - /** - * @var \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface - */ - private $daemonServerRepository; - /** * @var \Illuminate\Database\ConnectionInterface */ private $connection; - /** - * @var \Pterodactyl\Contracts\Repository\EggRepositoryInterface - */ - private $eggRepository; - - /** - * @var \Pterodactyl\Services\Servers\EnvironmentService - */ - private $environmentService; - - /** - * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface - */ - private $repository; - - /** - * @var \Pterodactyl\Contracts\Repository\ServerVariableRepositoryInterface - */ - private $serverVariableRepository; - /** * @var \Pterodactyl\Services\Servers\VariableValidatorService */ @@ -55,29 +27,12 @@ class StartupModificationService /** * StartupModificationService constructor. * - * @param \Illuminate\Database\ConnectionInterface $connection - * @param \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface $daemonServerRepository - * @param \Pterodactyl\Contracts\Repository\EggRepositoryInterface $eggRepository - * @param \Pterodactyl\Services\Servers\EnvironmentService $environmentService - * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository - * @param \Pterodactyl\Contracts\Repository\ServerVariableRepositoryInterface $serverVariableRepository - * @param \Pterodactyl\Services\Servers\VariableValidatorService $validatorService + * @param \Illuminate\Database\ConnectionInterface $connection + * @param \Pterodactyl\Services\Servers\VariableValidatorService $validatorService */ - public function __construct( - ConnectionInterface $connection, - DaemonServerRepositoryInterface $daemonServerRepository, - EggRepositoryInterface $eggRepository, - EnvironmentService $environmentService, - ServerRepositoryInterface $repository, - ServerVariableRepositoryInterface $serverVariableRepository, - VariableValidatorService $validatorService - ) { - $this->daemonServerRepository = $daemonServerRepository; + public function __construct(ConnectionInterface $connection, VariableValidatorService $validatorService) + { $this->connection = $connection; - $this->eggRepository = $eggRepository; - $this->environmentService = $environmentService; - $this->repository = $repository; - $this->serverVariableRepository = $serverVariableRepository; $this->validatorService = $validatorService; } @@ -85,91 +40,72 @@ class StartupModificationService * Process startup modification for a server. * * @param \Pterodactyl\Models\Server $server - * @param array $data + * @param array $data * @return \Pterodactyl\Models\Server * - * @throws \Illuminate\Validation\ValidationException - * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Throwable */ public function handle(Server $server, array $data): Server { - $this->connection->beginTransaction(); - if (! is_null(array_get($data, 'environment'))) { - $this->validatorService->setUserLevel($this->getUserLevel()); - $results = $this->validatorService->handle(array_get($data, 'egg_id', $server->egg_id), array_get($data, 'environment', [])); + return $this->connection->transaction(function () use ($server, $data) { + if (! empty($data['environment'])) { + $egg = $this->isUserLevel(User::USER_LEVEL_ADMIN) ? ($data['egg_id'] ?? $server->egg_id) : $server->egg_id; - $results->each(function ($result) use ($server) { - $this->serverVariableRepository->withoutFreshModel()->updateOrCreate([ - 'server_id' => $server->id, - 'variable_id' => $result->id, - ], [ - 'variable_value' => $result->value ?? '', - ]); - }); - } + $results = $this->validatorService + ->setUserLevel($this->getUserLevel()) + ->handle($egg, $data['environment']); - $daemonData = []; - if ($this->isUserLevel(User::USER_LEVEL_ADMIN)) { - $this->updateAdministrativeSettings($data, $server, $daemonData); - } + foreach ($results as $result) { + ServerVariable::query()->updateOrCreate( + [ + 'server_id' => $server->id, + 'variable_id' => $result->id, + ], + ['variable_value' => $result->value ?? ''] + ); + } + } - $daemonData = array_merge_recursive($daemonData, [ - 'build' => [ - 'env|overwrite' => $this->environmentService->handle($server), - ], - ]); + if ($this->isUserLevel(User::USER_LEVEL_ADMIN)) { + $this->updateAdministrativeSettings($data, $server); + } - try { - $this->daemonServerRepository->setServer($server)->update($daemonData); - } catch (RequestException $exception) { - $this->connection->rollBack(); - throw new DaemonConnectionException($exception); - } - - $this->connection->commit(); - - return $server; + // Calling ->refresh() rather than ->fresh() here causes it to return the + // variables as triplicates for some reason? Not entirely sure, should dig + // in more to figure it out, but luckily we have a test case covering this + // specific call so we can be assured we're not breaking it _here_ at least. + // + // TODO(dane): this seems like a red-flag for the code powering the relationship + // that should be looked into more. + return $server->fresh(); + }); } /** * Update certain administrative settings for a server in the DB. * - * @param array $data + * @param array $data * @param \Pterodactyl\Models\Server $server - * @param array $daemonData - * - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - private function updateAdministrativeSettings(array $data, Server &$server, array &$daemonData) + protected function updateAdministrativeSettings(array $data, Server &$server) { - if ( - is_digit(array_get($data, 'egg_id')) - && $data['egg_id'] != $server->egg_id - && is_null(array_get($data, 'nest_id')) - ) { - $egg = $this->eggRepository->setColumns(['id', 'nest_id'])->find($data['egg_id']); - $data['nest_id'] = $egg->nest_id; + $eggId = Arr::get($data, 'egg_id'); + + if (is_digit($eggId) && $server->egg_id !== (int)$eggId) { + /** @var \Pterodactyl\Models\Egg $egg */ + $egg = Egg::query()->findOrFail($data['egg_id']); + + $server = $server->forceFill([ + 'egg_id' => $egg->id, + 'nest_id' => $egg->nest_id, + ]); } - $server = $this->repository->update($server->id, [ + $server->forceFill([ 'installed' => 0, - 'startup' => array_get($data, 'startup', $server->startup), - 'nest_id' => array_get($data, 'nest_id', $server->nest_id), - 'egg_id' => array_get($data, 'egg_id', $server->egg_id), - 'pack_id' => array_get($data, 'pack_id', $server->pack_id) > 0 ? array_get($data, 'pack_id', $server->pack_id) : null, - 'skip_scripts' => array_get($data, 'skip_scripts') ?? isset($data['skip_scripts']), - 'image' => array_get($data, 'docker_image', $server->image), - ]); - - $daemonData = array_merge($daemonData, [ - 'build' => ['image' => $server->image], - 'service' => array_merge( - $this->repository->getDaemonServiceData($server, true), - ['skip_scripts' => $server->skip_scripts] - ), - ]); + 'startup' => $data['startup'] ?? $server->startup, + 'skip_scripts' => $data['skip_scripts'] ?? isset($data['skip_scripts']), + 'image' => $data['docker_image'] ?? $server->image, + ])->save(); } } diff --git a/app/Services/Servers/SuspensionService.php b/app/Services/Servers/SuspensionService.php index d8d36780..6497d73e 100644 --- a/app/Services/Servers/SuspensionService.php +++ b/app/Services/Servers/SuspensionService.php @@ -1,115 +1,67 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Services\Servers; +use Webmozart\Assert\Assert; use Pterodactyl\Models\Server; -use Psr\Log\LoggerInterface as Writer; -use GuzzleHttp\Exception\RequestException; use Illuminate\Database\ConnectionInterface; -use Pterodactyl\Exceptions\DisplayException; -use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; -use Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface as DaemonServerRepositoryInterface; +use Pterodactyl\Repositories\Wings\DaemonServerRepository; class SuspensionService { const ACTION_SUSPEND = 'suspend'; const ACTION_UNSUSPEND = 'unsuspend'; - /** - * @var \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface - */ - protected $daemonServerRepository; - /** * @var \Illuminate\Database\ConnectionInterface */ - protected $database; + private $connection; /** - * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface + * @var \Pterodactyl\Repositories\Wings\DaemonServerRepository */ - protected $repository; - - /** - * @var \Psr\Log\LoggerInterface - */ - protected $writer; + private $daemonServerRepository; /** * SuspensionService constructor. * - * @param \Illuminate\Database\ConnectionInterface $database - * @param \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface $daemonServerRepository - * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository - * @param \Psr\Log\LoggerInterface $writer + * @param \Illuminate\Database\ConnectionInterface $connection + * @param \Pterodactyl\Repositories\Wings\DaemonServerRepository $daemonServerRepository */ public function __construct( - ConnectionInterface $database, - DaemonServerRepositoryInterface $daemonServerRepository, - ServerRepositoryInterface $repository, - Writer $writer + ConnectionInterface $connection, + DaemonServerRepository $daemonServerRepository ) { + $this->connection = $connection; $this->daemonServerRepository = $daemonServerRepository; - $this->database = $database; - $this->repository = $repository; - $this->writer = $writer; } /** * Suspends a server on the system. * - * @param int|\Pterodactyl\Models\Server $server - * @param string $action - * @return bool + * @param \Pterodactyl\Models\Server $server + * @param string $action * - * @throws \Pterodactyl\Exceptions\DisplayException - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Throwable */ - public function toggle($server, $action = self::ACTION_SUSPEND) + public function toggle(Server $server, $action = self::ACTION_SUSPEND) { - if (! $server instanceof Server) { - $server = $this->repository->find($server); + Assert::oneOf($action, [self::ACTION_SUSPEND, self::ACTION_UNSUSPEND]); + + $isSuspending = $action === self::ACTION_SUSPEND; + // Nothing needs to happen if we're suspending the server and it is already + // suspended in the database. Additionally, nothing needs to happen if the server + // is not suspended and we try to un-suspend the instance. + if ($isSuspending === $server->suspended) { + return; } - if (! in_array($action, [self::ACTION_SUSPEND, self::ACTION_UNSUSPEND])) { - throw new \InvalidArgumentException(sprintf( - 'Action must be either ' . self::ACTION_SUSPEND . ' or ' . self::ACTION_UNSUSPEND . ', %s passed.', - $action - )); - } + $this->connection->transaction(function () use ($action, $server) { + $server->update([ + 'suspended' => $action === self::ACTION_SUSPEND, + ]); - if ( - $action === self::ACTION_SUSPEND && $server->suspended || - $action === self::ACTION_UNSUSPEND && ! $server->suspended - ) { - return true; - } - - $this->database->beginTransaction(); - $this->repository->withoutFreshModel()->update($server->id, [ - 'suspended' => $action === self::ACTION_SUSPEND, - ]); - - try { - $this->daemonServerRepository->setServer($server)->$action(); - $this->database->commit(); - - return true; - } catch (RequestException $exception) { - $response = $exception->getResponse(); - $this->writer->warning($exception); - - throw new DisplayException(trans('admin/server.exceptions.daemon_exception', [ - 'code' => is_null($response) ? 'E_CONN_REFUSED' : $response->getStatusCode(), - ])); - } + $this->daemonServerRepository->setServer($server)->suspend($action === self::ACTION_UNSUSPEND); + }); } } diff --git a/app/Services/Servers/TransferService.php b/app/Services/Servers/TransferService.php new file mode 100644 index 00000000..b45a78dc --- /dev/null +++ b/app/Services/Servers/TransferService.php @@ -0,0 +1,46 @@ +repository = $repository; + $this->daemonServerRepository = $daemonServerRepository; + } + + /** + * Requests an archive from the daemon. + * + * @param int|\Pterodactyl\Models\Server $server + * + * @throws \Throwable + */ + public function requestArchive(Server $server) + { + $this->daemonServerRepository->setServer($server)->requestArchive(); + } +} diff --git a/app/Services/Servers/VariableValidatorService.php b/app/Services/Servers/VariableValidatorService.php index 75846055..7cb1aa42 100644 --- a/app/Services/Servers/VariableValidatorService.php +++ b/app/Services/Servers/VariableValidatorService.php @@ -11,32 +11,15 @@ namespace Pterodactyl\Services\Servers; use Pterodactyl\Models\User; use Illuminate\Support\Collection; +use Pterodactyl\Models\EggVariable; use Illuminate\Validation\ValidationException; use Pterodactyl\Traits\Services\HasUserLevels; -use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; use Illuminate\Contracts\Validation\Factory as ValidationFactory; -use Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface; -use Pterodactyl\Contracts\Repository\ServerVariableRepositoryInterface; class VariableValidatorService { use HasUserLevels; - /** - * @var \Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface - */ - private $optionVariableRepository; - - /** - * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface - */ - private $serverRepository; - - /** - * @var \Pterodactyl\Contracts\Repository\ServerVariableRepositoryInterface - */ - private $serverVariableRepository; - /** * @var \Illuminate\Contracts\Validation\Factory */ @@ -45,43 +28,35 @@ class VariableValidatorService /** * VariableValidatorService constructor. * - * @param \Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface $optionVariableRepository - * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $serverRepository - * @param \Pterodactyl\Contracts\Repository\ServerVariableRepositoryInterface $serverVariableRepository - * @param \Illuminate\Contracts\Validation\Factory $validator + * @param \Illuminate\Contracts\Validation\Factory $validator */ - public function __construct( - EggVariableRepositoryInterface $optionVariableRepository, - ServerRepositoryInterface $serverRepository, - ServerVariableRepositoryInterface $serverVariableRepository, - ValidationFactory $validator - ) { - $this->optionVariableRepository = $optionVariableRepository; - $this->serverRepository = $serverRepository; - $this->serverVariableRepository = $serverVariableRepository; + public function __construct(ValidationFactory $validator) + { $this->validator = $validator; } /** * Validate all of the passed data against the given service option variables. * - * @param int $egg + * @param int $egg * @param array $fields * @return \Illuminate\Support\Collection * @throws \Illuminate\Validation\ValidationException */ public function handle(int $egg, array $fields = []): Collection { - $variables = $this->optionVariableRepository->findWhere([['egg_id', '=', $egg]]); + $query = EggVariable::query()->where('egg_id', $egg); + if (! $this->isUserLevel(User::USER_LEVEL_ADMIN)) { + // Don't attempt to validate variables if they aren't user editable + // and we're not running this at an admin level. + $query = $query->where('user_editable', true)->where('user_viewable', true); + } + + /** @var \Pterodactyl\Models\EggVariable[] $variables */ + $variables = $query->get(); $data = $rules = $customAttributes = []; foreach ($variables as $variable) { - // Don't attempt to validate variables if they aren't user editable - // and we're not running this at an admin level. - if (! $variable->user_editable && ! $this->isUserLevel(User::USER_LEVEL_ADMIN)) { - continue; - } - $data['environment'][$variable->env_variable] = array_get($fields, $variable->env_variable); $rules['environment.' . $variable->env_variable] = $variable->rules; $customAttributes['environment.' . $variable->env_variable] = trans('validation.internal.variable_value', ['env' => $variable->name]); @@ -92,23 +67,12 @@ class VariableValidatorService throw new ValidationException($validator); } - $response = $variables->filter(function ($item) { - // Skip doing anything if user is not an admin and variable is not user viewable or editable. - if (! $this->isUserLevel(User::USER_LEVEL_ADMIN) && (! $item->user_editable || ! $item->user_viewable)) { - return false; - } - - return true; - })->map(function ($item) use ($fields) { - return (object) [ + return Collection::make($variables)->map(function ($item) use ($fields) { + return (object)[ 'id' => $item->id, 'key' => $item->env_variable, - 'value' => array_get($fields, $item->env_variable), + 'value' => $fields[$item->env_variable] ?? null, ]; - })->filter(function ($item) { - return is_object($item); }); - - return $response; } } diff --git a/app/Services/Sftp/AuthenticateUsingPasswordService.php b/app/Services/Sftp/AuthenticateUsingPasswordService.php deleted file mode 100644 index 4310a50f..00000000 --- a/app/Services/Sftp/AuthenticateUsingPasswordService.php +++ /dev/null @@ -1,108 +0,0 @@ -keyProviderService = $keyProviderService; - $this->repository = $repository; - $this->subuserRepository = $subuserRepository; - $this->userRepository = $userRepository; - } - - /** - * Attempt to authenticate a provided username and password and determine if they - * have permission to access a given server. This function does not account for - * subusers currently. Only administrators and server owners can login to access - * their files at this time. - * - * Server must exist on the node that the API call is being made from in order for a - * valid response to be provided. - * - * @param string $username - * @param string $password - * @param int $node - * @param string|null $server - * @return array - * - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - * @throws \Symfony\Component\HttpKernel\Exception\BadRequestHttpException - */ - public function handle(string $username, string $password, int $node, string $server = null): array - { - if (is_null($server)) { - throw new RecordNotFoundException; - } - - $user = $this->userRepository->setColumns(['id', 'root_admin', 'password'])->findFirstWhere([['username', '=', $username]]); - if (! password_verify($password, $user->password)) { - throw new RecordNotFoundException; - } - - $server = $this->repository->setColumns(['id', 'node_id', 'owner_id', 'uuid', 'installed', 'suspended'])->getByUuid($server); - if ($server->node_id !== $node) { - throw new RecordNotFoundException; - } - - if (! $user->root_admin && $server->owner_id !== $user->id) { - $subuser = $this->subuserRepository->getWithPermissionsUsingUserAndServer($user->id, $server->id); - $permissions = $subuser->getRelation('permissions')->pluck('permission')->toArray(); - - if (! in_array('access-sftp', $permissions)) { - throw new RecordNotFoundException; - } - } - - if ($server->installed !== 1 || $server->suspended) { - throw new BadRequestHttpException; - } - - return [ - 'server' => $server->uuid, - 'token' => $this->keyProviderService->handle($server, $user), - 'permissions' => $permissions ?? ['*'], - ]; - } -} diff --git a/app/Services/Subusers/PermissionCreationService.php b/app/Services/Subusers/PermissionCreationService.php deleted file mode 100644 index 074048f0..00000000 --- a/app/Services/Subusers/PermissionCreationService.php +++ /dev/null @@ -1,63 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -namespace Pterodactyl\Services\Subusers; - -use Webmozart\Assert\Assert; -use Pterodactyl\Models\Permission; -use Pterodactyl\Contracts\Repository\PermissionRepositoryInterface; - -class PermissionCreationService -{ - /** - * @var \Pterodactyl\Contracts\Repository\PermissionRepositoryInterface - */ - protected $repository; - - /** - * PermissionCreationService constructor. - * - * @param \Pterodactyl\Contracts\Repository\PermissionRepositoryInterface $repository - */ - public function __construct(PermissionRepositoryInterface $repository) - { - $this->repository = $repository; - } - - /** - * Assign permissions to a given subuser. - * - * @param int $subuser - * @param array $permissions - * - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - */ - public function handle($subuser, array $permissions) - { - Assert::integerish($subuser, 'First argument passed to handle must be an integer, received %s.'); - - $permissionMappings = Permission::getPermissions(true); - $insertPermissions = []; - - foreach ($permissions as $permission) { - if (array_key_exists($permission, $permissionMappings)) { - Assert::stringNotEmpty($permission, 'Permission argument provided must be a non-empty string, received %s.'); - - array_push($insertPermissions, [ - 'subuser_id' => $subuser, - 'permission' => $permission, - ]); - } - } - - if (! empty($insertPermissions)) { - $this->repository->withoutFreshModel()->insert($insertPermissions); - } - } -} diff --git a/app/Services/Subusers/SubuserCreationService.php b/app/Services/Subusers/SubuserCreationService.php index 1a5d7120..5efc700f 100644 --- a/app/Services/Subusers/SubuserCreationService.php +++ b/app/Services/Subusers/SubuserCreationService.php @@ -1,22 +1,15 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Services\Subusers; +use Illuminate\Support\Str; use Pterodactyl\Models\Server; +use Pterodactyl\Models\Subuser; use Illuminate\Database\ConnectionInterface; use Pterodactyl\Services\Users\UserCreationService; +use Pterodactyl\Repositories\Eloquent\SubuserRepository; use Pterodactyl\Contracts\Repository\UserRepositoryInterface; -use Pterodactyl\Services\DaemonKeys\DaemonKeyCreationService; use Pterodactyl\Exceptions\Repository\RecordNotFoundException; -use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; -use Pterodactyl\Contracts\Repository\SubuserRepositoryInterface; use Pterodactyl\Exceptions\Service\Subuser\UserIsServerOwnerException; use Pterodactyl\Exceptions\Service\Subuser\ServerSubuserExistsException; @@ -25,113 +18,91 @@ class SubuserCreationService /** * @var \Illuminate\Database\ConnectionInterface */ - protected $connection; + private $connection; /** - * @var \Pterodactyl\Services\DaemonKeys\DaemonKeyCreationService + * @var \Pterodactyl\Repositories\Eloquent\SubuserRepository */ - protected $keyCreationService; - - /** - * @var \Pterodactyl\Services\Subusers\PermissionCreationService - */ - protected $permissionService; - - /** - * @var \Pterodactyl\Contracts\Repository\SubuserRepositoryInterface - */ - protected $subuserRepository; - - /** - * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface - */ - protected $serverRepository; + private $subuserRepository; /** * @var \Pterodactyl\Services\Users\UserCreationService */ - protected $userCreationService; + private $userCreationService; /** * @var \Pterodactyl\Contracts\Repository\UserRepositoryInterface */ - protected $userRepository; + private $userRepository; /** * SubuserCreationService constructor. * - * @param \Illuminate\Database\ConnectionInterface $connection - * @param \Pterodactyl\Services\DaemonKeys\DaemonKeyCreationService $keyCreationService - * @param \Pterodactyl\Services\Subusers\PermissionCreationService $permissionService - * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $serverRepository - * @param \Pterodactyl\Contracts\Repository\SubuserRepositoryInterface $subuserRepository - * @param \Pterodactyl\Services\Users\UserCreationService $userCreationService - * @param \Pterodactyl\Contracts\Repository\UserRepositoryInterface $userRepository + * @param \Illuminate\Database\ConnectionInterface $connection + * @param \Pterodactyl\Repositories\Eloquent\SubuserRepository $subuserRepository + * @param \Pterodactyl\Services\Users\UserCreationService $userCreationService + * @param \Pterodactyl\Contracts\Repository\UserRepositoryInterface $userRepository */ public function __construct( ConnectionInterface $connection, - DaemonKeyCreationService $keyCreationService, - PermissionCreationService $permissionService, - ServerRepositoryInterface $serverRepository, - SubuserRepositoryInterface $subuserRepository, + SubuserRepository $subuserRepository, UserCreationService $userCreationService, UserRepositoryInterface $userRepository ) { $this->connection = $connection; - $this->keyCreationService = $keyCreationService; - $this->permissionService = $permissionService; - $this->serverRepository = $serverRepository; $this->subuserRepository = $subuserRepository; $this->userRepository = $userRepository; $this->userCreationService = $userCreationService; } /** - * @param int|\Pterodactyl\Models\Server $server - * @param string $email - * @param array $permissions + * Creates a new user on the system and assigns them access to the provided server. + * If the email address already belongs to a user on the system a new user will not + * be created. + * + * @param \Pterodactyl\Models\Server $server + * @param string $email + * @param array $permissions * @return \Pterodactyl\Models\Subuser * - * @throws \Exception * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException * @throws \Pterodactyl\Exceptions\Service\Subuser\ServerSubuserExistsException * @throws \Pterodactyl\Exceptions\Service\Subuser\UserIsServerOwnerException + * @throws \Throwable */ - public function handle($server, $email, array $permissions) + public function handle(Server $server, string $email, array $permissions): Subuser { - if (! $server instanceof Server) { - $server = $this->serverRepository->find($server); - } + return $this->connection->transaction(function () use ($server, $email, $permissions) { + try { + $user = $this->userRepository->findFirstWhere([['email', '=', $email]]); - $this->connection->beginTransaction(); - try { - $user = $this->userRepository->findFirstWhere([['email', '=', $email]]); + if ($server->owner_id === $user->id) { + throw new UserIsServerOwnerException(trans('exceptions.subusers.user_is_owner')); + } - if ($server->owner_id === $user->id) { - throw new UserIsServerOwnerException(trans('exceptions.subusers.user_is_owner')); + $subuserCount = $this->subuserRepository->findCountWhere([['user_id', '=', $user->id], ['server_id', '=', $server->id]]); + if ($subuserCount !== 0) { + throw new ServerSubuserExistsException(trans('exceptions.subusers.subuser_exists')); + } + } catch (RecordNotFoundException $exception) { + // Just cap the username generated at 64 characters at most and then append a random string + // to the end to make it "unique"... + $username = substr(preg_replace('/([^\w\.-]+)/', '', strtok($email, '@')), 0, 64) . Str::random(3); + + $user = $this->userCreationService->handle([ + 'email' => $email, + 'username' => $username, + 'name_first' => 'Server', + 'name_last' => 'Subuser', + 'root_admin' => false, + ]); } - $subuserCount = $this->subuserRepository->findCountWhere([['user_id', '=', $user->id], ['server_id', '=', $server->id]]); - if ($subuserCount !== 0) { - throw new ServerSubuserExistsException(trans('exceptions.subusers.subuser_exists')); - } - } catch (RecordNotFoundException $exception) { - $username = preg_replace('/([^\w\.-]+)/', '', strtok($email, '@')); - $user = $this->userCreationService->handle([ - 'email' => $email, - 'username' => $username . str_random(3), - 'name_first' => 'Server', - 'name_last' => 'Subuser', - 'root_admin' => false, + return $this->subuserRepository->create([ + 'user_id' => $user->id, + 'server_id' => $server->id, + 'permissions' => array_unique($permissions), ]); - } - - $subuser = $this->subuserRepository->create(['user_id' => $user->id, 'server_id' => $server->id]); - $this->keyCreationService->handle($server->id, $user->id); - $this->permissionService->handle($subuser->id, $permissions); - $this->connection->commit(); - - return $subuser; + }); } } diff --git a/app/Services/Subusers/SubuserDeletionService.php b/app/Services/Subusers/SubuserDeletionService.php deleted file mode 100644 index 6b100cd1..00000000 --- a/app/Services/Subusers/SubuserDeletionService.php +++ /dev/null @@ -1,66 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -namespace Pterodactyl\Services\Subusers; - -use Pterodactyl\Models\Subuser; -use Illuminate\Database\ConnectionInterface; -use Pterodactyl\Services\DaemonKeys\DaemonKeyDeletionService; -use Pterodactyl\Contracts\Repository\SubuserRepositoryInterface; - -class SubuserDeletionService -{ - /** - * @var \Illuminate\Database\ConnectionInterface - */ - private $connection; - - /** - * @var \Pterodactyl\Services\DaemonKeys\DaemonKeyDeletionService - */ - private $keyDeletionService; - - /** - * @var \Pterodactyl\Contracts\Repository\SubuserRepositoryInterface - */ - private $repository; - - /** - * SubuserDeletionService constructor. - * - * @param \Illuminate\Database\ConnectionInterface $connection - * @param \Pterodactyl\Services\DaemonKeys\DaemonKeyDeletionService $keyDeletionService - * @param \Pterodactyl\Contracts\Repository\SubuserRepositoryInterface $repository - */ - public function __construct( - ConnectionInterface $connection, - DaemonKeyDeletionService $keyDeletionService, - SubuserRepositoryInterface $repository - ) { - $this->connection = $connection; - $this->keyDeletionService = $keyDeletionService; - $this->repository = $repository; - } - - /** - * Delete a subuser and their associated permissions from the Panel and Daemon. - * - * @param \Pterodactyl\Models\Subuser $subuser - * - * @throws \Pterodactyl\Exceptions\DisplayException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - */ - public function handle(Subuser $subuser) - { - $this->connection->beginTransaction(); - $this->keyDeletionService->handle($subuser->server_id, $subuser->user_id); - $this->repository->delete($subuser->id); - $this->connection->commit(); - } -} diff --git a/app/Services/Subusers/SubuserUpdateService.php b/app/Services/Subusers/SubuserUpdateService.php deleted file mode 100644 index f56e47b9..00000000 --- a/app/Services/Subusers/SubuserUpdateService.php +++ /dev/null @@ -1,107 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -namespace Pterodactyl\Services\Subusers; - -use Pterodactyl\Models\Subuser; -use GuzzleHttp\Exception\RequestException; -use Illuminate\Database\ConnectionInterface; -use Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService; -use Pterodactyl\Contracts\Repository\SubuserRepositoryInterface; -use Pterodactyl\Contracts\Repository\PermissionRepositoryInterface; -use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException; -use Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface as DaemonServerRepositoryInterface; - -class SubuserUpdateService -{ - /** - * @var \Illuminate\Database\ConnectionInterface - */ - private $connection; - - /** - * @var \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface - */ - private $daemonRepository; - - /** - * @var \Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService - */ - private $keyProviderService; - - /** - * @var \Pterodactyl\Contracts\Repository\PermissionRepositoryInterface - */ - private $permissionRepository; - - /** - * @var \Pterodactyl\Services\Subusers\PermissionCreationService - */ - private $permissionService; - - /** - * @var \Pterodactyl\Contracts\Repository\SubuserRepositoryInterface - */ - private $repository; - - /** - * SubuserUpdateService constructor. - * - * @param \Illuminate\Database\ConnectionInterface $connection - * @param \Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService $keyProviderService - * @param \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface $daemonRepository - * @param \Pterodactyl\Services\Subusers\PermissionCreationService $permissionService - * @param \Pterodactyl\Contracts\Repository\PermissionRepositoryInterface $permissionRepository - * @param \Pterodactyl\Contracts\Repository\SubuserRepositoryInterface $repository - */ - public function __construct( - ConnectionInterface $connection, - DaemonKeyProviderService $keyProviderService, - DaemonServerRepositoryInterface $daemonRepository, - PermissionCreationService $permissionService, - PermissionRepositoryInterface $permissionRepository, - SubuserRepositoryInterface $repository - ) { - $this->connection = $connection; - $this->daemonRepository = $daemonRepository; - $this->keyProviderService = $keyProviderService; - $this->permissionRepository = $permissionRepository; - $this->permissionService = $permissionService; - $this->repository = $repository; - } - - /** - * Update permissions for a given subuser. - * - * @param \Pterodactyl\Models\Subuser $subuser - * @param array $permissions - * - * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - */ - public function handle(Subuser $subuser, array $permissions) - { - $subuser = $this->repository->loadServerAndUserRelations($subuser); - - $this->connection->beginTransaction(); - $this->permissionRepository->deleteWhere([['subuser_id', '=', $subuser->id]]); - $this->permissionService->handle($subuser->id, $permissions); - - try { - $token = $this->keyProviderService->handle($subuser->getRelation('server'), $subuser->getRelation('user'), false); - $this->daemonRepository->setServer($subuser->getRelation('server'))->revokeAccessKey($token); - } catch (RequestException $exception) { - $this->connection->rollBack(); - throw new DaemonConnectionException($exception); - } - - $this->connection->commit(); - } -} diff --git a/app/Services/Users/ToggleTwoFactorService.php b/app/Services/Users/ToggleTwoFactorService.php index 6fe8bc9d..f8b41b45 100644 --- a/app/Services/Users/ToggleTwoFactorService.php +++ b/app/Services/Users/ToggleTwoFactorService.php @@ -3,20 +3,17 @@ namespace Pterodactyl\Services\Users; use Carbon\Carbon; +use Illuminate\Support\Str; use Pterodactyl\Models\User; use PragmaRX\Google2FA\Google2FA; -use Illuminate\Contracts\Config\Repository; +use Illuminate\Database\ConnectionInterface; use Illuminate\Contracts\Encryption\Encrypter; use Pterodactyl\Contracts\Repository\UserRepositoryInterface; +use Pterodactyl\Repositories\Eloquent\RecoveryTokenRepository; use Pterodactyl\Exceptions\Service\User\TwoFactorAuthenticationTokenInvalid; class ToggleTwoFactorService { - /** - * @var \Illuminate\Contracts\Config\Repository - */ - private $config; - /** * @var \Illuminate\Contracts\Encryption\Encrypter */ @@ -32,54 +29,99 @@ class ToggleTwoFactorService */ private $repository; + /** + * @var \Pterodactyl\Repositories\Eloquent\RecoveryTokenRepository + */ + private $recoveryTokenRepository; + + /** + * @var \Illuminate\Database\ConnectionInterface + */ + private $connection; + /** * ToggleTwoFactorService constructor. * - * @param \Illuminate\Contracts\Encryption\Encrypter $encrypter - * @param \PragmaRX\Google2FA\Google2FA $google2FA - * @param \Illuminate\Contracts\Config\Repository $config + * @param \Illuminate\Database\ConnectionInterface $connection + * @param \Illuminate\Contracts\Encryption\Encrypter $encrypter + * @param \PragmaRX\Google2FA\Google2FA $google2FA + * @param \Pterodactyl\Repositories\Eloquent\RecoveryTokenRepository $recoveryTokenRepository * @param \Pterodactyl\Contracts\Repository\UserRepositoryInterface $repository */ public function __construct( + ConnectionInterface $connection, Encrypter $encrypter, Google2FA $google2FA, - Repository $config, + RecoveryTokenRepository $recoveryTokenRepository, UserRepositoryInterface $repository ) { - $this->config = $config; $this->encrypter = $encrypter; $this->google2FA = $google2FA; $this->repository = $repository; + $this->recoveryTokenRepository = $recoveryTokenRepository; + $this->connection = $connection; } /** * Toggle 2FA on an account only if the token provided is valid. * * @param \Pterodactyl\Models\User $user - * @param string $token - * @param bool|null $toggleState - * @return bool + * @param string $token + * @param bool|null $toggleState + * @return string[] * - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Throwable + * @throws \PragmaRX\Google2FA\Exceptions\IncompatibleWithGoogleAuthenticatorException + * @throws \PragmaRX\Google2FA\Exceptions\InvalidCharactersException + * @throws \PragmaRX\Google2FA\Exceptions\SecretKeyTooShortException * @throws \Pterodactyl\Exceptions\Service\User\TwoFactorAuthenticationTokenInvalid */ - public function handle(User $user, string $token, bool $toggleState = null): bool + public function handle(User $user, string $token, bool $toggleState = null): array { - $window = $this->config->get('pterodactyl.auth.2fa.window'); $secret = $this->encrypter->decrypt($user->totp_secret); - $isValidToken = $this->google2FA->verifyKey($secret, $token, $window); + $isValidToken = $this->google2FA->verifyKey($secret, $token, config()->get('pterodactyl.auth.2fa.window')); if (! $isValidToken) { - throw new TwoFactorAuthenticationTokenInvalid; + throw new TwoFactorAuthenticationTokenInvalid('The token provided is not valid.'); } - $this->repository->withoutFreshModel()->update($user->id, [ - 'totp_authenticated_at' => Carbon::now(), - 'use_totp' => (is_null($toggleState) ? ! $user->use_totp : $toggleState), - ]); + return $this->connection->transaction(function () use ($user, $toggleState) { + // Now that we're enabling 2FA on the account, generate 10 recovery tokens for the account + // and store them hashed in the database. We'll return them to the caller so that the user + // can see and save them. + // + // If a user is unable to login with a 2FA token they can provide one of these backup codes + // which will then be marked as deleted from the database and will also bypass 2FA protections + // on their account. + $tokens = []; + if ((! $toggleState && ! $user->use_totp) || $toggleState) { + $inserts = []; + for ($i = 0; $i < 10; $i++) { + $token = Str::random(10); - return true; + $inserts[] = [ + 'user_id' => $user->id, + 'token' => password_hash($token, PASSWORD_DEFAULT), + ]; + + $tokens[] = $token; + } + + // Before inserting any new records make sure all of the old ones are deleted to avoid + // any issues or storing an unnecessary number of tokens in the database. + $this->recoveryTokenRepository->deleteWhere(['user_id' => $user->id]); + + // Bulk insert the hashed tokens. + $this->recoveryTokenRepository->insert($inserts); + } + + $this->repository->withoutFreshModel()->update($user->id, [ + 'totp_authenticated_at' => Carbon::now(), + 'use_totp' => (is_null($toggleState) ? ! $user->use_totp : $toggleState), + ]); + + return $tokens; + }); } } diff --git a/app/Services/Users/TwoFactorSetupService.php b/app/Services/Users/TwoFactorSetupService.php index 4f2389b1..5dcb7879 100644 --- a/app/Services/Users/TwoFactorSetupService.php +++ b/app/Services/Users/TwoFactorSetupService.php @@ -31,8 +31,8 @@ class TwoFactorSetupService /** * TwoFactorSetupService constructor. * - * @param \Illuminate\Contracts\Config\Repository $config - * @param \Illuminate\Contracts\Encryption\Encrypter $encrypter + * @param \Illuminate\Contracts\Config\Repository $config + * @param \Illuminate\Contracts\Encryption\Encrypter $encrypter * @param \Pterodactyl\Contracts\Repository\UserRepositoryInterface $repository */ public function __construct( @@ -71,7 +71,7 @@ class TwoFactorSetupService 'totp_secret' => $this->encrypter->encrypt($secret), ]); - $company = preg_replace('/\s/', '', $this->config->get('app.name')); + $company = urlencode(preg_replace('/\s/', '', $this->config->get('app.name'))); return sprintf( 'otpauth://totp/%1$s:%2$s?secret=%3$s&issuer=%1$s', diff --git a/app/Services/Users/UserCreationService.php b/app/Services/Users/UserCreationService.php index b54c4f2d..5bc56dd4 100644 --- a/app/Services/Users/UserCreationService.php +++ b/app/Services/Users/UserCreationService.php @@ -34,9 +34,9 @@ class UserCreationService /** * CreationService constructor. * - * @param \Illuminate\Database\ConnectionInterface $connection - * @param \Illuminate\Contracts\Hashing\Hasher $hasher - * @param \Illuminate\Contracts\Auth\PasswordBroker $passwordBroker + * @param \Illuminate\Database\ConnectionInterface $connection + * @param \Illuminate\Contracts\Hashing\Hasher $hasher + * @param \Illuminate\Contracts\Auth\PasswordBroker $passwordBroker * @param \Pterodactyl\Contracts\Repository\UserRepositoryInterface $repository */ public function __construct( diff --git a/app/Services/Users/UserDeletionService.php b/app/Services/Users/UserDeletionService.php index 942e76fa..fd519682 100644 --- a/app/Services/Users/UserDeletionService.php +++ b/app/Services/Users/UserDeletionService.php @@ -36,8 +36,8 @@ class UserDeletionService * DeletionService constructor. * * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $serverRepository - * @param \Illuminate\Contracts\Translation\Translator $translator - * @param \Pterodactyl\Contracts\Repository\UserRepositoryInterface $repository + * @param \Illuminate\Contracts\Translation\Translator $translator + * @param \Pterodactyl\Contracts\Repository\UserRepositoryInterface $repository */ public function __construct( ServerRepositoryInterface $serverRepository, diff --git a/app/Services/Users/UserUpdateService.php b/app/Services/Users/UserUpdateService.php index 440f8b45..4e1911a3 100644 --- a/app/Services/Users/UserUpdateService.php +++ b/app/Services/Users/UserUpdateService.php @@ -3,11 +3,9 @@ namespace Pterodactyl\Services\Users; use Pterodactyl\Models\User; -use Illuminate\Support\Collection; use Illuminate\Contracts\Hashing\Hasher; use Pterodactyl\Traits\Services\HasUserLevels; -use Pterodactyl\Contracts\Repository\UserRepositoryInterface; -use Pterodactyl\Services\DaemonKeys\RevokeMultipleDaemonKeysService; +use Pterodactyl\Repositories\Eloquent\UserRepository; class UserUpdateService { @@ -19,44 +17,33 @@ class UserUpdateService private $hasher; /** - * @var \Pterodactyl\Contracts\Repository\UserRepositoryInterface + * @var \Pterodactyl\Repositories\Eloquent\UserRepository */ private $repository; - /** - * @var \Pterodactyl\Services\DaemonKeys\RevokeMultipleDaemonKeysService - */ - private $revocationService; - /** * UpdateService constructor. * - * @param \Illuminate\Contracts\Hashing\Hasher $hasher - * @param \Pterodactyl\Services\DaemonKeys\RevokeMultipleDaemonKeysService $revocationService - * @param \Pterodactyl\Contracts\Repository\UserRepositoryInterface $repository + * @param \Illuminate\Contracts\Hashing\Hasher $hasher + * @param \Pterodactyl\Repositories\Eloquent\UserRepository $repository */ - public function __construct( - Hasher $hasher, - RevokeMultipleDaemonKeysService $revocationService, - UserRepositoryInterface $repository - ) { + public function __construct(Hasher $hasher, UserRepository $repository) + { $this->hasher = $hasher; $this->repository = $repository; - $this->revocationService = $revocationService; } /** - * Update the user model instance. If the user has been removed as an administrator - * revoke all of the authentication tokens that have been assigned to their account. + * Update the user model instance. * * @param \Pterodactyl\Models\User $user - * @param array $data - * @return \Illuminate\Support\Collection + * @param array $data + * @return \Pterodactyl\Models\User * * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function handle(User $user, array $data): Collection + public function handle(User $user, array $data) { if (! empty(array_get($data, 'password'))) { $data['password'] = $this->hasher->make($data['password']); @@ -64,17 +51,9 @@ class UserUpdateService unset($data['password']); } - if ($this->isUserLevel(User::USER_LEVEL_ADMIN)) { - if (array_get($data, 'root_admin', 0) == 0 && $user->root_admin) { - $this->revocationService->handle($user, array_get($data, 'ignore_connection_error', false)); - } - } else { - unset($data['root_admin']); - } + /** @var \Pterodactyl\Models\User $response */ + $response = $this->repository->update($user->id, $data); - return collect([ - 'model' => $this->repository->update($user->id, $data), - 'exceptions' => $this->revocationService->getExceptions(), - ]); + return $response; } } diff --git a/app/Traits/Commands/EnvironmentWriterTrait.php b/app/Traits/Commands/EnvironmentWriterTrait.php index bc4d2486..6726e931 100644 --- a/app/Traits/Commands/EnvironmentWriterTrait.php +++ b/app/Traits/Commands/EnvironmentWriterTrait.php @@ -30,7 +30,10 @@ trait EnvironmentWriterTrait $saveContents = file_get_contents($path); collect($values)->each(function ($value, $key) use (&$saveContents) { $key = strtoupper($key); - if (str_contains($value, ' ') && ! preg_match('/\"(.*)\"/', $value)) { + // If the key value is not sorrounded by quotation marks, and contains anything that could reasonably + // cause environment parsing issues, wrap it in quotes before writing it. This also adds slashes to the + // value to ensure quotes within it don't cause us issues. + if (! preg_match('/^\"(.*)\"$/', $value) && preg_match('/([^\w.\-+\/])+/', $value)) { $value = sprintf('"%s"', addslashes($value)); } diff --git a/app/Traits/Controllers/JavascriptInjection.php b/app/Traits/Controllers/JavascriptInjection.php index bbda917d..556c9955 100644 --- a/app/Traits/Controllers/JavascriptInjection.php +++ b/app/Traits/Controllers/JavascriptInjection.php @@ -33,32 +33,13 @@ trait JavascriptInjection } /** - * Injects server javascript into the page to be used by other services. + * Injects the exact array passed in, nothing more. * * @param array $args - * @param bool $overwrite * @return array */ - public function injectJavascript($args = [], $overwrite = false) + public function plainInject($args = []) { - $request = $this->request ?? app()->make(Request::class); - $server = $request->attributes->get('server'); - $token = $request->attributes->get('server_token'); - - $response = array_merge_recursive([ - 'server' => [ - 'uuid' => $server->uuid, - 'uuidShort' => $server->uuidShort, - 'daemonSecret' => $token, - ], - 'server_token' => $token, - 'node' => [ - 'fqdn' => $server->node->fqdn, - 'scheme' => $server->node->scheme, - 'daemonListen' => $server->node->daemonListen, - ], - ], $args); - - return Javascript::put($overwrite ? $args : $response); + return Javascript::put($args); } } diff --git a/app/Transformers/Api/Application/AllocationTransformer.php b/app/Transformers/Api/Application/AllocationTransformer.php index f1a35f4f..d0c71e63 100644 --- a/app/Transformers/Api/Application/AllocationTransformer.php +++ b/app/Transformers/Api/Application/AllocationTransformer.php @@ -2,6 +2,8 @@ namespace Pterodactyl\Transformers\Api\Application; +use Pterodactyl\Models\Node; +use Pterodactyl\Models\Server; use Pterodactyl\Models\Allocation; use Pterodactyl\Services\Acl\Api\AdminAcl; @@ -37,6 +39,7 @@ class AllocationTransformer extends BaseTransformer 'ip' => $allocation->ip, 'alias' => $allocation->ip_alias, 'port' => $allocation->port, + 'notes' => $allocation->notes, 'assigned' => ! is_null($allocation->server_id), ]; } @@ -54,10 +57,8 @@ class AllocationTransformer extends BaseTransformer return $this->null(); } - $allocation->loadMissing('node'); - return $this->item( - $allocation->getRelation('node'), $this->makeTransformer(NodeTransformer::class), 'node' + $allocation->node, $this->makeTransformer(NodeTransformer::class), Node::RESOURCE_NAME ); } @@ -70,14 +71,12 @@ class AllocationTransformer extends BaseTransformer */ public function includeServer(Allocation $allocation) { - if (! $this->authorize(AdminAcl::RESOURCE_SERVERS)) { + if (! $this->authorize(AdminAcl::RESOURCE_SERVERS) || ! $allocation->server) { return $this->null(); } - $allocation->loadMissing('server'); - return $this->item( - $allocation->getRelation('server'), $this->makeTransformer(ServerTransformer::class), 'server' + $allocation->server, $this->makeTransformer(ServerTransformer::class), Server::RESOURCE_NAME ); } } diff --git a/app/Transformers/Api/Application/BaseTransformer.php b/app/Transformers/Api/Application/BaseTransformer.php index be5e367f..563dd086 100644 --- a/app/Transformers/Api/Application/BaseTransformer.php +++ b/app/Transformers/Api/Application/BaseTransformer.php @@ -8,7 +8,6 @@ use Illuminate\Container\Container; use Illuminate\Database\Eloquent\Model; use League\Fractal\TransformerAbstract; use Pterodactyl\Services\Acl\Api\AdminAcl; -use Pterodactyl\Transformers\Api\Client\BaseClientTransformer; use Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException; /** @@ -82,7 +81,7 @@ abstract class BaseTransformer extends TransformerAbstract * set API key. * * @param string $abstract - * @param array $parameters + * @param array $parameters * @return \Pterodactyl\Transformers\Api\Application\BaseTransformer * * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException @@ -93,7 +92,7 @@ abstract class BaseTransformer extends TransformerAbstract $transformer = Container::getInstance()->makeWith($abstract, $parameters); $transformer->setKey($this->getKey()); - if (! $transformer instanceof self || $transformer instanceof BaseClientTransformer) { + if (! $transformer instanceof self) { throw new InvalidTransformerLevelException('Calls to ' . __METHOD__ . ' must return a transformer that is an instance of ' . __CLASS__); } diff --git a/app/Transformers/Api/Application/LocationTransformer.php b/app/Transformers/Api/Application/LocationTransformer.php index d54e77d2..7d24cc97 100644 --- a/app/Transformers/Api/Application/LocationTransformer.php +++ b/app/Transformers/Api/Application/LocationTransformer.php @@ -25,7 +25,7 @@ class LocationTransformer extends BaseTransformer } /** - * Return a generic transformed pack array. + * Return a generic transformed location array. * * @param \Pterodactyl\Models\Location $location * @return array diff --git a/app/Transformers/Api/Application/NodeTransformer.php b/app/Transformers/Api/Application/NodeTransformer.php index d9b8b61f..c26d67ce 100644 --- a/app/Transformers/Api/Application/NodeTransformer.php +++ b/app/Transformers/Api/Application/NodeTransformer.php @@ -44,6 +44,13 @@ class NodeTransformer extends BaseTransformer $response[$node->getUpdatedAtColumn()] = $this->formatTimestamp($node->updated_at); $response[$node->getCreatedAtColumn()] = $this->formatTimestamp($node->created_at); + $resources = $node->servers()->select(['memory', 'disk'])->get(); + + $response['allocated_resources'] = [ + 'memory' => $resources->sum('memory'), + 'disk' => $resources->sum('disk'), + ]; + return $response; } diff --git a/app/Transformers/Api/Application/PackTransformer.php b/app/Transformers/Api/Application/PackTransformer.php deleted file mode 100644 index e77bdd45..00000000 --- a/app/Transformers/Api/Application/PackTransformer.php +++ /dev/null @@ -1,40 +0,0 @@ - $pack->id, - 'uuid' => $pack->uuid, - 'egg' => $pack->egg_id, - 'name' => $pack->name, - 'description' => $pack->description, - 'is_selectable' => (bool) $pack->selectable, - 'is_visible' => (bool) $pack->visible, - 'is_locked' => (bool) $pack->locked, - 'created_at' => $this->formatTimestamp($pack->created_at), - 'updated_at' => $this->formatTimestamp($pack->updated_at), - ]; - } -} diff --git a/app/Transformers/Api/Application/ServerDatabaseTransformer.php b/app/Transformers/Api/Application/ServerDatabaseTransformer.php index 1cdced61..a88ba6e8 100644 --- a/app/Transformers/Api/Application/ServerDatabaseTransformer.php +++ b/app/Transformers/Api/Application/ServerDatabaseTransformer.php @@ -56,6 +56,7 @@ class ServerDatabaseTransformer extends BaseTransformer 'database' => $model->database, 'username' => $model->username, 'remote' => $model->remote, + 'max_connections' => $model->max_connections, 'created_at' => Chronos::createFromFormat(Chronos::DEFAULT_TO_STRING_FORMAT, $model->created_at) ->setTimezone(config('app.timezone')) ->toIso8601String(), diff --git a/app/Transformers/Api/Application/ServerTransformer.php b/app/Transformers/Api/Application/ServerTransformer.php index 70dc185d..e2c32eb7 100644 --- a/app/Transformers/Api/Application/ServerTransformer.php +++ b/app/Transformers/Api/Application/ServerTransformer.php @@ -22,7 +22,6 @@ class ServerTransformer extends BaseTransformer 'allocations', 'user', 'subusers', - 'pack', 'nest', 'egg', 'variables', @@ -75,17 +74,18 @@ class ServerTransformer extends BaseTransformer 'disk' => $server->disk, 'io' => $server->io, 'cpu' => $server->cpu, + 'threads' => $server->threads, ], 'feature_limits' => [ 'databases' => $server->database_limit, 'allocations' => $server->allocation_limit, + 'backups' => $server->backup_limit, ], 'user' => $server->owner_id, 'node' => $server->node_id, 'allocation' => $server->allocation_id, 'nest' => $server->nest_id, 'egg' => $server->egg_id, - 'pack' => $server->pack_id, 'container' => [ 'startup_command' => $server->startup, 'image' => $server->image, @@ -154,28 +154,6 @@ class ServerTransformer extends BaseTransformer return $this->item($server->getRelation('user'), $this->makeTransformer(UserTransformer::class), 'user'); } - /** - * Return a generic array with pack information for this server. - * - * @param \Pterodactyl\Models\Server $server - * @return \League\Fractal\Resource\Item|\League\Fractal\Resource\NullResource - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException - */ - public function includePack(Server $server) - { - if (! $this->authorize(AdminAcl::RESOURCE_PACKS)) { - return $this->null(); - } - - $server->loadMissing('pack'); - if (is_null($server->getRelation('pack'))) { - return $this->null(); - } - - return $this->item($server->getRelation('pack'), $this->makeTransformer(PackTransformer::class), 'pack'); - } - /** * Return a generic array with nest information for this server. * @@ -234,7 +212,7 @@ class ServerTransformer extends BaseTransformer } /** - * Return a generic array with pack information for this server. + * Return a generic array with location information for this server. * * @param \Pterodactyl\Models\Server $server * @return \League\Fractal\Resource\Item|\League\Fractal\Resource\NullResource @@ -253,7 +231,7 @@ class ServerTransformer extends BaseTransformer } /** - * Return a generic array with pack information for this server. + * Return a generic array with node information for this server. * * @param \Pterodactyl\Models\Server $server * @return \League\Fractal\Resource\Item|\League\Fractal\Resource\NullResource diff --git a/app/Transformers/Api/Application/SubuserTransformer.php b/app/Transformers/Api/Application/SubuserTransformer.php index 7927272c..265e2f9d 100644 --- a/app/Transformers/Api/Application/SubuserTransformer.php +++ b/app/Transformers/Api/Application/SubuserTransformer.php @@ -37,9 +37,7 @@ class SubuserTransformer extends BaseTransformer 'id' => $subuser->id, 'user_id' => $subuser->user_id, 'server_id' => $subuser->server_id, - 'permissions' => $subuser->permissions->map(function (Permission $permission) { - return $permission->permission; - }), + 'permissions' => $subuser->permissions, 'created_at' => $this->formatTimestamp($subuser->created_at), 'updated_at' => $this->formatTimestamp($subuser->updated_at), ]; diff --git a/app/Transformers/Api/Client/AccountTransformer.php b/app/Transformers/Api/Client/AccountTransformer.php new file mode 100644 index 00000000..2cdc92e3 --- /dev/null +++ b/app/Transformers/Api/Client/AccountTransformer.php @@ -0,0 +1,37 @@ + $model->id, + 'admin' => $model->root_admin, + 'username' => $model->username, + 'email' => $model->email, + 'first_name' => $model->name_first, + 'last_name' => $model->name_last, + 'language' => $model->language, + ]; + } +} diff --git a/app/Transformers/Api/Client/AllocationTransformer.php b/app/Transformers/Api/Client/AllocationTransformer.php new file mode 100644 index 00000000..57f72eaf --- /dev/null +++ b/app/Transformers/Api/Client/AllocationTransformer.php @@ -0,0 +1,36 @@ + $model->id, + 'ip' => $model->ip, + 'ip_alias' => $model->ip_alias, + 'port' => $model->port, + 'notes' => $model->notes, + 'is_default' => $model->server->allocation_id === $model->id, + ]; + } +} diff --git a/app/Transformers/Api/Client/ApiKeyTransformer.php b/app/Transformers/Api/Client/ApiKeyTransformer.php new file mode 100644 index 00000000..4c30ea3a --- /dev/null +++ b/app/Transformers/Api/Client/ApiKeyTransformer.php @@ -0,0 +1,33 @@ + $model->identifier, + 'description' => $model->memo, + 'allowed_ips' => $model->allowed_ips, + 'last_used_at' => $model->last_used_at ? $model->last_used_at->toIso8601String() : null, + 'created_at' => $model->created_at->toIso8601String(), + ]; + } +} diff --git a/app/Transformers/Api/Client/BackupTransformer.php b/app/Transformers/Api/Client/BackupTransformer.php new file mode 100644 index 00000000..d5acd41f --- /dev/null +++ b/app/Transformers/Api/Client/BackupTransformer.php @@ -0,0 +1,34 @@ + $backup->uuid, + 'is_successful' => $backup->is_successful, + 'name' => $backup->name, + 'ignored_files' => $backup->ignored_files, + 'checksum' => $backup->checksum, + 'bytes' => $backup->bytes, + 'created_at' => $backup->created_at->toIso8601String(), + 'completed_at' => $backup->completed_at ? $backup->completed_at->toIso8601String() : null, + ]; + } +} diff --git a/app/Transformers/Api/Client/BaseClientTransformer.php b/app/Transformers/Api/Client/BaseClientTransformer.php index faa1abbe..c15e583e 100644 --- a/app/Transformers/Api/Client/BaseClientTransformer.php +++ b/app/Transformers/Api/Client/BaseClientTransformer.php @@ -5,6 +5,7 @@ namespace Pterodactyl\Transformers\Api\Client; use Pterodactyl\Models\User; use Webmozart\Assert\Assert; use Pterodactyl\Models\Server; +use Illuminate\Container\Container; use Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException; use Pterodactyl\Transformers\Api\Application\BaseTransformer as BaseApplicationTransformer; @@ -40,7 +41,7 @@ abstract class BaseClientTransformer extends BaseApplicationTransformer * to access a different resource. This is used when including other * models on a transformation request. * - * @param string $ability + * @param string $ability * @param \Pterodactyl\Models\Server $server * @return bool */ @@ -56,14 +57,16 @@ abstract class BaseClientTransformer extends BaseApplicationTransformer * set API key. * * @param string $abstract - * @param array $parameters + * @param array $parameters * @return self * * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ protected function makeTransformer(string $abstract, array $parameters = []) { - $transformer = parent::makeTransformer($abstract, $parameters); + /** @var \Pterodactyl\Transformers\Api\Application\BaseTransformer $transformer */ + $transformer = Container::getInstance()->makeWith($abstract, $parameters); + $transformer->setKey($this->getKey()); if (! $transformer instanceof self) { throw new InvalidTransformerLevelException('Calls to ' . __METHOD__ . ' must return a transformer that is an instance of ' . __CLASS__); diff --git a/app/Transformers/Api/Client/DatabaseTransformer.php b/app/Transformers/Api/Client/DatabaseTransformer.php new file mode 100644 index 00000000..ddf02af1 --- /dev/null +++ b/app/Transformers/Api/Client/DatabaseTransformer.php @@ -0,0 +1,84 @@ +encrypter = $encrypter; + $this->hashids = $hashids; + } + + /** + * @return string + */ + public function getResourceName(): string + { + return Database::RESOURCE_NAME; + } + + /** + * @param \Pterodactyl\Models\Database $model + * @return array + */ + public function transform(Database $model): array + { + $model->loadMissing('host'); + + return [ + 'id' => $this->hashids->encode($model->id), + 'host' => [ + 'address' => $model->getRelation('host')->host, + 'port' => $model->getRelation('host')->port, + ], + 'name' => $model->database, + 'username' => $model->username, + 'connections_from' => $model->remote, + 'max_connections' => $model->max_connections, + ]; + } + + /** + * Include the database password in the request. + * + * @param \Pterodactyl\Models\Database $database + * @return \League\Fractal\Resource\Item|\League\Fractal\Resource\NullResource + */ + public function includePassword(Database $database): Item + { + if (!$this->getUser()->can(Permission::ACTION_DATABASE_VIEW_PASSWORD, $database->server)) { + return $this->null(); + } + + return $this->item($database, function (Database $model) { + return [ + 'password' => $this->encrypter->decrypt($model->password), + ]; + }, 'database_password'); + } +} diff --git a/app/Transformers/Api/Client/EggTransformer.php b/app/Transformers/Api/Client/EggTransformer.php new file mode 100644 index 00000000..ed5b8c58 --- /dev/null +++ b/app/Transformers/Api/Client/EggTransformer.php @@ -0,0 +1,30 @@ + $egg->uuid, + 'name' => $egg->name, + ]; + } +} diff --git a/app/Transformers/Api/Client/EggVariableTransformer.php b/app/Transformers/Api/Client/EggVariableTransformer.php new file mode 100644 index 00000000..4f7e3965 --- /dev/null +++ b/app/Transformers/Api/Client/EggVariableTransformer.php @@ -0,0 +1,44 @@ +user_viewable) { + throw new BadMethodCallException( + 'Cannot transform a hidden egg variable in a client transformer.' + ); + } + + return [ + 'name' => $variable->name, + 'description' => $variable->description, + 'env_variable' => $variable->env_variable, + 'default_value' => $variable->default_value, + 'server_value' => $variable->server_value, + 'is_editable' => $variable->user_editable, + 'rules' => $variable->rules, + ]; + } +} diff --git a/app/Transformers/Api/Client/ScheduleTransformer.php b/app/Transformers/Api/Client/ScheduleTransformer.php new file mode 100644 index 00000000..43e35ed4 --- /dev/null +++ b/app/Transformers/Api/Client/ScheduleTransformer.php @@ -0,0 +1,69 @@ + $model->id, + 'name' => $model->name, + 'cron' => [ + 'day_of_week' => $model->cron_day_of_week, + 'day_of_month' => $model->cron_day_of_month, + 'hour' => $model->cron_hour, + 'minute' => $model->cron_minute, + ], + 'is_active' => $model->is_active, + 'is_processing' => $model->is_processing, + 'last_run_at' => $model->last_run_at ? $model->last_run_at->toIso8601String() : null, + 'next_run_at' => $model->next_run_at ? $model->next_run_at->toIso8601String() : null, + 'created_at' => $model->created_at->toIso8601String(), + 'updated_at' => $model->updated_at->toIso8601String(), + ]; + } + + /** + * Allows attaching the tasks specific to the schedule in the response. + * + * @param \Pterodactyl\Models\Schedule $model + * @return \League\Fractal\Resource\Collection + * + * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException + */ + public function includeTasks(Schedule $model) + { + return $this->collection( + $model->tasks, $this->makeTransformer(TaskTransformer::class), Task::RESOURCE_NAME + ); + } +} diff --git a/app/Transformers/Api/Client/ServerTransformer.php b/app/Transformers/Api/Client/ServerTransformer.php index 6816d6d7..92b9ea0b 100644 --- a/app/Transformers/Api/Client/ServerTransformer.php +++ b/app/Transformers/Api/Client/ServerTransformer.php @@ -2,10 +2,27 @@ namespace Pterodactyl\Transformers\Api\Client; +use Pterodactyl\Models\Egg; use Pterodactyl\Models\Server; +use Pterodactyl\Models\Subuser; +use Pterodactyl\Models\Allocation; +use Pterodactyl\Models\Permission; +use Illuminate\Container\Container; +use Pterodactyl\Models\EggVariable; +use Pterodactyl\Services\Servers\StartupCommandService; class ServerTransformer extends BaseClientTransformer { + /** + * @var string[] + */ + protected $defaultIncludes = ['allocations', 'variables']; + + /** + * @var array + */ + protected $availableIncludes = ['egg', 'subusers']; + /** * @return string */ @@ -23,11 +40,19 @@ class ServerTransformer extends BaseClientTransformer */ public function transform(Server $server): array { + /** @var \Pterodactyl\Services\Servers\StartupCommandService $service */ + $service = Container::getInstance()->make(StartupCommandService::class); + return [ 'server_owner' => $this->getKey()->user_id === $server->owner_id, 'identifier' => $server->uuidShort, 'uuid' => $server->uuid, 'name' => $server->name, + 'node' => $server->node->name, + 'sftp_details' => [ + 'ip' => $server->node->fqdn, + 'port' => $server->node->daemonSFTP, + ], 'description' => $server->description, 'limits' => [ 'memory' => $server->memory, @@ -36,10 +61,83 @@ class ServerTransformer extends BaseClientTransformer 'io' => $server->io, 'cpu' => $server->cpu, ], + 'invocation' => $service->handle($server, ! $this->getUser()->can(Permission::ACTION_STARTUP_READ, $server)), 'feature_limits' => [ 'databases' => $server->database_limit, 'allocations' => $server->allocation_limit, + 'backups' => $server->backup_limit, ], + 'is_suspended' => $server->suspended, + 'is_installing' => $server->installed !== 1, ]; } + + /** + * Returns the allocations associated with this server. + * + * @param \Pterodactyl\Models\Server $server + * @return \League\Fractal\Resource\Collection|\League\Fractal\Resource\NullResource + * + * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException + */ + public function includeAllocations(Server $server) + { + if (! $this->getUser()->can(Permission::ACTION_ALLOCATION_READ, $server)) { + return $this->null(); + } + + return $this->collection( + $server->allocations, + $this->makeTransformer(AllocationTransformer::class), + Allocation::RESOURCE_NAME + ); + } + + /** + * @param \Pterodactyl\Models\Server $server + * @return \League\Fractal\Resource\Collection|\League\Fractal\Resource\NullResource + * + * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException + */ + public function includeVariables(Server $server) + { + if (! $this->getUser()->can(Permission::ACTION_STARTUP_READ, $server)) { + return $this->null(); + } + + return $this->collection( + $server->variables->where('user_viewable', true), + $this->makeTransformer(EggVariableTransformer::class), + EggVariable::RESOURCE_NAME + ); + } + + /** + * Returns the egg associated with this server. + * + * @param \Pterodactyl\Models\Server $server + * @return \League\Fractal\Resource\Item + * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException + */ + public function includeEgg(Server $server) + { + return $this->item($server->egg, $this->makeTransformer(EggTransformer::class), Egg::RESOURCE_NAME); + } + + /** + * Returns the subusers associated with this server. + * + * @param \Pterodactyl\Models\Server $server + * @return \League\Fractal\Resource\Collection|\League\Fractal\Resource\NullResource + * + * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException + */ + public function includeSubusers(Server $server) + { + if (! $this->getUser()->can(Permission::ACTION_USER_READ, $server)) { + return $this->null(); + } + + return $this->collection($server->subusers, $this->makeTransformer(SubuserTransformer::class), Subuser::RESOURCE_NAME); + } } diff --git a/app/Transformers/Api/Client/StatsTransformer.php b/app/Transformers/Api/Client/StatsTransformer.php index 01d8e3f2..97989cc3 100644 --- a/app/Transformers/Api/Client/StatsTransformer.php +++ b/app/Transformers/Api/Client/StatsTransformer.php @@ -2,26 +2,10 @@ namespace Pterodactyl\Transformers\Api\Client; -use Pterodactyl\Models\Server; -use Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface; +use Illuminate\Support\Arr; class StatsTransformer extends BaseClientTransformer { - /** - * @var \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface - */ - private $repository; - - /** - * Perform dependency injection. - * - * @param \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface $repository - */ - public function handle(ServerRepositoryInterface $repository) - { - $this->repository = $repository; - } - /** * @return string */ @@ -34,55 +18,21 @@ class StatsTransformer extends BaseClientTransformer * Transform stats from the daemon into a result set that can be used in * the client API. * - * @param \Pterodactyl\Models\Server $model + * @param array $data * @return array */ - public function transform(Server $model) + public function transform(array $data) { - try { - $stats = $this->repository->setServer($model)->details(); - } catch (RequestException $exception) { - throw new DaemonConnectionException($exception); - } - - $object = json_decode($stats->getBody()->getContents()); - return [ - 'state' => $this->transformState(object_get($object, 'status', 0)), - 'memory' => [ - 'current' => round(object_get($object, 'proc.memory.total', 0) / 1024 / 1024), - 'limit' => floatval($model->memory), - ], - 'cpu' => [ - 'current' => object_get($object, 'proc.cpu.total', 0), - 'cores' => object_get($object, 'proc.cpu.cores', []), - 'limit' => floatval($model->cpu), - ], - 'disk' => [ - 'current' => round(object_get($object, 'proc.disk.used', 0)), - 'limit' => floatval($model->disk), + 'current_state' => Arr::get($data, 'state', 'stopped'), + 'is_suspended' => Arr::get($data, 'suspended', false), + 'resources' => [ + 'memory_bytes' => Arr::get($data, 'memory_bytes', 0), + 'cpu_absolute' => Arr::get($data, 'cpu_absolute', 0), + 'disk_bytes' => Arr::get($data, 'disk_bytes', 0), + 'network_rx_bytes' => Arr::get($data, 'network.rx_bytes', 0), + 'network_tx_bytes' => Arr::get($data, 'network.tx_bytes', 0), ], ]; } - - /** - * Transform the state returned by the daemon into a human readable string. - * - * @param int $state - * @return string - */ - private function transformState(int $state): string - { - switch ($state) { - case 1: - return 'on'; - case 2: - return 'starting'; - case 3: - return 'stopping'; - case 0: - default: - return 'off'; - } - } } diff --git a/app/Transformers/Api/Client/SubuserTransformer.php b/app/Transformers/Api/Client/SubuserTransformer.php new file mode 100644 index 00000000..d2e7ce0f --- /dev/null +++ b/app/Transformers/Api/Client/SubuserTransformer.php @@ -0,0 +1,33 @@ +makeTransformer(UserTransformer::class)->transform($model->user), + ['permissions' => $model->permissions] + ); + } +} diff --git a/app/Transformers/Api/Client/TaskTransformer.php b/app/Transformers/Api/Client/TaskTransformer.php new file mode 100644 index 00000000..c215479a --- /dev/null +++ b/app/Transformers/Api/Client/TaskTransformer.php @@ -0,0 +1,36 @@ + $model->id, + 'sequence_id' => $model->sequence_id, + 'action' => $model->action, + 'payload' => $model->payload, + 'time_offset' => $model->time_offset, + 'is_queued' => $model->is_queued, + 'created_at' => $model->created_at->toIso8601String(), + 'updated_at' => $model->updated_at->toIso8601String(), + ]; + } +} diff --git a/app/Transformers/Api/Client/UserTransformer.php b/app/Transformers/Api/Client/UserTransformer.php new file mode 100644 index 00000000..65849f06 --- /dev/null +++ b/app/Transformers/Api/Client/UserTransformer.php @@ -0,0 +1,38 @@ + $model->uuid, + 'username' => $model->username, + 'email' => $model->email, + 'image' => 'https://gravatar.com/avatar/' . md5(Str::lower($model->email)), + '2fa_enabled' => $model->use_totp, + 'created_at' => $model->created_at->toIso8601String(), + ]; + } +} diff --git a/app/Transformers/Daemon/ApiKeyTransformer.php b/app/Transformers/Daemon/ApiKeyTransformer.php deleted file mode 100644 index 7f1d7427..00000000 --- a/app/Transformers/Daemon/ApiKeyTransformer.php +++ /dev/null @@ -1,76 +0,0 @@ -repository = $repository; - $this->keyRepository = $keyRepository; - } - - /** - * Return a listing of servers that a daemon key can access. - * - * @param \Pterodactyl\Models\DaemonKey $key - * @return array - * - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - */ - public function transform(DaemonKey $key) - { - $this->keyRepository->loadServerAndUserRelations($key); - - if ($key->user_id === $key->getRelation('server')->owner_id || $key->getRelation('user')->root_admin) { - return [ - 'id' => $key->getRelation('server')->uuid, - 'is_temporary' => true, - 'expires_in' => max(Carbon::now()->diffInSeconds($key->expires_at, false), 0), - 'permissions' => ['s:*'], - ]; - } - - $subuser = $this->repository->getWithPermissionsUsingUserAndServer($key->user_id, $key->server_id); - - $permissions = $subuser->getRelation('permissions')->pluck('permission')->toArray(); - $mappings = Permission::getPermissions(true); - $daemonPermissions = ['s:console']; - - foreach ($permissions as $permission) { - if (! is_null(array_get($mappings, $permission))) { - $daemonPermissions[] = array_get($mappings, $permission); - } - } - - return [ - 'id' => $key->getRelation('server')->uuid, - 'is_temporary' => true, - 'expires_in' => max(Carbon::now()->diffInSeconds($key->expires_at, false), 0), - 'permissions' => $daemonPermissions, - ]; - } -} diff --git a/app/Transformers/Daemon/BaseDaemonTransformer.php b/app/Transformers/Daemon/BaseDaemonTransformer.php new file mode 100644 index 00000000..7ec75864 --- /dev/null +++ b/app/Transformers/Daemon/BaseDaemonTransformer.php @@ -0,0 +1,9 @@ + Arr::get($item, 'name'), + 'mode' => Arr::get($item, 'mode'), + 'size' => Arr::get($item, 'size'), + 'is_file' => Arr::get($item, 'file', true), + 'is_symlink' => Arr::get($item, 'symlink', false), + 'mimetype' => Arr::get($item, 'mime', 'application/octet-stream'), + 'created_at' => Carbon::parse(Arr::get($item, 'created', ''))->toIso8601String(), + 'modified_at' => Carbon::parse(Arr::get($item, 'modified', ''))->toIso8601String(), + ]; + } + + /** + * @return string + */ + public function getResourceName(): string + { + return 'file_object'; + } +} diff --git a/app/helpers.php b/app/helpers.php index bfb12cc7..0716fd21 100644 --- a/app/helpers.php +++ b/app/helpers.php @@ -1,34 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ -if (! function_exists('human_readable')) { - /** - * Generate a human-readable filesize for a given file path. - * - * @param string $path - * @param int $precision - * @return string - */ - function human_readable($path, $precision = 2) - { - if (is_numeric($path)) { - $i = 0; - while (($path / 1024) > 0.9) { - $path = $path / 1024; - $i++; - } - - return round($path, $precision) . ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'][$i]; - } - - return app('file')->humanReadableSize($path, $precision); - } -} if (! function_exists('is_digit')) { /** @@ -51,7 +21,7 @@ if (! function_exists('object_get_strict')) { * * @param object $object * @param string $key - * @param null $default + * @param null $default * @return mixed */ function object_get_strict($object, $key, $default = null) diff --git a/babel.config.js b/babel.config.js new file mode 100644 index 00000000..a0ead029 --- /dev/null +++ b/babel.config.js @@ -0,0 +1,23 @@ +module.exports = { + presets: [ + '@babel/typescript', + ['@babel/env', { + modules: false, + useBuiltIns: 'entry', + corejs: 3, + }], + '@babel/react', + ], + plugins: [ + 'babel-plugin-macros', + 'styled-components', + 'react-hot-loader/babel', + '@babel/transform-runtime', + '@babel/transform-react-jsx', + '@babel/proposal-class-properties', + '@babel/proposal-object-rest-spread', + '@babel/proposal-optional-chaining', + '@babel/proposal-nullish-coalescing-operator', + '@babel/syntax-dynamic-import', + ], +}; diff --git a/bootstrap/tests.php b/bootstrap/tests.php index 27678661..e2233462 100644 --- a/bootstrap/tests.php +++ b/bootstrap/tests.php @@ -17,12 +17,22 @@ $kernel->bootstrap(); $output = new ConsoleOutput; +if (config('database.default') !== 'testing') { + $output->writeln(PHP_EOL . 'Cannot run test process against non-testing database.'); + $output->writeln(PHP_EOL . 'Environment is currently pointed at: "' . config('database.default') . '".'); + exit(1); +} + /* * Perform database migrations and reseeding before continuing with * running the tests. */ -$output->writeln(PHP_EOL . 'Refreshing database for Integration tests...'); -$kernel->call('migrate:fresh', ['--database' => 'testing']); +if (! env('SKIP_MIGRATIONS')) { + $output->writeln(PHP_EOL . 'Refreshing database for Integration tests...'); + $kernel->call('migrate:fresh', ['--database' => 'testing']); -$output->writeln('Seeding database for Integration tests...' . PHP_EOL); -$kernel->call('db:seed', ['--database' => 'testing']); + $output->writeln('Seeding database for Integration tests...' . PHP_EOL); + $kernel->call('db:seed', ['--database' => 'testing']); +} else { + $output->writeln(PHP_EOL . 'Skipping database migrations...' . PHP_EOL); +} diff --git a/composer.json b/composer.json index e1bc76f5..a07a9ae8 100644 --- a/composer.json +++ b/composer.json @@ -11,46 +11,47 @@ } ], "require": { - "php": ">=7.2", + "php": "^7.2", "ext-mbstring": "*", "ext-pdo_mysql": "*", "ext-zip": "*", - "appstract/laravel-blade-directives": "^0.7", - "aws/aws-sdk-php": "^3.48", - "cakephp/chronos": "^1.1", - "doctrine/dbal": "^2.5", - "fideloper/proxy": "^4.0", - "guzzlehttp/guzzle": "^6.3", - "hashids/hashids": "^3.0", - "igaster/laravel-theme": "^2.0.6", - "laracasts/utilities": "^3.0", - "laravel/framework": "~5.7.14", - "laravel/tinker": "^1.0", - "lord/laroute": "^2.4", + "appstract/laravel-blade-directives": "^1.8", + "aws/aws-sdk-php": "^3.134", + "cakephp/chronos": "^1.3", + "doctrine/dbal": "^2.10", + "fideloper/proxy": "^4.2", + "guzzlehttp/guzzle": "^6.5", + "hashids/hashids": "^4.0", + "laracasts/utilities": "^3.1", + "laravel/framework": "^7.17", + "laravel/helpers": "^1.2", + "laravel/tinker": "^2.4", + "laravel/ui": "^2.0", + "lcobucci/jwt": "^3.3", + "league/flysystem-aws-s3-v3": "^1.0", + "league/flysystem-memory": "^1.0", "matriphe/iso-639": "^1.2", - "nesbot/carbon": "^1.22", "pragmarx/google2fa": "^5.0", "predis/predis": "^1.1", "prologue/alerts": "^0.4", - "ramsey/uuid": "^3.7", + "psy/psysh": "^0.10.4", "s1lentium/iptools": "^1.1", - "sofa/eloquence-base": "v5.6.2", - "sofa/eloquence-validable": "v5.6", - "spatie/laravel-fractal": "^5.4", - "staudenmeir/belongs-to-through": "~2.3.0", - "webmozart/assert": "^1.2" + "spatie/laravel-fractal": "^5.7", + "spatie/laravel-query-builder": "^2.8", + "staudenmeir/belongs-to-through": "^2.10", + "symfony/yaml": "^4.4", + "webmozart/assert": "^1.9" }, "require-dev": { - "barryvdh/laravel-debugbar": "^3.2", - "barryvdh/laravel-ide-helper": "^2.5", - "codedungeon/phpunit-result-printer": "^0.17.1", - "filp/whoops": "^2.1", - "friendsofphp/php-cs-fixer": "^2.15.1", - "fzaninotto/faker": "^1.6", - "mockery/mockery": "^1.0", - "nunomaduro/collision": "^2.0", - "php-mock/php-mock-phpunit": "^2.1", - "phpunit/phpunit": "~7.0" + "barryvdh/laravel-debugbar": "^3.3", + "barryvdh/laravel-ide-helper": "^2.7", + "codedungeon/phpunit-result-printer": "^0.28.0", + "friendsofphp/php-cs-fixer": "2.16.1", + "fzaninotto/faker": "^1.9", + "laravel/dusk": "^6.3", + "mockery/mockery": "^1.4", + "php-mock/php-mock-phpunit": "^2.6", + "phpunit/phpunit": "^8.5" }, "autoload": { "classmap": [ @@ -65,6 +66,7 @@ }, "autoload-dev": { "psr-4": { + "Pterodactyl\\Tests\\Browser\\": "tests/Browser", "Pterodactyl\\Tests\\Integration\\": "tests/Integration", "Tests\\": "tests/" } diff --git a/composer.lock b/composer.lock index 714d1867..6df479de 100644 --- a/composer.lock +++ b/composer.lock @@ -4,27 +4,28 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "175a2431d23dd32f79b42bbc8030ace1", + "content-hash": "d05ab995e4aff4b847ff2a027924065c", "packages": [ { "name": "appstract/laravel-blade-directives", - "version": "0.7.1", + "version": "1.9.0", "source": { "type": "git", "url": "https://github.com/appstract/laravel-blade-directives.git", - "reference": "398d36a1c1f2740c81358b99473fd1564a81c406" + "reference": "c569529da9bf4d87c85157061c5e86b5ee1f8ff4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/appstract/laravel-blade-directives/zipball/398d36a1c1f2740c81358b99473fd1564a81c406", - "reference": "398d36a1c1f2740c81358b99473fd1564a81c406", + "url": "https://api.github.com/repos/appstract/laravel-blade-directives/zipball/c569529da9bf4d87c85157061c5e86b5ee1f8ff4", + "reference": "c569529da9bf4d87c85157061c5e86b5ee1f8ff4", "shasum": "" }, "require": { - "php": ">=5.6" + "laravel/framework": "^5.7|^6.0|^7.0", + "php": "^7.1.3" }, "require-dev": { - "phpunit/phpunit": "^5.7" + "orchestra/testbench": "~3.7|^4.0|^5.0" }, "type": "library", "extra": { @@ -46,9 +47,9 @@ "authors": [ { "name": "Gijs Jorissen", - "role": "Developer", - "email": "hello@appstract.team", - "homepage": "https://appstract.team" + "email": "gijs@appstract.nl", + "homepage": "https://appstract.nl", + "role": "Developer" } ], "description": "Handy Blade directives", @@ -57,31 +58,30 @@ "appstract", "laravel-blade-directives" ], - "time": "2018-01-08T13:58:34+00:00" + "time": "2020-04-14T08:41:18+00:00" }, { "name": "aws/aws-sdk-php", - "version": "3.74.1", + "version": "3.142.8", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "e02575af8021ee57b818107c1fd8759110374044" + "reference": "a9cffb2442b63ca8a99ed3e68aef19df221e2a54" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/e02575af8021ee57b818107c1fd8759110374044", - "reference": "e02575af8021ee57b818107c1fd8759110374044", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/a9cffb2442b63ca8a99ed3e68aef19df221e2a54", + "reference": "a9cffb2442b63ca8a99ed3e68aef19df221e2a54", "shasum": "" }, "require": { "ext-json": "*", "ext-pcre": "*", "ext-simplexml": "*", - "ext-spl": "*", - "guzzlehttp/guzzle": "^5.3.3|^6.2.1", - "guzzlehttp/promises": "~1.0", + "guzzlehttp/guzzle": "^5.3.3|^6.2.1|^7.0", + "guzzlehttp/promises": "^1.0", "guzzlehttp/psr7": "^1.4.1", - "mtdowling/jmespath.php": "~2.2", + "mtdowling/jmespath.php": "^2.5", "php": ">=5.5" }, "require-dev": { @@ -94,8 +94,11 @@ "ext-pcntl": "*", "ext-sockets": "*", "nette/neon": "^2.3", + "paragonie/random_compat": ">= 2", "phpunit/phpunit": "^4.8.35|^5.4.3", - "psr/cache": "^1.0" + "psr/cache": "^1.0", + "psr/simple-cache": "^1.0", + "sebastian/comparator": "^1.2.3" }, "suggest": { "aws/aws-php-sns-message-validator": "To validate incoming SNS notifications", @@ -140,30 +143,81 @@ "s3", "sdk" ], - "time": "2018-11-21T19:18:43+00:00" + "time": "2020-06-23T18:46:28+00:00" }, { - "name": "cakephp/chronos", - "version": "1.2.3", + "name": "brick/math", + "version": "0.8.15", "source": { "type": "git", - "url": "https://github.com/cakephp/chronos.git", - "reference": "395110125ff577f080fa064dca5c5608a4e77ee1" + "url": "https://github.com/brick/math.git", + "reference": "9b08d412b9da9455b210459ff71414de7e6241cd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/cakephp/chronos/zipball/395110125ff577f080fa064dca5c5608a4e77ee1", - "reference": "395110125ff577f080fa064dca5c5608a4e77ee1", + "url": "https://api.github.com/repos/brick/math/zipball/9b08d412b9da9455b210459ff71414de7e6241cd", + "reference": "9b08d412b9da9455b210459ff71414de7e6241cd", "shasum": "" }, "require": { - "php": "^5.5.9|^7" + "ext-json": "*", + "php": "^7.1|^8.0" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.2", + "phpunit/phpunit": "^7.5.15|^8.5", + "vimeo/psalm": "^3.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Brick\\Math\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Arbitrary-precision arithmetic library", + "keywords": [ + "Arbitrary-precision", + "BigInteger", + "BigRational", + "arithmetic", + "bigdecimal", + "bignum", + "brick", + "math" + ], + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/brick/math", + "type": "tidelift" + } + ], + "time": "2020-04-15T15:59:35+00:00" + }, + { + "name": "cakephp/chronos", + "version": "1.3.0", + "source": { + "type": "git", + "url": "https://github.com/cakephp/chronos.git", + "reference": "ba2bab98849e7bf29b02dd634ada49ab36472959" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/cakephp/chronos/zipball/ba2bab98849e7bf29b02dd634ada49ab36472959", + "reference": "ba2bab98849e7bf29b02dd634ada49ab36472959", + "shasum": "" + }, + "require": { + "php": ">=5.6" }, "require-dev": { "athletic/athletic": "~0.1", "cakephp/cakephp-codesniffer": "^3.0", "phpbench/phpbench": "@dev", - "phpstan/phpstan": "^0.6.4", "phpunit/phpunit": "<6.0 || ^7.0" }, "type": "library", @@ -197,29 +251,29 @@ "datetime", "time" ], - "time": "2018-10-18T22:02:21+00:00" + "time": "2019-11-30T02:33:19+00:00" }, { "name": "dnoegel/php-xdg-base-dir", - "version": "0.1", + "version": "v0.1.1", "source": { "type": "git", "url": "https://github.com/dnoegel/php-xdg-base-dir.git", - "reference": "265b8593498b997dc2d31e75b89f053b5cc9621a" + "reference": "8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dnoegel/php-xdg-base-dir/zipball/265b8593498b997dc2d31e75b89f053b5cc9621a", - "reference": "265b8593498b997dc2d31e75b89f053b5cc9621a", + "url": "https://api.github.com/repos/dnoegel/php-xdg-base-dir/zipball/8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd", + "reference": "8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd", "shasum": "" }, "require": { "php": ">=5.3.2" }, "require-dev": { - "phpunit/phpunit": "@stable" + "phpunit/phpunit": "~7.0|~6.0|~5.0|~4.8.35" }, - "type": "project", + "type": "library", "autoload": { "psr-4": { "XdgBaseDir\\": "src/" @@ -230,31 +284,31 @@ "MIT" ], "description": "implementation of xdg base directory specification for php", - "time": "2014-10-24T07:27:01+00:00" + "time": "2019-12-04T15:06:13+00:00" }, { "name": "doctrine/cache", - "version": "v1.8.0", + "version": "1.10.1", "source": { "type": "git", "url": "https://github.com/doctrine/cache.git", - "reference": "d768d58baee9a4862ca783840eca1b9add7a7f57" + "reference": "35a4a70cd94e09e2259dfae7488afc6b474ecbd3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/cache/zipball/d768d58baee9a4862ca783840eca1b9add7a7f57", - "reference": "d768d58baee9a4862ca783840eca1b9add7a7f57", + "url": "https://api.github.com/repos/doctrine/cache/zipball/35a4a70cd94e09e2259dfae7488afc6b474ecbd3", + "reference": "35a4a70cd94e09e2259dfae7488afc6b474ecbd3", "shasum": "" }, "require": { - "php": "~7.1" + "php": "~7.1 || ^8.0" }, "conflict": { "doctrine/common": ">2.2,<2.4" }, "require-dev": { "alcaeus/mongo-php-adapter": "^1.1", - "doctrine/coding-standard": "^4.0", + "doctrine/coding-standard": "^6.0", "mongodb/mongodb": "^1.1", "phpunit/phpunit": "^7.0", "predis/predis": "~1.0" @@ -265,7 +319,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.8.x-dev" + "dev-master": "1.9.x-dev" } }, "autoload": { @@ -278,6 +332,10 @@ "MIT" ], "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, { "name": "Roman Borschel", "email": "roman@code-factory.org" @@ -286,10 +344,6 @@ "name": "Benjamin Eberlei", "email": "kontakt@beberlei.de" }, - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, { "name": "Jonathan Wage", "email": "jonwage@gmail.com" @@ -299,42 +353,63 @@ "email": "schmittjoh@gmail.com" } ], - "description": "Caching library offering an object-oriented API for many cache backends", - "homepage": "https://www.doctrine-project.org", + "description": "PHP Doctrine Cache library is a popular cache implementation that supports many different drivers such as redis, memcache, apc, mongodb and others.", + "homepage": "https://www.doctrine-project.org/projects/cache.html", "keywords": [ + "abstraction", + "apcu", "cache", - "caching" + "caching", + "couchdb", + "memcached", + "php", + "redis", + "xcache" ], - "time": "2018-08-21T18:01:43+00:00" + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fcache", + "type": "tidelift" + } + ], + "time": "2020-05-27T16:24:54+00:00" }, { "name": "doctrine/dbal", - "version": "v2.8.0", + "version": "2.10.2", "source": { "type": "git", "url": "https://github.com/doctrine/dbal.git", - "reference": "5140a64c08b4b607b9bedaae0cedd26f04a0e621" + "reference": "aab745e7b6b2de3b47019da81e7225e14dcfdac8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/5140a64c08b4b607b9bedaae0cedd26f04a0e621", - "reference": "5140a64c08b4b607b9bedaae0cedd26f04a0e621", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/aab745e7b6b2de3b47019da81e7225e14dcfdac8", + "reference": "aab745e7b6b2de3b47019da81e7225e14dcfdac8", "shasum": "" }, "require": { "doctrine/cache": "^1.0", "doctrine/event-manager": "^1.0", "ext-pdo": "*", - "php": "^7.1" + "php": "^7.2" }, "require-dev": { - "doctrine/coding-standard": "^4.0", - "jetbrains/phpstorm-stubs": "^2018.1.2", - "phpstan/phpstan": "^0.10.1", - "phpunit/phpunit": "^7.1.2", - "phpunit/phpunit-mock-objects": "!=3.2.4,!=3.2.5", - "symfony/console": "^2.0.5|^3.0|^4.0", - "symfony/phpunit-bridge": "^3.4.5|^4.0.5" + "doctrine/coding-standard": "^6.0", + "jetbrains/phpstorm-stubs": "^2019.1", + "nikic/php-parser": "^4.4", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^8.4.1", + "symfony/console": "^2.0.5|^3.0|^4.0|^5.0", + "vimeo/psalm": "^3.11" }, "suggest": { "symfony/console": "For helpful console commands such as SQL execution and import of files." @@ -345,13 +420,13 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.8.x-dev", + "dev-master": "2.10.x-dev", "dev-develop": "3.0.x-dev" } }, "autoload": { - "psr-0": { - "Doctrine\\DBAL\\": "lib/" + "psr-4": { + "Doctrine\\DBAL\\": "lib/Doctrine/DBAL" } }, "notification-url": "https://packagist.org/downloads/", @@ -359,6 +434,10 @@ "MIT" ], "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, { "name": "Roman Borschel", "email": "roman@code-factory.org" @@ -367,37 +446,62 @@ "name": "Benjamin Eberlei", "email": "kontakt@beberlei.de" }, - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, { "name": "Jonathan Wage", "email": "jonwage@gmail.com" } ], - "description": "Database Abstraction Layer", - "homepage": "http://www.doctrine-project.org", + "description": "Powerful PHP database abstraction layer (DBAL) with many features for database schema introspection and management.", + "homepage": "https://www.doctrine-project.org/projects/dbal.html", "keywords": [ + "abstraction", "database", + "db2", "dbal", - "persistence", - "queryobject" + "mariadb", + "mssql", + "mysql", + "oci8", + "oracle", + "pdo", + "pgsql", + "postgresql", + "queryobject", + "sasql", + "sql", + "sqlanywhere", + "sqlite", + "sqlserver", + "sqlsrv" ], - "time": "2018-07-13T03:16:35+00:00" + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fdbal", + "type": "tidelift" + } + ], + "time": "2020-04-20T17:19:26+00:00" }, { "name": "doctrine/event-manager", - "version": "v1.0.0", + "version": "1.1.0", "source": { "type": "git", "url": "https://github.com/doctrine/event-manager.git", - "reference": "a520bc093a0170feeb6b14e9d83f3a14452e64b3" + "reference": "629572819973f13486371cb611386eb17851e85c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/event-manager/zipball/a520bc093a0170feeb6b14e9d83f3a14452e64b3", - "reference": "a520bc093a0170feeb6b14e9d83f3a14452e64b3", + "url": "https://api.github.com/repos/doctrine/event-manager/zipball/629572819973f13486371cb611386eb17851e85c", + "reference": "629572819973f13486371cb611386eb17851e85c", "shasum": "" }, "require": { @@ -407,7 +511,7 @@ "doctrine/common": "<2.9@dev" }, "require-dev": { - "doctrine/coding-standard": "^4.0", + "doctrine/coding-standard": "^6.0", "phpunit/phpunit": "^7.0" }, "type": "library", @@ -426,6 +530,10 @@ "MIT" ], "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, { "name": "Roman Borschel", "email": "roman@code-factory.org" @@ -434,10 +542,6 @@ "name": "Benjamin Eberlei", "email": "kontakt@beberlei.de" }, - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, { "name": "Jonathan Wage", "email": "jonwage@gmail.com" @@ -451,44 +555,50 @@ "email": "ocramius@gmail.com" } ], - "description": "Doctrine Event Manager component", + "description": "The Doctrine Event Manager is a simple PHP event system that was built to be used with the various Doctrine projects.", "homepage": "https://www.doctrine-project.org/projects/event-manager.html", "keywords": [ "event", - "eventdispatcher", - "eventmanager" + "event dispatcher", + "event manager", + "event system", + "events" ], - "time": "2018-06-11T11:59:03+00:00" + "time": "2019-11-10T09:48:07+00:00" }, { "name": "doctrine/inflector", - "version": "v1.3.0", + "version": "2.0.3", "source": { "type": "git", "url": "https://github.com/doctrine/inflector.git", - "reference": "5527a48b7313d15261292c149e55e26eae771b0a" + "reference": "9cf661f4eb38f7c881cac67c75ea9b00bf97b210" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/inflector/zipball/5527a48b7313d15261292c149e55e26eae771b0a", - "reference": "5527a48b7313d15261292c149e55e26eae771b0a", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/9cf661f4eb38f7c881cac67c75ea9b00bf97b210", + "reference": "9cf661f4eb38f7c881cac67c75ea9b00bf97b210", "shasum": "" }, "require": { - "php": "^7.1" + "php": "^7.2 || ^8.0" }, "require-dev": { - "phpunit/phpunit": "^6.2" + "doctrine/coding-standard": "^7.0", + "phpstan/phpstan": "^0.11", + "phpstan/phpstan-phpunit": "^0.11", + "phpstan/phpstan-strict-rules": "^0.11", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.3.x-dev" + "dev-master": "2.0.x-dev" } }, "autoload": { "psr-4": { - "Doctrine\\Common\\Inflector\\": "lib/Doctrine/Common/Inflector" + "Doctrine\\Inflector\\": "lib/Doctrine/Inflector" } }, "notification-url": "https://packagist.org/downloads/", @@ -496,6 +606,10 @@ "MIT" ], "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, { "name": "Roman Borschel", "email": "roman@code-factory.org" @@ -504,10 +618,6 @@ "name": "Benjamin Eberlei", "email": "kontakt@beberlei.de" }, - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, { "name": "Jonathan Wage", "email": "jonwage@gmail.com" @@ -517,42 +627,67 @@ "email": "schmittjoh@gmail.com" } ], - "description": "Common String Manipulations with regard to casing and singular/plural rules.", - "homepage": "http://www.doctrine-project.org", + "description": "PHP Doctrine Inflector is a small library that can perform string manipulations with regard to upper/lowercase and singular/plural forms of words.", + "homepage": "https://www.doctrine-project.org/projects/inflector.html", "keywords": [ "inflection", - "pluralize", - "singularize", - "string" + "inflector", + "lowercase", + "manipulation", + "php", + "plural", + "singular", + "strings", + "uppercase", + "words" ], - "time": "2018-01-09T20:05:19+00:00" + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finflector", + "type": "tidelift" + } + ], + "time": "2020-05-29T15:13:26+00:00" }, { "name": "doctrine/lexer", - "version": "v1.0.1", + "version": "1.2.1", "source": { "type": "git", "url": "https://github.com/doctrine/lexer.git", - "reference": "83893c552fd2045dd78aef794c31e694c37c0b8c" + "reference": "e864bbf5904cb8f5bb334f99209b48018522f042" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/lexer/zipball/83893c552fd2045dd78aef794c31e694c37c0b8c", - "reference": "83893c552fd2045dd78aef794c31e694c37c0b8c", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/e864bbf5904cb8f5bb334f99209b48018522f042", + "reference": "e864bbf5904cb8f5bb334f99209b48018522f042", "shasum": "" }, "require": { - "php": ">=5.3.2" + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^6.0", + "phpstan/phpstan": "^0.11.8", + "phpunit/phpunit": "^8.2" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "1.2.x-dev" } }, "autoload": { - "psr-0": { - "Doctrine\\Common\\Lexer\\": "lib/" + "psr-4": { + "Doctrine\\Common\\Lexer\\": "lib/Doctrine/Common/Lexer" } }, "notification-url": "https://packagist.org/downloads/", @@ -560,48 +695,70 @@ "MIT" ], "authors": [ - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, { "name": "Guilherme Blanco", "email": "guilhermeblanco@gmail.com" }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, { "name": "Johannes Schmitt", "email": "schmittjoh@gmail.com" } ], - "description": "Base library for a lexer that can be used in Top-Down, Recursive Descent Parsers.", - "homepage": "http://www.doctrine-project.org", + "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.", + "homepage": "https://www.doctrine-project.org/projects/lexer.html", "keywords": [ + "annotations", + "docblock", "lexer", - "parser" + "parser", + "php" ], - "time": "2014-09-09T13:34:57+00:00" + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Flexer", + "type": "tidelift" + } + ], + "time": "2020-05-25T17:44:05+00:00" }, { "name": "dragonmantank/cron-expression", - "version": "v2.2.0", + "version": "v2.3.0", "source": { "type": "git", "url": "https://github.com/dragonmantank/cron-expression.git", - "reference": "92a2c3768d50e21a1f26a53cb795ce72806266c5" + "reference": "72b6fbf76adb3cf5bc0db68559b33d41219aba27" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dragonmantank/cron-expression/zipball/92a2c3768d50e21a1f26a53cb795ce72806266c5", - "reference": "92a2c3768d50e21a1f26a53cb795ce72806266c5", + "url": "https://api.github.com/repos/dragonmantank/cron-expression/zipball/72b6fbf76adb3cf5bc0db68559b33d41219aba27", + "reference": "72b6fbf76adb3cf5bc0db68559b33d41219aba27", "shasum": "" }, "require": { - "php": ">=7.0.0" + "php": "^7.0" }, "require-dev": { - "phpunit/phpunit": "~6.4" + "phpunit/phpunit": "^6.4|^7.0" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev" + } + }, "autoload": { "psr-4": { "Cron\\": "src/Cron/" @@ -628,29 +785,30 @@ "cron", "schedule" ], - "time": "2018-06-06T03:12:17+00:00" + "time": "2019-03-31T00:38:28+00:00" }, { "name": "egulias/email-validator", - "version": "2.1.6", + "version": "2.1.18", "source": { "type": "git", "url": "https://github.com/egulias/EmailValidator.git", - "reference": "0578b32b30b22de3e8664f797cf846fc9246f786" + "reference": "cfa3d44471c7f5bfb684ac2b0da7114283d78441" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/0578b32b30b22de3e8664f797cf846fc9246f786", - "reference": "0578b32b30b22de3e8664f797cf846fc9246f786", + "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/cfa3d44471c7f5bfb684ac2b0da7114283d78441", + "reference": "cfa3d44471c7f5bfb684ac2b0da7114283d78441", "shasum": "" }, "require": { "doctrine/lexer": "^1.0.1", - "php": ">= 5.5" + "php": ">=5.5", + "symfony/polyfill-intl-idn": "^1.10" }, "require-dev": { - "dominicsayers/isemail": "dev-master", - "phpunit/phpunit": "^4.8.35||^5.7||^6.0", + "dominicsayers/isemail": "^3.0.7", + "phpunit/phpunit": "^4.8.36|^7.5.15", "satooshi/php-coveralls": "^1.0.1" }, "suggest": { @@ -659,12 +817,12 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-master": "2.1.x-dev" } }, "autoload": { "psr-4": { - "Egulias\\EmailValidator\\": "EmailValidator" + "Egulias\\EmailValidator\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -685,75 +843,29 @@ "validation", "validator" ], - "time": "2018-09-25T20:47:26+00:00" - }, - { - "name": "erusev/parsedown", - "version": "1.7.1", - "source": { - "type": "git", - "url": "https://github.com/erusev/parsedown.git", - "reference": "92e9c27ba0e74b8b028b111d1b6f956a15c01fc1" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/erusev/parsedown/zipball/92e9c27ba0e74b8b028b111d1b6f956a15c01fc1", - "reference": "92e9c27ba0e74b8b028b111d1b6f956a15c01fc1", - "shasum": "" - }, - "require": { - "ext-mbstring": "*", - "php": ">=5.3.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.8.35" - }, - "type": "library", - "autoload": { - "psr-0": { - "Parsedown": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Emanuil Rusev", - "email": "hello@erusev.com", - "homepage": "http://erusev.com" - } - ], - "description": "Parser for Markdown.", - "homepage": "http://parsedown.org", - "keywords": [ - "markdown", - "parser" - ], - "time": "2018-03-08T01:11:30+00:00" + "time": "2020-06-16T20:11:17+00:00" }, { "name": "fideloper/proxy", - "version": "4.0.0", + "version": "4.4.0", "source": { "type": "git", "url": "https://github.com/fideloper/TrustedProxy.git", - "reference": "cf8a0ca4b85659b9557e206c90110a6a4dba980a" + "reference": "9beebf48a1c344ed67c1d36bb1b8709db7c3c1a8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/fideloper/TrustedProxy/zipball/cf8a0ca4b85659b9557e206c90110a6a4dba980a", - "reference": "cf8a0ca4b85659b9557e206c90110a6a4dba980a", + "url": "https://api.github.com/repos/fideloper/TrustedProxy/zipball/9beebf48a1c344ed67c1d36bb1b8709db7c3c1a8", + "reference": "9beebf48a1c344ed67c1d36bb1b8709db7c3c1a8", "shasum": "" }, "require": { - "illuminate/contracts": "~5.0", + "illuminate/contracts": "^5.0|^6.0|^7.0|^8.0", "php": ">=5.4.0" }, "require-dev": { - "illuminate/http": "~5.6", - "mockery/mockery": "~1.0", + "illuminate/http": "^5.0|^6.0|^7.0|^8.0", + "mockery/mockery": "^1.0", "phpunit/phpunit": "^6.0" }, "type": "library", @@ -785,31 +897,33 @@ "proxy", "trusted proxy" ], - "time": "2018-02-07T20:20:57+00:00" + "time": "2020-06-23T01:36:47+00:00" }, { "name": "guzzlehttp/guzzle", - "version": "6.3.3", + "version": "6.5.5", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "407b0cb880ace85c9b63c5f9551db498cb2d50ba" + "reference": "9d4290de1cfd701f38099ef7e183b64b4b7b0c5e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/407b0cb880ace85c9b63c5f9551db498cb2d50ba", - "reference": "407b0cb880ace85c9b63c5f9551db498cb2d50ba", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/9d4290de1cfd701f38099ef7e183b64b4b7b0c5e", + "reference": "9d4290de1cfd701f38099ef7e183b64b4b7b0c5e", "shasum": "" }, "require": { + "ext-json": "*", "guzzlehttp/promises": "^1.0", - "guzzlehttp/psr7": "^1.4", - "php": ">=5.5" + "guzzlehttp/psr7": "^1.6.1", + "php": ">=5.5", + "symfony/polyfill-intl-idn": "^1.17.0" }, "require-dev": { "ext-curl": "*", "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.4 || ^7.0", - "psr/log": "^1.0" + "psr/log": "^1.1" }, "suggest": { "psr/log": "Required for using the Log middleware" @@ -817,16 +931,16 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "6.3-dev" + "dev-master": "6.5-dev" } }, "autoload": { - "files": [ - "src/functions_include.php" - ], "psr-4": { "GuzzleHttp\\": "src/" - } + }, + "files": [ + "src/functions_include.php" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -850,7 +964,7 @@ "rest", "web service" ], - "time": "2018-04-22T15:46:56+00:00" + "time": "2020-06-16T21:01:06+00:00" }, { "name": "guzzlehttp/promises", @@ -905,32 +1019,37 @@ }, { "name": "guzzlehttp/psr7", - "version": "1.4.2", + "version": "1.6.1", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c" + "reference": "239400de7a173fe9901b9ac7c06497751f00727a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/f5b8a8512e2b58b0071a7280e39f14f72e05d87c", - "reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/239400de7a173fe9901b9ac7c06497751f00727a", + "reference": "239400de7a173fe9901b9ac7c06497751f00727a", "shasum": "" }, "require": { "php": ">=5.4.0", - "psr/http-message": "~1.0" + "psr/http-message": "~1.0", + "ralouphie/getallheaders": "^2.0.5 || ^3.0.0" }, "provide": { "psr/http-message-implementation": "1.0" }, "require-dev": { - "phpunit/phpunit": "~4.0" + "ext-zlib": "*", + "phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.8" + }, + "suggest": { + "zendframework/zend-httphandlerrunner": "Emit PSR-7 responses" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.4-dev" + "dev-master": "1.6-dev" } }, "autoload": { @@ -960,33 +1079,35 @@ "keywords": [ "http", "message", + "psr-7", "request", "response", "stream", "uri", "url" ], - "time": "2017-03-20T17:10:46+00:00" + "time": "2019-07-01T23:21:34+00:00" }, { "name": "hashids/hashids", - "version": "3.0.0", + "version": "4.0.0", "source": { "type": "git", "url": "https://github.com/vinkla/hashids.git", - "reference": "b6c61142bfe36d43740a5419d11c351dddac0458" + "reference": "43bb2407f16a631f0128f47bcb67ff986c63dde2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vinkla/hashids/zipball/b6c61142bfe36d43740a5419d11c351dddac0458", - "reference": "b6c61142bfe36d43740a5419d11c351dddac0458", + "url": "https://api.github.com/repos/vinkla/hashids/zipball/43bb2407f16a631f0128f47bcb67ff986c63dde2", + "reference": "43bb2407f16a631f0128f47bcb67ff986c63dde2", "shasum": "" }, "require": { - "php": "^7.1.3" + "ext-mbstring": "*", + "php": "^7.2" }, "require-dev": { - "phpunit/phpunit": "^7.0" + "phpunit/phpunit": "^8.0" }, "suggest": { "ext-bcmath": "Required to use BC Math arbitrary precision mathematics (*).", @@ -995,7 +1116,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-master": "4.0-dev" } }, "autoload": { @@ -1015,8 +1136,8 @@ }, { "name": "Vincent Klaiber", - "email": "hello@vinkla.com", - "homepage": "https://vinkla.com" + "email": "hello@doubledip.se", + "homepage": "https://doubledip.se" } ], "description": "Generate short, unique, non-sequential ids (like YouTube and Bitly) from numbers", @@ -1032,176 +1153,25 @@ "obfuscate", "youtube" ], - "time": "2018-03-12T16:30:09+00:00" - }, - { - "name": "igaster/laravel-theme", - "version": "v2.0.9", - "source": { - "type": "git", - "url": "https://github.com/igaster/laravel-theme.git", - "reference": "c0b93dfcfac3602d6d224ad79f76795f6918f4c1" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/igaster/laravel-theme/zipball/c0b93dfcfac3602d6d224ad79f76795f6918f4c1", - "reference": "c0b93dfcfac3602d6d224ad79f76795f6918f4c1", - "shasum": "" - }, - "require": { - "illuminate/contracts": "5.4.*|5.5.*|5.6.*|5.7.*" - }, - "require-dev": { - "orchestra/testbench": "~3.4", - "phpunit/phpunit": "^6.0" - }, - "suggest": { - "orchestra/asset": "Use '@css' and '@js' in Blade files" - }, - "type": "library", - "extra": { - "laravel": { - "providers": [ - "Igaster\\LaravelTheme\\themeServiceProvider" - ], - "aliases": { - "Theme": "Igaster\\LaravelTheme\\Facades\\Theme" - } - } - }, - "autoload": { - "psr-4": { - "Igaster\\LaravelTheme\\": "src/", - "Igaster\\LaravelTheme\\Tests\\": "tests/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Giannis Gasteratos", - "email": "igasteratos@gmail.com" - } - ], - "description": "Laravel 5 Themes: Asset & Views folder per theme. Theme inheritance. Blade integration and more...", - "homepage": "https://github.com/Igaster/laravel-theme.git", - "keywords": [ - "assets", - "blade", - "laravel-5", - "package", - "themes", - "views" - ], - "time": "2018-09-17T07:57:42+00:00" - }, - { - "name": "jakub-onderka/php-console-color", - "version": "v0.2", - "source": { - "type": "git", - "url": "https://github.com/JakubOnderka/PHP-Console-Color.git", - "reference": "d5deaecff52a0d61ccb613bb3804088da0307191" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/JakubOnderka/PHP-Console-Color/zipball/d5deaecff52a0d61ccb613bb3804088da0307191", - "reference": "d5deaecff52a0d61ccb613bb3804088da0307191", - "shasum": "" - }, - "require": { - "php": ">=5.4.0" - }, - "require-dev": { - "jakub-onderka/php-code-style": "1.0", - "jakub-onderka/php-parallel-lint": "1.0", - "jakub-onderka/php-var-dump-check": "0.*", - "phpunit/phpunit": "~4.3", - "squizlabs/php_codesniffer": "1.*" - }, - "type": "library", - "autoload": { - "psr-4": { - "JakubOnderka\\PhpConsoleColor\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-2-Clause" - ], - "authors": [ - { - "name": "Jakub Onderka", - "email": "jakub.onderka@gmail.com" - } - ], - "time": "2018-09-29T17:23:10+00:00" - }, - { - "name": "jakub-onderka/php-console-highlighter", - "version": "v0.4", - "source": { - "type": "git", - "url": "https://github.com/JakubOnderka/PHP-Console-Highlighter.git", - "reference": "9f7a229a69d52506914b4bc61bfdb199d90c5547" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/JakubOnderka/PHP-Console-Highlighter/zipball/9f7a229a69d52506914b4bc61bfdb199d90c5547", - "reference": "9f7a229a69d52506914b4bc61bfdb199d90c5547", - "shasum": "" - }, - "require": { - "ext-tokenizer": "*", - "jakub-onderka/php-console-color": "~0.2", - "php": ">=5.4.0" - }, - "require-dev": { - "jakub-onderka/php-code-style": "~1.0", - "jakub-onderka/php-parallel-lint": "~1.0", - "jakub-onderka/php-var-dump-check": "~0.1", - "phpunit/phpunit": "~4.0", - "squizlabs/php_codesniffer": "~1.5" - }, - "type": "library", - "autoload": { - "psr-4": { - "JakubOnderka\\PhpConsoleHighlighter\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jakub Onderka", - "email": "acci@acci.cz", - "homepage": "http://www.acci.cz/" - } - ], - "description": "Highlight PHP code in terminal", - "time": "2018-09-29T18:48:56+00:00" + "time": "2019-04-03T13:40:29+00:00" }, { "name": "laracasts/utilities", - "version": "3.0", + "version": "3.1", "source": { "type": "git", "url": "https://github.com/laracasts/PHP-Vars-To-Js-Transformer.git", - "reference": "298fb3c6f29901a4550c4f98b57c05f368341d04" + "reference": "7c5eb11221de608eef8c70c2d3540c8cd80466e3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laracasts/PHP-Vars-To-Js-Transformer/zipball/298fb3c6f29901a4550c4f98b57c05f368341d04", - "reference": "298fb3c6f29901a4550c4f98b57c05f368341d04", + "url": "https://api.github.com/repos/laracasts/PHP-Vars-To-Js-Transformer/zipball/7c5eb11221de608eef8c70c2d3540c8cd80466e3", + "reference": "7c5eb11221de608eef8c70c2d3540c8cd80466e3", "shasum": "" }, "require": { - "illuminate/support": "~5.0", - "php": ">=5.4.0" + "illuminate/support": "^5.0|^6.0|^7.0", + "php": ">=5.5.0|>=7.2.5" }, "require-dev": { "phpspec/phpspec": "~2.0" @@ -1240,51 +1210,59 @@ "javascript", "laravel" ], - "time": "2017-09-01T17:25:57+00:00" + "time": "2020-03-03T16:07:08+00:00" }, { "name": "laravel/framework", - "version": "v5.7.14", + "version": "v7.17.1", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "ad6c1fe1e455c0f73a431928282704879ccbd856" + "reference": "6633c4017b311d3aaae842edabd567c637afc39c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/ad6c1fe1e455c0f73a431928282704879ccbd856", - "reference": "ad6c1fe1e455c0f73a431928282704879ccbd856", + "url": "https://api.github.com/repos/laravel/framework/zipball/6633c4017b311d3aaae842edabd567c637afc39c", + "reference": "6633c4017b311d3aaae842edabd567c637afc39c", "shasum": "" }, "require": { - "doctrine/inflector": "^1.1", + "doctrine/inflector": "^1.4|^2.0", "dragonmantank/cron-expression": "^2.0", - "erusev/parsedown": "^1.7", + "egulias/email-validator": "^2.1.10", + "ext-json": "*", "ext-mbstring": "*", "ext-openssl": "*", - "league/flysystem": "^1.0.8", - "monolog/monolog": "^1.12", - "nesbot/carbon": "^1.26.3", + "league/commonmark": "^1.3", + "league/flysystem": "^1.0.34", + "monolog/monolog": "^2.0", + "nesbot/carbon": "^2.17", "opis/closure": "^3.1", - "php": "^7.1.3", + "php": "^7.2.5", "psr/container": "^1.0", "psr/simple-cache": "^1.0", - "ramsey/uuid": "^3.7", + "ramsey/uuid": "^3.7|^4.0", "swiftmailer/swiftmailer": "^6.0", - "symfony/console": "^4.1", - "symfony/debug": "^4.1", - "symfony/finder": "^4.1", - "symfony/http-foundation": "^4.1", - "symfony/http-kernel": "^4.1", - "symfony/process": "^4.1", - "symfony/routing": "^4.1", - "symfony/var-dumper": "^4.1", - "tijsverkoyen/css-to-inline-styles": "^2.2.1", - "vlucas/phpdotenv": "^2.2" + "symfony/console": "^5.0", + "symfony/error-handler": "^5.0", + "symfony/finder": "^5.0", + "symfony/http-foundation": "^5.0", + "symfony/http-kernel": "^5.0", + "symfony/mime": "^5.0", + "symfony/polyfill-php73": "^1.17", + "symfony/process": "^5.0", + "symfony/routing": "^5.0", + "symfony/var-dumper": "^5.0", + "tijsverkoyen/css-to-inline-styles": "^2.2.2", + "vlucas/phpdotenv": "^4.0", + "voku/portable-ascii": "^1.4.8" }, "conflict": { "tightenco/collect": "<5.5.33" }, + "provide": { + "psr/container-implementation": "1.0" + }, "replace": { "illuminate/auth": "self.version", "illuminate/broadcasting": "self.version", @@ -1311,6 +1289,7 @@ "illuminate/routing": "self.version", "illuminate/session": "self.version", "illuminate/support": "self.version", + "illuminate/testing": "self.version", "illuminate/translation": "self.version", "illuminate/validation": "self.version", "illuminate/view": "self.version" @@ -1318,45 +1297,49 @@ "require-dev": { "aws/aws-sdk-php": "^3.0", "doctrine/dbal": "^2.6", - "filp/whoops": "^2.1.4", - "guzzlehttp/guzzle": "^6.3", + "filp/whoops": "^2.4", + "guzzlehttp/guzzle": "^6.3.1|^7.0", "league/flysystem-cached-adapter": "^1.0", - "mockery/mockery": "^1.0", + "mockery/mockery": "^1.3.1", "moontoast/math": "^1.1", - "orchestra/testbench-core": "3.7.*", - "pda/pheanstalk": "^3.0", - "phpunit/phpunit": "^7.0", + "orchestra/testbench-core": "^5.0", + "pda/pheanstalk": "^4.0", + "phpunit/phpunit": "^8.4|^9.0", "predis/predis": "^1.1.1", - "symfony/css-selector": "^4.1", - "symfony/dom-crawler": "^4.1", - "true/punycode": "^2.1" + "symfony/cache": "^5.0" }, "suggest": { - "aws/aws-sdk-php": "Required to use the SQS queue driver and SES mail driver (^3.0).", + "aws/aws-sdk-php": "Required to use the SQS queue driver, DynamoDb failed job storage and SES mail driver (^3.0).", "doctrine/dbal": "Required to rename columns and drop SQLite columns (^2.6).", + "ext-ftp": "Required to use the Flysystem FTP driver.", + "ext-gd": "Required to use Illuminate\\Http\\Testing\\FileFactory::image().", + "ext-memcached": "Required to use the memcache cache driver.", "ext-pcntl": "Required to use all features of the queue worker.", "ext-posix": "Required to use all features of the queue worker.", - "filp/whoops": "Required for friendly error pages in development (^2.1.4).", - "fzaninotto/faker": "Required to use the eloquent factory builder (^1.4).", - "guzzlehttp/guzzle": "Required to use the Mailgun and Mandrill mail drivers and the ping methods on schedules (^6.0).", - "laravel/tinker": "Required to use the tinker console command (^1.0).", + "ext-redis": "Required to use the Redis cache and queue drivers (^4.0|^5.0).", + "filp/whoops": "Required for friendly error pages in development (^2.4).", + "fzaninotto/faker": "Required to use the eloquent factory builder (^1.9.1).", + "guzzlehttp/guzzle": "Required to use the HTTP Client, Mailgun mail driver and the ping methods on schedules (^6.3.1|^7.0).", + "laravel/tinker": "Required to use the tinker console command (^2.0).", "league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (^1.0).", "league/flysystem-cached-adapter": "Required to use the Flysystem cache (^1.0).", - "league/flysystem-rackspace": "Required to use the Flysystem Rackspace driver (^1.0).", "league/flysystem-sftp": "Required to use the Flysystem SFTP driver (^1.0).", + "mockery/mockery": "Required to use mocking (^1.3.1).", "moontoast/math": "Required to use ordered UUIDs (^1.1).", - "nexmo/client": "Required to use the Nexmo transport (^1.0).", - "pda/pheanstalk": "Required to use the beanstalk queue driver (^3.0).", - "predis/predis": "Required to use the redis cache and queue drivers (^1.0).", - "pusher/pusher-php-server": "Required to use the Pusher broadcast driver (^3.0).", - "symfony/css-selector": "Required to use some of the crawler integration testing tools (^4.1).", - "symfony/dom-crawler": "Required to use most of the crawler integration testing tools (^4.1).", - "symfony/psr-http-message-bridge": "Required to psr7 bridging features (^1.0)." + "nyholm/psr7": "Required to use PSR-7 bridging features (^1.2).", + "pda/pheanstalk": "Required to use the beanstalk queue driver (^4.0).", + "phpunit/phpunit": "Required to use assertions and run tests (^8.4|^9.0).", + "psr/http-message": "Required to allow Storage::put to accept a StreamInterface (^1.0).", + "pusher/pusher-php-server": "Required to use the Pusher broadcast driver (^4.0).", + "symfony/cache": "Required to PSR-6 cache bridge (^5.0).", + "symfony/filesystem": "Required to create relative storage directory symbolic links (^5.0).", + "symfony/psr-http-message-bridge": "Required to use PSR-7 bridging features (^2.0).", + "wildbit/swiftmailer-postmark": "Required to use Postmark mail driver (^3.0)." }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.7-dev" + "dev-master": "7.x-dev" } }, "autoload": { @@ -1384,40 +1367,94 @@ "framework", "laravel" ], - "time": "2018-11-21T13:46:08+00:00" + "time": "2020-06-23T15:22:07+00:00" }, { - "name": "laravel/tinker", - "version": "v1.0.8", + "name": "laravel/helpers", + "version": "v1.2.0", "source": { "type": "git", - "url": "https://github.com/laravel/tinker.git", - "reference": "cafbf598a90acde68985660e79b2b03c5609a405" + "url": "https://github.com/laravel/helpers.git", + "reference": "1f978fc5dad9f7f906b18242c654252615201de4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/tinker/zipball/cafbf598a90acde68985660e79b2b03c5609a405", - "reference": "cafbf598a90acde68985660e79b2b03c5609a405", + "url": "https://api.github.com/repos/laravel/helpers/zipball/1f978fc5dad9f7f906b18242c654252615201de4", + "reference": "1f978fc5dad9f7f906b18242c654252615201de4", "shasum": "" }, "require": { - "illuminate/console": "~5.1", - "illuminate/contracts": "~5.1", - "illuminate/support": "~5.1", - "php": ">=5.5.9", - "psy/psysh": "0.7.*|0.8.*|0.9.*", - "symfony/var-dumper": "~3.0|~4.0" + "illuminate/support": "~5.8.0|^6.0|^7.0", + "php": ">=7.1.3" }, "require-dev": { - "phpunit/phpunit": "~4.0|~5.0" - }, - "suggest": { - "illuminate/database": "The Illuminate Database package (~5.1)." + "phpunit/phpunit": "^7.0" }, "type": "library", "extra": { "branch-alias": { "dev-master": "1.0-dev" + } + }, + "autoload": { + "files": [ + "src/helpers.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + }, + { + "name": "Dries Vints", + "email": "dries.vints@gmail.com" + } + ], + "description": "Provides backwards compatibility for helpers in the latest Laravel release.", + "keywords": [ + "helpers", + "laravel" + ], + "time": "2020-03-03T13:52:16+00:00" + }, + { + "name": "laravel/tinker", + "version": "v2.4.0", + "source": { + "type": "git", + "url": "https://github.com/laravel/tinker.git", + "reference": "cde90a7335a2130a4488beb68f4b2141869241db" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/tinker/zipball/cde90a7335a2130a4488beb68f4b2141869241db", + "reference": "cde90a7335a2130a4488beb68f4b2141869241db", + "shasum": "" + }, + "require": { + "illuminate/console": "^6.0|^7.0|^8.0", + "illuminate/contracts": "^6.0|^7.0|^8.0", + "illuminate/support": "^6.0|^7.0|^8.0", + "php": "^7.2", + "psy/psysh": "^0.10.3", + "symfony/var-dumper": "^4.3|^5.0" + }, + "require-dev": { + "mockery/mockery": "^1.3.1", + "phpunit/phpunit": "^8.4|^9.0" + }, + "suggest": { + "illuminate/database": "The Illuminate Database package (^6.0|^7.0|^8.0)." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" }, "laravel": { "providers": [ @@ -1447,20 +1484,235 @@ "laravel", "psysh" ], - "time": "2018-10-12T19:39:35+00:00" + "time": "2020-04-07T15:01:31+00:00" }, { - "name": "league/flysystem", - "version": "1.0.49", + "name": "laravel/ui", + "version": "v2.0.3", "source": { "type": "git", - "url": "https://github.com/thephpleague/flysystem.git", - "reference": "a63cc83d8a931b271be45148fa39ba7156782ffd" + "url": "https://github.com/laravel/ui.git", + "reference": "15368c5328efb7ce94f35ca750acde9b496ab1b1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/a63cc83d8a931b271be45148fa39ba7156782ffd", - "reference": "a63cc83d8a931b271be45148fa39ba7156782ffd", + "url": "https://api.github.com/repos/laravel/ui/zipball/15368c5328efb7ce94f35ca750acde9b496ab1b1", + "reference": "15368c5328efb7ce94f35ca750acde9b496ab1b1", + "shasum": "" + }, + "require": { + "illuminate/console": "^7.0", + "illuminate/filesystem": "^7.0", + "illuminate/support": "^7.0", + "php": "^7.2.5" + }, + "require-dev": { + "mockery/mockery": "^1.0", + "phpunit/phpunit": "^8.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Laravel\\Ui\\UiServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Laravel\\Ui\\": "src/", + "Illuminate\\Foundation\\Auth\\": "auth-backend/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "Laravel UI utilities and presets.", + "keywords": [ + "laravel", + "ui" + ], + "time": "2020-04-29T15:06:45+00:00" + }, + { + "name": "lcobucci/jwt", + "version": "3.3.2", + "source": { + "type": "git", + "url": "https://github.com/lcobucci/jwt.git", + "reference": "56f10808089e38623345e28af2f2d5e4eb579455" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/lcobucci/jwt/zipball/56f10808089e38623345e28af2f2d5e4eb579455", + "reference": "56f10808089e38623345e28af2f2d5e4eb579455", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "ext-openssl": "*", + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "mikey179/vfsstream": "~1.5", + "phpmd/phpmd": "~2.2", + "phpunit/php-invoker": "~1.1", + "phpunit/phpunit": "^5.7 || ^7.3", + "squizlabs/php_codesniffer": "~2.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + }, + "autoload": { + "psr-4": { + "Lcobucci\\JWT\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Luís Otávio Cobucci Oblonczyk", + "email": "lcobucci@gmail.com", + "role": "Developer" + } + ], + "description": "A simple library to work with JSON Web Token and JSON Web Signature", + "keywords": [ + "JWS", + "jwt" + ], + "funding": [ + { + "url": "https://github.com/lcobucci", + "type": "github" + }, + { + "url": "https://www.patreon.com/lcobucci", + "type": "patreon" + } + ], + "time": "2020-05-22T08:21:12+00:00" + }, + { + "name": "league/commonmark", + "version": "1.5.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/commonmark.git", + "reference": "fc33ca12575e98e57cdce7d5f38b2ca5335714b3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/fc33ca12575e98e57cdce7d5f38b2ca5335714b3", + "reference": "fc33ca12575e98e57cdce7d5f38b2ca5335714b3", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": "^7.1 || ^8.0" + }, + "conflict": { + "scrutinizer/ocular": "1.7.*" + }, + "require-dev": { + "cebe/markdown": "~1.0", + "commonmark/commonmark.js": "0.29.1", + "erusev/parsedown": "~1.0", + "ext-json": "*", + "github/gfm": "0.29.0", + "michelf/php-markdown": "~1.4", + "mikehaertl/php-shellcommand": "^1.4", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^7.5", + "scrutinizer/ocular": "^1.5", + "symfony/finder": "^4.2" + }, + "bin": [ + "bin/commonmark" + ], + "type": "library", + "autoload": { + "psr-4": { + "League\\CommonMark\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com", + "role": "Lead Developer" + } + ], + "description": "Highly-extensible PHP Markdown parser which fully supports the CommonMark spec and Github-Flavored Markdown (GFM)", + "homepage": "https://commonmark.thephpleague.com", + "keywords": [ + "commonmark", + "flavored", + "gfm", + "github", + "github-flavored", + "markdown", + "md", + "parser" + ], + "funding": [ + { + "url": "https://enjoy.gitstore.app/repositories/thephpleague/commonmark", + "type": "custom" + }, + { + "url": "https://www.colinodell.com/sponsor", + "type": "custom" + }, + { + "url": "https://www.paypal.me/colinpodell/10.00", + "type": "custom" + }, + { + "url": "https://github.com/colinodell", + "type": "github" + }, + { + "url": "https://www.patreon.com/colinodell", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/league/commonmark", + "type": "tidelift" + } + ], + "time": "2020-06-21T20:50:13+00:00" + }, + { + "name": "league/flysystem", + "version": "1.0.69", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem.git", + "reference": "7106f78428a344bc4f643c233a94e48795f10967" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/7106f78428a344bc4f643c233a94e48795f10967", + "reference": "7106f78428a344bc4f643c233a94e48795f10967", "shasum": "" }, "require": { @@ -1472,7 +1724,7 @@ }, "require-dev": { "phpspec/phpspec": "^3.4", - "phpunit/phpunit": "^5.7.10" + "phpunit/phpunit": "^5.7.26" }, "suggest": { "ext-fileinfo": "Required for MimeType", @@ -1531,20 +1783,124 @@ "sftp", "storage" ], - "time": "2018-11-23T23:41:29+00:00" + "funding": [ + { + "url": "https://offset.earth/frankdejonge", + "type": "other" + } + ], + "time": "2020-05-18T15:13:39+00:00" }, { - "name": "league/fractal", - "version": "0.17.0", + "name": "league/flysystem-aws-s3-v3", + "version": "1.0.25", "source": { "type": "git", - "url": "https://github.com/thephpleague/fractal.git", - "reference": "a0b350824f22fc2fdde2500ce9d6851a3f275b0e" + "url": "https://github.com/thephpleague/flysystem-aws-s3-v3.git", + "reference": "d409b97a50bf85fbde30cbc9fc10237475e696ea" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/fractal/zipball/a0b350824f22fc2fdde2500ce9d6851a3f275b0e", - "reference": "a0b350824f22fc2fdde2500ce9d6851a3f275b0e", + "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/d409b97a50bf85fbde30cbc9fc10237475e696ea", + "reference": "d409b97a50bf85fbde30cbc9fc10237475e696ea", + "shasum": "" + }, + "require": { + "aws/aws-sdk-php": "^3.0.0", + "league/flysystem": "^1.0.40", + "php": ">=5.5.0" + }, + "require-dev": { + "henrikbjorn/phpspec-code-coverage": "~1.0.1", + "phpspec/phpspec": "^2.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Flysystem\\AwsS3v3\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frenky.net" + } + ], + "description": "Flysystem adapter for the AWS S3 SDK v3.x", + "time": "2020-06-02T18:41:58+00:00" + }, + { + "name": "league/flysystem-memory", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem-memory.git", + "reference": "d0e87477c32e29f999b4de05e64c1adcddb51757" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem-memory/zipball/d0e87477c32e29f999b4de05e64c1adcddb51757", + "reference": "d0e87477c32e29f999b4de05e64c1adcddb51757", + "shasum": "" + }, + "require": { + "league/flysystem": "~1.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7.10" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Flysystem\\Memory\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Chris Leppanen", + "email": "chris.leppanen@gmail.com", + "role": "Developer" + } + ], + "description": "An in-memory adapter for Flysystem.", + "homepage": "https://github.com/thephpleague/flysystem-memory", + "keywords": [ + "Flysystem", + "adapter", + "memory" + ], + "time": "2019-05-30T21:34:13+00:00" + }, + { + "name": "league/fractal", + "version": "0.19.2", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/fractal.git", + "reference": "06dc15f6ba38f2dde2f919d3095d13b571190a7c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/fractal/zipball/06dc15f6ba38f2dde2f919d3095d13b571190a7c", + "reference": "06dc15f6ba38f2dde2f919d3095d13b571190a7c", "shasum": "" }, "require": { @@ -1555,8 +1911,8 @@ "illuminate/contracts": "~5.0", "mockery/mockery": "~0.9", "pagerfanta/pagerfanta": "~1.0.0", - "phpunit/phpunit": "~4.0", - "squizlabs/php_codesniffer": "~1.5", + "phpunit/phpunit": "^4.8.35 || ^7.5", + "squizlabs/php_codesniffer": "~1.5|~2.0|~3.4", "zendframework/zend-paginator": "~2.3" }, "suggest": { @@ -1595,58 +1951,7 @@ "league", "rest" ], - "time": "2017-06-12T11:04:56+00:00" - }, - { - "name": "lord/laroute", - "version": "2.4.8", - "source": { - "type": "git", - "url": "https://github.com/aaronlord/laroute.git", - "reference": "7242310f02ebc3f3d6a94b00db95ad0978b1802b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/aaronlord/laroute/zipball/7242310f02ebc3f3d6a94b00db95ad0978b1802b", - "reference": "7242310f02ebc3f3d6a94b00db95ad0978b1802b", - "shasum": "" - }, - "require": { - "illuminate/config": "5.0.*|5.1.*|5.2.*|5.3.*|5.4.*|5.5.*|5.6.*|5.7.*", - "illuminate/console": "5.0.*|5.1.*|5.2.*|5.3.*|5.4.*|5.5.*|5.6.*|5.7.*", - "illuminate/filesystem": "5.0.*|5.1.*|5.2.*|5.3.*|5.4.*|5.5.*|5.6.*|5.7.*", - "illuminate/routing": "5.0.*|5.1.*|5.2.*|5.3.*|5.4.*|5.5.*|5.6.*|5.7.*", - "illuminate/support": "5.0.*|5.1.*|5.2.*|5.3.*|5.4.*|5.5.*|5.6.*|5.7.*", - "php": ">=5.4.0" - }, - "require-dev": { - "mockery/mockery": "dev-master", - "phpunit/phpunit": "~4.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Lord\\Laroute\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Aaron Lord", - "email": "hello@aaronlord.is" - } - ], - "description": "Access Laravels URL/Route helper functions, from JavaScript.", - "keywords": [ - "javascript", - "laravel", - "routes", - "routing" - ], - "time": "2018-09-11T00:19:41+00:00" + "time": "2020-01-24T23:17:29+00:00" }, { "name": "matriphe/iso-639", @@ -1694,21 +1999,21 @@ }, { "name": "monolog/monolog", - "version": "1.24.0", + "version": "2.1.0", "source": { "type": "git", "url": "https://github.com/Seldaek/monolog.git", - "reference": "bfc9ebb28f97e7a24c45bdc3f0ff482e47bb0266" + "reference": "38914429aac460e8e4616c8cb486ecb40ec90bb1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/bfc9ebb28f97e7a24c45bdc3f0ff482e47bb0266", - "reference": "bfc9ebb28f97e7a24c45bdc3f0ff482e47bb0266", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/38914429aac460e8e4616c8cb486ecb40ec90bb1", + "reference": "38914429aac460e8e4616c8cb486ecb40ec90bb1", "shasum": "" }, "require": { - "php": ">=5.3.0", - "psr/log": "~1.0" + "php": ">=7.2", + "psr/log": "^1.0.1" }, "provide": { "psr/log-implementation": "1.0.0" @@ -1716,33 +2021,36 @@ "require-dev": { "aws/aws-sdk-php": "^2.4.9 || ^3.0", "doctrine/couchdb": "~1.0@dev", - "graylog2/gelf-php": "~1.0", - "jakub-onderka/php-parallel-lint": "0.9", + "elasticsearch/elasticsearch": "^6.0", + "graylog2/gelf-php": "^1.4.2", "php-amqplib/php-amqplib": "~2.4", "php-console/php-console": "^3.1.3", - "phpunit/phpunit": "~4.5", - "phpunit/phpunit-mock-objects": "2.3.0", + "php-parallel-lint/php-parallel-lint": "^1.0", + "phpspec/prophecy": "^1.6.1", + "phpunit/phpunit": "^8.5", + "predis/predis": "^1.1", + "rollbar/rollbar": "^1.3", "ruflin/elastica": ">=0.90 <3.0", - "sentry/sentry": "^0.13", "swiftmailer/swiftmailer": "^5.3|^6.0" }, "suggest": { "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", "doctrine/couchdb": "Allow sending log messages to a CouchDB server", + "elasticsearch/elasticsearch": "Allow sending log messages to an Elasticsearch server via official client", "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", - "ext-mongo": "Allow sending log messages to a MongoDB server", + "ext-mbstring": "Allow to work properly with unicode symbols", + "ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)", "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", - "mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver", + "mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)", "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", "php-console/php-console": "Allow sending log messages to Google Chrome", "rollbar/rollbar": "Allow sending log messages to Rollbar", - "ruflin/elastica": "Allow sending log messages to an Elastic Search server", - "sentry/sentry": "Allow sending log messages to a Sentry server" + "ruflin/elastica": "Allow sending log messages to an Elastic Search server" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-master": "2.x-dev" } }, "autoload": { @@ -1768,27 +2076,39 @@ "logging", "psr-3" ], - "time": "2018-11-05T09:00:11+00:00" + "funding": [ + { + "url": "https://github.com/Seldaek", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/monolog/monolog", + "type": "tidelift" + } + ], + "time": "2020-05-22T08:12:19+00:00" }, { "name": "mtdowling/jmespath.php", - "version": "2.4.0", + "version": "2.5.0", "source": { "type": "git", "url": "https://github.com/jmespath/jmespath.php.git", - "reference": "adcc9531682cf87dfda21e1fd5d0e7a41d292fac" + "reference": "52168cb9472de06979613d365c7f1ab8798be895" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/jmespath/jmespath.php/zipball/adcc9531682cf87dfda21e1fd5d0e7a41d292fac", - "reference": "adcc9531682cf87dfda21e1fd5d0e7a41d292fac", + "url": "https://api.github.com/repos/jmespath/jmespath.php/zipball/52168cb9472de06979613d365c7f1ab8798be895", + "reference": "52168cb9472de06979613d365c7f1ab8798be895", "shasum": "" }, "require": { - "php": ">=5.4.0" + "php": ">=5.4.0", + "symfony/polyfill-mbstring": "^1.4" }, "require-dev": { - "phpunit/phpunit": "~4.0" + "composer/xdebug-handler": "^1.2", + "phpunit/phpunit": "^4.8.36|^7.5.15" }, "bin": [ "bin/jp.php" @@ -1796,7 +2116,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-master": "2.5-dev" } }, "autoload": { @@ -1823,35 +2143,46 @@ "json", "jsonpath" ], - "time": "2016-12-03T22:08:25+00:00" + "time": "2019-12-30T18:03:34+00:00" }, { "name": "nesbot/carbon", - "version": "1.36.1", + "version": "2.35.0", "source": { "type": "git", "url": "https://github.com/briannesbitt/Carbon.git", - "reference": "63da8cdf89d7a5efe43aabc794365f6e7b7b8983" + "reference": "4b9bd835261ef23d36397a46a76b496a458305e5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/63da8cdf89d7a5efe43aabc794365f6e7b7b8983", - "reference": "63da8cdf89d7a5efe43aabc794365f6e7b7b8983", + "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/4b9bd835261ef23d36397a46a76b496a458305e5", + "reference": "4b9bd835261ef23d36397a46a76b496a458305e5", "shasum": "" }, "require": { - "php": ">=5.3.9", - "symfony/translation": "~2.6 || ~3.0 || ~4.0" + "ext-json": "*", + "php": "^7.1.8 || ^8.0", + "symfony/polyfill-mbstring": "^1.0", + "symfony/translation": "^3.4 || ^4.0 || ^5.0" }, "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.7" - }, - "suggest": { - "friendsofphp/php-cs-fixer": "Needed for the `composer phpcs` command. Allow to automatically fix code style.", - "phpstan/phpstan": "Needed for the `composer phpstan` command. Allow to detect potential errors." + "doctrine/orm": "^2.7", + "friendsofphp/php-cs-fixer": "^2.14 || ^3.0", + "kylekatarnls/multi-tester": "^1.1", + "phpmd/phpmd": "^2.8", + "phpstan/phpstan": "^0.11", + "phpunit/phpunit": "^7.5 || ^8.0", + "squizlabs/php_codesniffer": "^3.4" }, + "bin": [ + "bin/carbon" + ], "type": "library", "extra": { + "branch-alias": { + "dev-master": "2.x-dev", + "dev-3.x": "3.x-dev" + }, "laravel": { "providers": [ "Carbon\\Laravel\\ServiceProvider" @@ -1860,7 +2191,7 @@ }, "autoload": { "psr-4": { - "": "src/" + "Carbon\\": "src/Carbon/" } }, "notification-url": "https://packagist.org/downloads/", @@ -1872,29 +2203,43 @@ "name": "Brian Nesbitt", "email": "brian@nesbot.com", "homepage": "http://nesbot.com" + }, + { + "name": "kylekatarnls", + "homepage": "http://github.com/kylekatarnls" } ], - "description": "A simple API extension for DateTime.", + "description": "An API extension for DateTime that supports 281 different languages.", "homepage": "http://carbon.nesbot.com", "keywords": [ "date", "datetime", "time" ], - "time": "2018-11-22T18:23:02+00:00" + "funding": [ + { + "url": "https://opencollective.com/Carbon", + "type": "open_collective" + }, + { + "url": "https://tidelift.com/funding/github/packagist/nesbot/carbon", + "type": "tidelift" + } + ], + "time": "2020-05-24T18:27:52+00:00" }, { "name": "nikic/php-parser", - "version": "v4.1.0", + "version": "v4.5.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "d0230c5c77a7e3cfa69446febf340978540958c0" + "reference": "53c2753d756f5adb586dca79c2ec0e2654dd9463" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/d0230c5c77a7e3cfa69446febf340978540958c0", - "reference": "d0230c5c77a7e3cfa69446febf340978540958c0", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/53c2753d756f5adb586dca79c2ec0e2654dd9463", + "reference": "53c2753d756f5adb586dca79c2ec0e2654dd9463", "shasum": "" }, "require": { @@ -1902,7 +2247,8 @@ "php": ">=7.0" }, "require-dev": { - "phpunit/phpunit": "^6.5 || ^7.0" + "ircmaxell/php-yacc": "0.0.5", + "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0" }, "bin": [ "bin/php-parse" @@ -1910,7 +2256,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.3-dev" } }, "autoload": { @@ -1932,20 +2278,20 @@ "parser", "php" ], - "time": "2018-10-10T09:24:14+00:00" + "time": "2020-06-03T07:24:19+00:00" }, { "name": "opis/closure", - "version": "3.1.1", + "version": "3.5.5", "source": { "type": "git", "url": "https://github.com/opis/closure.git", - "reference": "d3209e46ad6c69a969b705df0738fd0dbe26ef9e" + "reference": "dec9fc5ecfca93f45cd6121f8e6f14457dff372c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opis/closure/zipball/d3209e46ad6c69a969b705df0738fd0dbe26ef9e", - "reference": "d3209e46ad6c69a969b705df0738fd0dbe26ef9e", + "url": "https://api.github.com/repos/opis/closure/zipball/dec9fc5ecfca93f45cd6121f8e6f14457dff372c", + "reference": "dec9fc5ecfca93f45cd6121f8e6f14457dff372c", "shasum": "" }, "require": { @@ -1953,12 +2299,12 @@ }, "require-dev": { "jeremeamia/superclosure": "^2.0", - "phpunit/phpunit": "^4.0" + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0.x-dev" + "dev-master": "3.5.x-dev" } }, "autoload": { @@ -1993,28 +2339,28 @@ "serialization", "serialize" ], - "time": "2018-10-02T13:36:53+00:00" + "time": "2020-06-17T14:59:55+00:00" }, { "name": "paragonie/constant_time_encoding", - "version": "v2.2.2", + "version": "v2.3.0", "source": { "type": "git", "url": "https://github.com/paragonie/constant_time_encoding.git", - "reference": "eccf915f45f911bfb189d1d1638d940ec6ee6e33" + "reference": "47a1cedd2e4d52688eb8c96469c05ebc8fd28fa2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/eccf915f45f911bfb189d1d1638d940ec6ee6e33", - "reference": "eccf915f45f911bfb189d1d1638d940ec6ee6e33", + "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/47a1cedd2e4d52688eb8c96469c05ebc8fd28fa2", + "reference": "47a1cedd2e4d52688eb8c96469c05ebc8fd28fa2", "shasum": "" }, "require": { - "php": "^7" + "php": "^7|^8" }, "require-dev": { "phpunit/phpunit": "^6|^7", - "vimeo/psalm": "^1" + "vimeo/psalm": "^1|^2|^3" }, "type": "library", "autoload": { @@ -2055,37 +2401,33 @@ "hex2bin", "rfc4648" ], - "time": "2018-03-10T19:47:49+00:00" + "time": "2019-11-06T19:20:29+00:00" }, { "name": "paragonie/random_compat", - "version": "v2.0.17", + "version": "v9.99.99", "source": { "type": "git", "url": "https://github.com/paragonie/random_compat.git", - "reference": "29af24f25bab834fcbb38ad2a69fa93b867e070d" + "reference": "84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paragonie/random_compat/zipball/29af24f25bab834fcbb38ad2a69fa93b867e070d", - "reference": "29af24f25bab834fcbb38ad2a69fa93b867e070d", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95", + "reference": "84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95", "shasum": "" }, "require": { - "php": ">=5.2.0" + "php": "^7" }, "require-dev": { - "phpunit/phpunit": "4.*|5.*" + "phpunit/phpunit": "4.*|5.*", + "vimeo/psalm": "^1" }, "suggest": { "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." }, "type": "library", - "autoload": { - "files": [ - "lib/random.php" - ] - }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" @@ -2104,7 +2446,72 @@ "pseudorandom", "random" ], - "time": "2018-07-04T16:31:37+00:00" + "time": "2018-07-02T15:55:56+00:00" + }, + { + "name": "phpoption/phpoption", + "version": "1.7.4", + "source": { + "type": "git", + "url": "https://github.com/schmittjoh/php-option.git", + "reference": "b2ada2ad5d8a32b89088b8adc31ecd2e3a13baf3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/b2ada2ad5d8a32b89088b8adc31ecd2e3a13baf3", + "reference": "b2ada2ad5d8a32b89088b8adc31ecd2e3a13baf3", + "shasum": "" + }, + "require": { + "php": "^5.5.9 || ^7.0 || ^8.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.3", + "phpunit/phpunit": "^4.8.35 || ^5.0 || ^6.0 || ^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.7-dev" + } + }, + "autoload": { + "psr-4": { + "PhpOption\\": "src/PhpOption/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Johannes M. Schmitt", + "email": "schmittjoh@gmail.com" + }, + { + "name": "Graham Campbell", + "email": "graham@alt-three.com" + } + ], + "description": "Option Type for PHP", + "keywords": [ + "language", + "option", + "php", + "type" + ], + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpoption/phpoption", + "type": "tidelift" + } + ], + "time": "2020-06-07T10:40:07+00:00" }, { "name": "pragmarx/google2fa", @@ -2149,8 +2556,8 @@ "authors": [ { "name": "Antonio Carlos Ribeiro", - "role": "Creator & Designer", - "email": "acr@antoniocarlosribeiro.com" + "email": "acr@antoniocarlosribeiro.com", + "role": "Creator & Designer" } ], "description": "A One Time Password Authentication package, compatible with Google Authenticator.", @@ -2214,22 +2621,22 @@ }, { "name": "prologue/alerts", - "version": "0.4.2", + "version": "0.4.7", "source": { "type": "git", "url": "https://github.com/prologuephp/alerts.git", - "reference": "d3bf5d7ea480cbbf372bb7f80e23e193ce4862c7" + "reference": "631896b583129b2873df09b5295809c1244eddb1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/prologuephp/alerts/zipball/d3bf5d7ea480cbbf372bb7f80e23e193ce4862c7", - "reference": "d3bf5d7ea480cbbf372bb7f80e23e193ce4862c7", + "url": "https://api.github.com/repos/prologuephp/alerts/zipball/631896b583129b2873df09b5295809c1244eddb1", + "reference": "631896b583129b2873df09b5295809c1244eddb1", "shasum": "" }, "require": { - "illuminate/config": "~5", - "illuminate/session": "~5", - "illuminate/support": "~5", + "illuminate/config": "~5|~6|~7", + "illuminate/session": "~5|~6|~7", + "illuminate/support": "~5|~6|~7", "php": ">=5.4.0" }, "require-dev": { @@ -2259,9 +2666,15 @@ "authors": [ { "name": "Dries Vints", - "role": "Maintainer", "email": "dries.vints@gmail.com", - "homepage": "http://driesvints.com" + "homepage": "http://driesvints.com", + "role": "Creator" + }, + { + "name": "Cristian Tabacitu", + "email": "hello@tabacitu.ro", + "homepage": "http://tabacitu.ro", + "role": "Maintainer" } ], "description": "Prologue Alerts is a package that handles global site messages.", @@ -2270,7 +2683,7 @@ "laravel", "messages" ], - "time": "2018-02-08T11:29:22+00:00" + "time": "2020-04-24T06:00:16+00:00" }, { "name": "psr/container", @@ -2321,6 +2734,52 @@ ], "time": "2017-02-14T16:28:37+00:00" }, + { + "name": "psr/event-dispatcher", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/event-dispatcher.git", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\EventDispatcher\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Standard interfaces for event handling.", + "keywords": [ + "events", + "psr", + "psr-14" + ], + "time": "2019-01-08T18:20:26+00:00" + }, { "name": "psr/http-message", "version": "1.0.1", @@ -2373,16 +2832,16 @@ }, { "name": "psr/log", - "version": "1.1.0", + "version": "1.1.3", "source": { "type": "git", "url": "https://github.com/php-fig/log.git", - "reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd" + "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd", - "reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd", + "url": "https://api.github.com/repos/php-fig/log/zipball/0f73288fd15629204f9d42b7055f72dacbe811fc", + "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc", "shasum": "" }, "require": { @@ -2391,7 +2850,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "1.1.x-dev" } }, "autoload": { @@ -2416,7 +2875,7 @@ "psr", "psr-3" ], - "time": "2018-11-20T15:27:04+00:00" + "time": "2020-03-23T09:12:05+00:00" }, { "name": "psr/simple-cache", @@ -2468,32 +2927,30 @@ }, { "name": "psy/psysh", - "version": "v0.9.9", + "version": "v0.10.4", "source": { "type": "git", "url": "https://github.com/bobthecow/psysh.git", - "reference": "9aaf29575bb8293206bb0420c1e1c87ff2ffa94e" + "reference": "a8aec1b2981ab66882a01cce36a49b6317dc3560" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bobthecow/psysh/zipball/9aaf29575bb8293206bb0420c1e1c87ff2ffa94e", - "reference": "9aaf29575bb8293206bb0420c1e1c87ff2ffa94e", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/a8aec1b2981ab66882a01cce36a49b6317dc3560", + "reference": "a8aec1b2981ab66882a01cce36a49b6317dc3560", "shasum": "" }, "require": { - "dnoegel/php-xdg-base-dir": "0.1", + "dnoegel/php-xdg-base-dir": "0.1.*", "ext-json": "*", "ext-tokenizer": "*", - "jakub-onderka/php-console-highlighter": "0.3.*|0.4.*", - "nikic/php-parser": "~1.3|~2.0|~3.0|~4.0", - "php": ">=5.4.0", - "symfony/console": "~2.3.10|^2.4.2|~3.0|~4.0", - "symfony/var-dumper": "~2.7|~3.0|~4.0" + "nikic/php-parser": "~4.0|~3.0|~2.0|~1.3", + "php": "^8.0 || ^7.0 || ^5.5.9", + "symfony/console": "~5.0|~4.0|~3.0|^2.4.2|~2.3.10", + "symfony/var-dumper": "~5.0|~4.0|~3.0|~2.7" }, "require-dev": { "bamarni/composer-bin-plugin": "^1.2", - "hoa/console": "~2.15|~3.16", - "phpunit/phpunit": "~4.8.35|~5.0|~6.0|~7.0" + "hoa/console": "3.17.*" }, "suggest": { "ext-pcntl": "Enabling the PCNTL extension makes PsySH a lot happier :)", @@ -2508,7 +2965,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-develop": "0.9.x-dev" + "dev-master": "0.10.x-dev" } }, "autoload": { @@ -2538,60 +2995,84 @@ "interactive", "shell" ], - "time": "2018-10-13T15:16:03+00:00" + "time": "2020-05-03T19:32:03+00:00" }, { - "name": "ramsey/uuid", - "version": "3.8.0", + "name": "ralouphie/getallheaders", + "version": "3.0.3", "source": { "type": "git", - "url": "https://github.com/ramsey/uuid.git", - "reference": "d09ea80159c1929d75b3f9c60504d613aeb4a1e3" + "url": "https://github.com/ralouphie/getallheaders.git", + "reference": "120b605dfeb996808c31b6477290a714d356e822" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/uuid/zipball/d09ea80159c1929d75b3f9c60504d613aeb4a1e3", - "reference": "d09ea80159c1929d75b3f9c60504d613aeb4a1e3", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", + "reference": "120b605dfeb996808c31b6477290a714d356e822", "shasum": "" }, "require": { - "paragonie/random_compat": "^1.0|^2.0|9.99.99", - "php": "^5.4 || ^7.0", - "symfony/polyfill-ctype": "^1.8" - }, - "replace": { - "rhumsaa/uuid": "self.version" + "php": ">=5.6" }, "require-dev": { - "codeception/aspect-mock": "^1.0 | ~2.0.0", - "doctrine/annotations": "~1.2.0", - "goaop/framework": "1.0.0-alpha.2 | ^1.0 | ~2.1.0", - "ircmaxell/random-lib": "^1.1", - "jakub-onderka/php-parallel-lint": "^0.9.0", - "mockery/mockery": "^0.9.9", - "moontoast/math": "^1.1", - "php-mock/php-mock-phpunit": "^0.3|^1.1", - "phpunit/phpunit": "^4.7|^5.0|^6.5", - "squizlabs/php_codesniffer": "^2.3" - }, - "suggest": { - "ext-ctype": "Provides support for PHP Ctype functions", - "ext-libsodium": "Provides the PECL libsodium extension for use with the SodiumRandomGenerator", - "ext-uuid": "Provides the PECL UUID extension for use with the PeclUuidTimeGenerator and PeclUuidRandomGenerator", - "ircmaxell/random-lib": "Provides RandomLib for use with the RandomLibAdapter", - "moontoast/math": "Provides support for converting UUID to 128-bit integer (in string form).", - "ramsey/uuid-console": "A console application for generating UUIDs with ramsey/uuid", - "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type." + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5 || ^6.5" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } + "autoload": { + "files": [ + "src/getallheaders.php" + ] }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" + } + ], + "description": "A polyfill for getallheaders.", + "time": "2019-03-08T08:55:37+00:00" + }, + { + "name": "ramsey/collection", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/ramsey/collection.git", + "reference": "925ad8cf55ba7a3fc92e332c58fd0478ace3e1ca" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ramsey/collection/zipball/925ad8cf55ba7a3fc92e332c58fd0478ace3e1ca", + "reference": "925ad8cf55ba7a3fc92e332c58fd0478ace3e1ca", + "shasum": "" + }, + "require": { + "php": "^7.2" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.5.0", + "fzaninotto/faker": "^1.5", + "jakub-onderka/php-parallel-lint": "^1", + "jangregor/phpstan-prophecy": "^0.6", + "mockery/mockery": "^1.3", + "phpstan/extension-installer": "^1", + "phpstan/phpdoc-parser": "0.4.1", + "phpstan/phpstan": "^0.12", + "phpstan/phpstan-mockery": "^0.12", + "phpstan/phpstan-phpunit": "^0.12", + "phpunit/phpunit": "^8.5", + "slevomat/coding-standard": "^6.0", + "squizlabs/php_codesniffer": "^3.5" + }, + "type": "library", "autoload": { "psr-4": { - "Ramsey\\Uuid\\": "src/" + "Ramsey\\Collection\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -2599,28 +3080,110 @@ "MIT" ], "authors": [ - { - "name": "Marijn Huizendveld", - "email": "marijn.huizendveld@gmail.com" - }, - { - "name": "Thibaud Fabre", - "email": "thibaud@aztech.io" - }, { "name": "Ben Ramsey", "email": "ben@benramsey.com", "homepage": "https://benramsey.com" } ], - "description": "Formerly rhumsaa/uuid. A PHP 5.4+ library for generating RFC 4122 version 1, 3, 4, and 5 universally unique identifiers (UUID).", + "description": "A PHP 7.2+ library for representing and manipulating collections.", + "homepage": "https://github.com/ramsey/collection", + "keywords": [ + "array", + "collection", + "hash", + "map", + "queue", + "set" + ], + "time": "2020-01-05T00:22:59+00:00" + }, + { + "name": "ramsey/uuid", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/ramsey/uuid.git", + "reference": "ba8fff1d3abb8bb4d35a135ed22a31c6ef3ede3d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/ba8fff1d3abb8bb4d35a135ed22a31c6ef3ede3d", + "reference": "ba8fff1d3abb8bb4d35a135ed22a31c6ef3ede3d", + "shasum": "" + }, + "require": { + "brick/math": "^0.8", + "ext-json": "*", + "php": "^7.2 || ^8", + "ramsey/collection": "^1.0", + "symfony/polyfill-ctype": "^1.8" + }, + "replace": { + "rhumsaa/uuid": "self.version" + }, + "require-dev": { + "codeception/aspect-mock": "^3", + "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2", + "doctrine/annotations": "^1.8", + "goaop/framework": "^2", + "mockery/mockery": "^1.3", + "moontoast/math": "^1.1", + "paragonie/random-lib": "^2", + "php-mock/php-mock-mockery": "^1.3", + "php-mock/php-mock-phpunit": "^2.5", + "php-parallel-lint/php-parallel-lint": "^1.1", + "phpstan/extension-installer": "^1.0", + "phpstan/phpdoc-parser": "0.4.3", + "phpstan/phpstan": "^0.12", + "phpstan/phpstan-mockery": "^0.12", + "phpstan/phpstan-phpunit": "^0.12", + "phpunit/phpunit": "^8.5", + "psy/psysh": "^0.10.0", + "slevomat/coding-standard": "^6.0", + "squizlabs/php_codesniffer": "^3.5", + "vimeo/psalm": "3.9.4" + }, + "suggest": { + "ext-bcmath": "Enables faster math with arbitrary-precision integers using BCMath.", + "ext-ctype": "Enables faster processing of character classification using ctype functions.", + "ext-gmp": "Enables faster math with arbitrary-precision integers using GMP.", + "ext-uuid": "Enables the use of PeclUuidTimeGenerator and PeclUuidRandomGenerator.", + "paragonie/random-lib": "Provides RandomLib for use with the RandomLibAdapter", + "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.x-dev" + } + }, + "autoload": { + "psr-4": { + "Ramsey\\Uuid\\": "src/" + }, + "files": [ + "src/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A PHP library for generating and working with universally unique identifiers (UUIDs).", "homepage": "https://github.com/ramsey/uuid", "keywords": [ "guid", "identifier", "uuid" ], - "time": "2018-07-19T23:38:55+00:00" + "funding": [ + { + "url": "https://github.com/ramsey", + "type": "github" + } + ], + "time": "2020-03-29T20:13:32+00:00" }, { "name": "s1lentium/iptools", @@ -2673,183 +3236,22 @@ ], "time": "2018-09-19T06:15:53+00:00" }, - { - "name": "sofa/eloquence-base", - "version": "v5.6.2", - "source": { - "type": "git", - "url": "https://github.com/jarektkaczyk/eloquence-base.git", - "reference": "e941b7ff79ca9c77ef540b5f14a7f82a3acea5ba" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/jarektkaczyk/eloquence-base/zipball/e941b7ff79ca9c77ef540b5f14a7f82a3acea5ba", - "reference": "e941b7ff79ca9c77ef540b5f14a7f82a3acea5ba", - "shasum": "" - }, - "require": { - "illuminate/database": "^5.5", - "php": ">=7.0.0", - "sofa/hookable": "^5.5" - }, - "require-dev": { - "mockery/mockery": "0.9.4", - "phpunit/phpunit": "4.5.0", - "squizlabs/php_codesniffer": "2.3.3" - }, - "type": "library", - "extra": { - "laravel": { - "providers": [ - "Sofa\\Eloquence\\BaseServiceProvider" - ] - } - }, - "autoload": { - "psr-4": { - "Sofa\\Eloquence\\": "src" - }, - "files": [ - "src/helpers.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jarek Tkaczyk", - "role": "Developer", - "email": "jarek@softonsofa.com", - "homepage": "https://softonsofa.com/" - } - ], - "description": "Flexible Searchable, Mappable, Metable, Validation and more extensions for Laravel Eloquent ORM.", - "keywords": [ - "eloquent", - "laravel", - "mappable", - "metable", - "mutable", - "searchable" - ], - "time": "2018-03-10T12:37:43+00:00" - }, - { - "name": "sofa/eloquence-validable", - "version": "5.6", - "source": { - "type": "git", - "url": "https://github.com/jarektkaczyk/eloquence-validable.git", - "reference": "9d9ef65bf4a4952efb54b06ac0b04fc8893d5f95" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/jarektkaczyk/eloquence-validable/zipball/9d9ef65bf4a4952efb54b06ac0b04fc8893d5f95", - "reference": "9d9ef65bf4a4952efb54b06ac0b04fc8893d5f95", - "shasum": "" - }, - "require": { - "php": ">=7.0.0", - "sofa/eloquence-base": "^5.5" - }, - "require-dev": { - "mockery/mockery": "0.9.4", - "phpunit/phpunit": "4.5.0", - "squizlabs/php_codesniffer": "2.3.3" - }, - "type": "library", - "autoload": { - "psr-4": { - "Sofa\\Eloquence\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jarek Tkaczyk", - "email": "jarek@softonsofa.com", - "homepage": "https://softonsofa.com/", - "role": "Developer" - } - ], - "description": "Flexible Searchable, Mappable, Metable, Validation and more extensions for Laravel Eloquent ORM.", - "keywords": [ - "eloquent", - "laravel", - "mappable", - "metable", - "mutable", - "searchable" - ], - "time": "2018-03-03T03:09:46+00:00" - }, - { - "name": "sofa/hookable", - "version": "5.6", - "source": { - "type": "git", - "url": "https://github.com/jarektkaczyk/hookable.git", - "reference": "c6f03e5e742d539755f8c7993ee96e907593a668" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/jarektkaczyk/hookable/zipball/c6f03e5e742d539755f8c7993ee96e907593a668", - "reference": "c6f03e5e742d539755f8c7993ee96e907593a668", - "shasum": "" - }, - "require": { - "illuminate/database": "^5.3", - "php": ">=5.6.4" - }, - "require-dev": { - "crysalead/kahlan": "~1.1" - }, - "type": "library", - "autoload": { - "psr-4": { - "Sofa\\Hookable\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jarek Tkaczyk", - "role": "Developer", - "email": "jarek@softonsofa.com", - "homepage": "http://softonsofa.com/" - } - ], - "description": "Laravel Eloquent hooks system.", - "keywords": [ - "eloquent", - "laravel" - ], - "time": "2018-03-03T02:55:49+00:00" - }, { "name": "spatie/fractalistic", - "version": "2.7.2", + "version": "2.9.0", "source": { "type": "git", "url": "https://github.com/spatie/fractalistic.git", - "reference": "5b5710b748beb2c1d5c272f4d3598d44b5b59fc9" + "reference": "9d7594c4e9ed2657eb017db93ab6dbb58f56e049" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/fractalistic/zipball/5b5710b748beb2c1d5c272f4d3598d44b5b59fc9", - "reference": "5b5710b748beb2c1d5c272f4d3598d44b5b59fc9", + "url": "https://api.github.com/repos/spatie/fractalistic/zipball/9d7594c4e9ed2657eb017db93ab6dbb58f56e049", + "reference": "9d7594c4e9ed2657eb017db93ab6dbb58f56e049", "shasum": "" }, "require": { - "league/fractal": "^0.17.0", + "league/fractal": "^0.19.0", "php": "^7.0" }, "require-dev": { @@ -2883,31 +3285,31 @@ "spatie", "transform" ], - "time": "2018-10-08T09:18:33+00:00" + "time": "2020-01-31T11:54:45+00:00" }, { "name": "spatie/laravel-fractal", - "version": "5.4.2", + "version": "5.7.0", "source": { "type": "git", "url": "https://github.com/spatie/laravel-fractal.git", - "reference": "2931881cac3155ceb798f2fd1e55bd152576682b" + "reference": "482b73a7942d14087167b32c6681351c81afaeb3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-fractal/zipball/2931881cac3155ceb798f2fd1e55bd152576682b", - "reference": "2931881cac3155ceb798f2fd1e55bd152576682b", + "url": "https://api.github.com/repos/spatie/laravel-fractal/zipball/482b73a7942d14087167b32c6681351c81afaeb3", + "reference": "482b73a7942d14087167b32c6681351c81afaeb3", "shasum": "" }, "require": { - "illuminate/contracts": "~5.5.0|~5.6.0|~5.7.0", - "illuminate/support": "~5.5.0|~5.6.0|~5.7.0", - "php": "^7.0", + "illuminate/contracts": "~5.8.0|^6.0|^7.0", + "illuminate/support": "~5.8.0|^6.0|^7.0", + "php": "^7.2", "spatie/fractalistic": "^2.5" }, "require-dev": { - "orchestra/testbench": "~3.5.0|~3.6.0|~3.7.0", - "phpunit/phpunit": "^6.2|^7.0" + "dms/phpunit-arraysubset-asserts": "^0.1.0", + "orchestra/testbench": "~3.8.0|^4.0|^5.0" }, "type": "library", "extra": { @@ -2951,31 +3353,98 @@ "spatie", "transform" ], - "time": "2018-09-28T16:17:34+00:00" + "funding": [ + { + "url": "https://spatie.be/open-source/support-us", + "type": "custom" + } + ], + "time": "2020-03-02T18:40:49+00:00" }, { - "name": "staudenmeir/belongs-to-through", - "version": "v2.3.2", + "name": "spatie/laravel-query-builder", + "version": "2.8.2", "source": { "type": "git", - "url": "https://github.com/staudenmeir/belongs-to-through.git", - "reference": "2ba1ff76353058d2b4d395e725617d97fd103ab0" + "url": "https://github.com/spatie/laravel-query-builder.git", + "reference": "2737b2298e8bfeb632a80013646943307bf31775" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/staudenmeir/belongs-to-through/zipball/2ba1ff76353058d2b4d395e725617d97fd103ab0", - "reference": "2ba1ff76353058d2b4d395e725617d97fd103ab0", + "url": "https://api.github.com/repos/spatie/laravel-query-builder/zipball/2737b2298e8bfeb632a80013646943307bf31775", + "reference": "2737b2298e8bfeb632a80013646943307bf31775", "shasum": "" }, "require": { - "illuminate/database": "~5.0" + "illuminate/database": "~5.6.34|~5.7.0|~5.8.0|^6.0|^7.0", + "illuminate/http": "~5.6.34|~5.7.0|~5.8.0|^6.0|^7.0", + "illuminate/support": "~5.6.34|~5.7.0|~5.8.0|^6.0|^7.0", + "php": "^7.1" }, "require-dev": { - "fabpot/php-cs-fixer": "^1.11", - "orchestra/testbench": "~3.0", - "phpunit/php-code-coverage": "^3.3", - "phpunit/phpunit": "~5.0", - "satooshi/php-coveralls": "^1.0" + "ext-json": "*", + "orchestra/testbench": "~3.6.0|~3.7.0|~3.8.0|^4.0|^5.0", + "phpunit/phpunit": "^7.0|^8.0|^9.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Spatie\\QueryBuilder\\QueryBuilderServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Spatie\\QueryBuilder\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Alex Vanderbist", + "email": "alex@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + } + ], + "description": "Easily build Eloquent queries from API requests", + "homepage": "https://github.com/spatie/laravel-query-builder", + "keywords": [ + "laravel-query-builder", + "spatie" + ], + "funding": [ + { + "url": "https://spatie.be/open-source/support-us", + "type": "custom" + } + ], + "time": "2020-05-25T09:36:37+00:00" + }, + { + "name": "staudenmeir/belongs-to-through", + "version": "v2.10", + "source": { + "type": "git", + "url": "https://github.com/staudenmeir/belongs-to-through.git", + "reference": "23be043a4885f696a0e5eb24da7221947e480cb5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/staudenmeir/belongs-to-through/zipball/23be043a4885f696a0e5eb24da7221947e480cb5", + "reference": "23be043a4885f696a0e5eb24da7221947e480cb5", + "shasum": "" + }, + "require": { + "illuminate/database": "^7.0", + "php": "^7.2.5" + }, + "require-dev": { + "phpunit/phpunit": "^8.5" }, "type": "library", "autoload": { @@ -2991,41 +3460,39 @@ { "name": "Rahul Kadyan", "email": "hi@znck.me" + }, + { + "name": "Jonas Staudenmeir", + "email": "mail@jonas-staudenmeir.de" } ], - "description": "Adds belongsToThrough relation to laravel models", - "homepage": "https://github.com/staudenmeir/belongs-to-through", - "keywords": [ - "belongsToThrough", - "eloquent", - "laravel", - "model", - "models", - "znck" - ], - "time": "2019-02-01T14:33:18+00:00" + "description": "Laravel Eloquent BelongsToThrough relationships", + "time": "2020-01-31T11:29:47+00:00" }, { "name": "swiftmailer/swiftmailer", - "version": "v6.1.3", + "version": "v6.2.3", "source": { "type": "git", "url": "https://github.com/swiftmailer/swiftmailer.git", - "reference": "8ddcb66ac10c392d3beb54829eef8ac1438595f4" + "reference": "149cfdf118b169f7840bbe3ef0d4bc795d1780c9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/8ddcb66ac10c392d3beb54829eef8ac1438595f4", - "reference": "8ddcb66ac10c392d3beb54829eef8ac1438595f4", + "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/149cfdf118b169f7840bbe3ef0d4bc795d1780c9", + "reference": "149cfdf118b169f7840bbe3ef0d4bc795d1780c9", "shasum": "" }, "require": { "egulias/email-validator": "~2.0", - "php": ">=7.0.0" + "php": ">=7.0.0", + "symfony/polyfill-iconv": "^1.0", + "symfony/polyfill-intl-idn": "^1.10", + "symfony/polyfill-mbstring": "^1.0" }, "require-dev": { "mockery/mockery": "~0.9.1", - "symfony/phpunit-bridge": "~3.3@dev" + "symfony/phpunit-bridge": "^3.4.19|^4.1.8" }, "suggest": { "ext-intl": "Needed to support internationalized email addresses", @@ -3034,7 +3501,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "6.1-dev" + "dev-master": "6.2-dev" } }, "autoload": { @@ -3062,40 +3529,51 @@ "mail", "mailer" ], - "time": "2018-09-11T07:12:52+00:00" + "time": "2019-11-12T09:31:26+00:00" }, { "name": "symfony/console", - "version": "v4.1.7", + "version": "v5.1.2", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "432122af37d8cd52fba1b294b11976e0d20df595" + "reference": "34ac555a3627e324b660e318daa07572e1140123" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/432122af37d8cd52fba1b294b11976e0d20df595", - "reference": "432122af37d8cd52fba1b294b11976e0d20df595", + "url": "https://api.github.com/repos/symfony/console/zipball/34ac555a3627e324b660e318daa07572e1140123", + "reference": "34ac555a3627e324b660e318daa07572e1140123", "shasum": "" }, "require": { - "php": "^7.1.3", - "symfony/polyfill-mbstring": "~1.0" + "php": ">=7.2.5", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php73": "^1.8", + "symfony/polyfill-php80": "^1.15", + "symfony/service-contracts": "^1.1|^2", + "symfony/string": "^5.1" }, "conflict": { - "symfony/dependency-injection": "<3.4", - "symfony/process": "<3.3" + "symfony/dependency-injection": "<4.4", + "symfony/dotenv": "<5.1", + "symfony/event-dispatcher": "<4.4", + "symfony/lock": "<4.4", + "symfony/process": "<4.4" + }, + "provide": { + "psr/log-implementation": "1.0" }, "require-dev": { "psr/log": "~1.0", - "symfony/config": "~3.4|~4.0", - "symfony/dependency-injection": "~3.4|~4.0", - "symfony/event-dispatcher": "~3.4|~4.0", - "symfony/lock": "~3.4|~4.0", - "symfony/process": "~3.4|~4.0" + "symfony/config": "^4.4|^5.0", + "symfony/dependency-injection": "^4.4|^5.0", + "symfony/event-dispatcher": "^4.4|^5.0", + "symfony/lock": "^4.4|^5.0", + "symfony/process": "^4.4|^5.0", + "symfony/var-dumper": "^4.4|^5.0" }, "suggest": { - "psr/log-implementation": "For using the console logger", + "psr/log": "For using the console logger", "symfony/event-dispatcher": "", "symfony/lock": "", "symfony/process": "" @@ -3103,7 +3581,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "5.1-dev" } }, "autoload": { @@ -3130,29 +3608,43 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "time": "2018-10-31T09:30:44+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-06-15T12:59:21+00:00" }, { "name": "symfony/css-selector", - "version": "v4.1.7", + "version": "v5.1.2", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "d67de79a70a27d93c92c47f37ece958bf8de4d8a" + "reference": "e544e24472d4c97b2d11ade7caacd446727c6bf9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/d67de79a70a27d93c92c47f37ece958bf8de4d8a", - "reference": "d67de79a70a27d93c92c47f37ece958bf8de4d8a", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/e544e24472d4c97b2d11ade7caacd446727c6bf9", + "reference": "e544e24472d4c97b2d11ade7caacd446727c6bf9", "shasum": "" }, "require": { - "php": "^7.1.3" + "php": ">=7.2.5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "5.1-dev" } }, "autoload": { @@ -3168,14 +3660,14 @@ "MIT" ], "authors": [ - { - "name": "Jean-François Simon", - "email": "jeanfrancois.simon@sensiolabs.com" - }, { "name": "Fabien Potencier", "email": "fabien@symfony.com" }, + { + "name": "Jean-François Simon", + "email": "jeanfrancois.simon@sensiolabs.com" + }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" @@ -3183,41 +3675,116 @@ ], "description": "Symfony CssSelector Component", "homepage": "https://symfony.com", - "time": "2018-10-02T16:36:10+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-05-20T17:43:50+00:00" }, { - "name": "symfony/debug", - "version": "v4.1.7", + "name": "symfony/deprecation-contracts", + "version": "v2.1.2", "source": { "type": "git", - "url": "https://github.com/symfony/debug.git", - "reference": "19090917b848a799cbae4800abf740fe4eb71c1d" + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "dd99cb3a0aff6cadd2a8d7d7ed72c2161e218337" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/debug/zipball/19090917b848a799cbae4800abf740fe4eb71c1d", - "reference": "19090917b848a799cbae4800abf740fe4eb71c1d", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/dd99cb3a0aff6cadd2a8d7d7ed72c2161e218337", + "reference": "dd99cb3a0aff6cadd2a8d7d7ed72c2161e218337", "shasum": "" }, "require": { - "php": "^7.1.3", - "psr/log": "~1.0" - }, - "conflict": { - "symfony/http-kernel": "<3.4" - }, - "require-dev": { - "symfony/http-kernel": "~3.4|~4.0" + "php": ">=7.1" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "2.1-dev" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-05-27T08:34:37+00:00" + }, + { + "name": "symfony/error-handler", + "version": "v5.1.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/error-handler.git", + "reference": "7d0b927b9d3dc41d7d46cda38cbfcd20cdcbb896" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/7d0b927b9d3dc41d7d46cda38cbfcd20cdcbb896", + "reference": "7d0b927b9d3dc41d7d46cda38cbfcd20cdcbb896", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "psr/log": "^1.0", + "symfony/polyfill-php80": "^1.15", + "symfony/var-dumper": "^4.4|^5.0" + }, + "require-dev": { + "symfony/deprecation-contracts": "^2.1", + "symfony/http-kernel": "^4.4|^5.0", + "symfony/serializer": "^4.4|^5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" } }, "autoload": { "psr-4": { - "Symfony\\Component\\Debug\\": "" + "Symfony\\Component\\ErrorHandler\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -3237,36 +3804,59 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Debug Component", + "description": "Symfony ErrorHandler Component", "homepage": "https://symfony.com", - "time": "2018-10-31T09:09:42+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-05-30T20:35:19+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v4.1.7", + "version": "v5.1.2", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "552541dad078c85d9414b09c041ede488b456cd5" + "reference": "cc0d059e2e997e79ca34125a52f3e33de4424ac7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/552541dad078c85d9414b09c041ede488b456cd5", - "reference": "552541dad078c85d9414b09c041ede488b456cd5", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/cc0d059e2e997e79ca34125a52f3e33de4424ac7", + "reference": "cc0d059e2e997e79ca34125a52f3e33de4424ac7", "shasum": "" }, "require": { - "php": "^7.1.3" + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1", + "symfony/event-dispatcher-contracts": "^2", + "symfony/polyfill-php80": "^1.15" }, "conflict": { - "symfony/dependency-injection": "<3.4" + "symfony/dependency-injection": "<4.4" + }, + "provide": { + "psr/event-dispatcher-implementation": "1.0", + "symfony/event-dispatcher-implementation": "2.0" }, "require-dev": { "psr/log": "~1.0", - "symfony/config": "~3.4|~4.0", - "symfony/dependency-injection": "~3.4|~4.0", - "symfony/expression-language": "~3.4|~4.0", - "symfony/stopwatch": "~3.4|~4.0" + "symfony/config": "^4.4|^5.0", + "symfony/dependency-injection": "^4.4|^5.0", + "symfony/expression-language": "^4.4|^5.0", + "symfony/http-foundation": "^4.4|^5.0", + "symfony/service-contracts": "^1.1|^2", + "symfony/stopwatch": "^4.4|^5.0" }, "suggest": { "symfony/dependency-injection": "", @@ -3275,7 +3865,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "5.1-dev" } }, "autoload": { @@ -3302,29 +3892,115 @@ ], "description": "Symfony EventDispatcher Component", "homepage": "https://symfony.com", - "time": "2018-10-10T13:52:42+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-05-20T17:43:50+00:00" }, { - "name": "symfony/finder", - "version": "v4.1.7", + "name": "symfony/event-dispatcher-contracts", + "version": "v2.1.2", "source": { "type": "git", - "url": "https://github.com/symfony/finder.git", - "reference": "1f17195b44543017a9c9b2d437c670627e96ad06" + "url": "https://github.com/symfony/event-dispatcher-contracts.git", + "reference": "405952c4e90941a17e52ef7489a2bd94870bb290" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/1f17195b44543017a9c9b2d437c670627e96ad06", - "reference": "1f17195b44543017a9c9b2d437c670627e96ad06", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/405952c4e90941a17e52ef7489a2bd94870bb290", + "reference": "405952c4e90941a17e52ef7489a2bd94870bb290", "shasum": "" }, "require": { - "php": "^7.1.3" + "php": ">=7.2.5", + "psr/event-dispatcher": "^1" + }, + "suggest": { + "symfony/event-dispatcher-implementation": "" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "2.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\EventDispatcher\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to dispatching event", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-05-20T17:43:50+00:00" + }, + { + "name": "symfony/finder", + "version": "v5.1.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "4298870062bfc667cb78d2b379be4bf5dec5f187" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/4298870062bfc667cb78d2b379be4bf5dec5f187", + "reference": "4298870062bfc667cb78d2b379be4bf5dec5f187", + "shasum": "" + }, + "require": { + "php": ">=7.2.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" } }, "autoload": { @@ -3351,34 +4027,55 @@ ], "description": "Symfony Finder Component", "homepage": "https://symfony.com", - "time": "2018-10-03T08:47:56+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-05-20T17:43:50+00:00" }, { "name": "symfony/http-foundation", - "version": "v4.1.7", + "version": "v5.1.2", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "82d494c1492b0dd24bbc5c2d963fb02eb44491af" + "reference": "f93055171b847915225bd5b0a5792888419d8d75" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/82d494c1492b0dd24bbc5c2d963fb02eb44491af", - "reference": "82d494c1492b0dd24bbc5c2d963fb02eb44491af", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/f93055171b847915225bd5b0a5792888419d8d75", + "reference": "f93055171b847915225bd5b0a5792888419d8d75", "shasum": "" }, "require": { - "php": "^7.1.3", - "symfony/polyfill-mbstring": "~1.1" + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1", + "symfony/polyfill-mbstring": "~1.1", + "symfony/polyfill-php80": "^1.15" }, "require-dev": { "predis/predis": "~1.0", - "symfony/expression-language": "~3.4|~4.0" + "symfony/cache": "^4.4|^5.0", + "symfony/expression-language": "^4.4|^5.0", + "symfony/mime": "^4.4|^5.0" + }, + "suggest": { + "symfony/mime": "To use the file extension guesser" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "5.1-dev" } }, "autoload": { @@ -3405,67 +4102,93 @@ ], "description": "Symfony HttpFoundation Component", "homepage": "https://symfony.com", - "time": "2018-10-31T09:09:42+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-06-15T06:52:54+00:00" }, { "name": "symfony/http-kernel", - "version": "v4.1.7", + "version": "v5.1.2", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "958be64ab13b65172ad646ef5ae20364c2305fae" + "reference": "a18c27ace1ef344ffcb129a5b089bad7643b387a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/958be64ab13b65172ad646ef5ae20364c2305fae", - "reference": "958be64ab13b65172ad646ef5ae20364c2305fae", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/a18c27ace1ef344ffcb129a5b089bad7643b387a", + "reference": "a18c27ace1ef344ffcb129a5b089bad7643b387a", "shasum": "" }, "require": { - "php": "^7.1.3", + "php": ">=7.2.5", "psr/log": "~1.0", - "symfony/debug": "~3.4|~4.0", - "symfony/event-dispatcher": "~4.1", - "symfony/http-foundation": "^4.1.1", - "symfony/polyfill-ctype": "~1.8" + "symfony/deprecation-contracts": "^2.1", + "symfony/error-handler": "^4.4|^5.0", + "symfony/event-dispatcher": "^5.0", + "symfony/http-foundation": "^4.4|^5.0", + "symfony/polyfill-ctype": "^1.8", + "symfony/polyfill-php73": "^1.9", + "symfony/polyfill-php80": "^1.15" }, "conflict": { - "symfony/config": "<3.4", - "symfony/dependency-injection": "<4.1", - "symfony/var-dumper": "<4.1.1", - "twig/twig": "<1.34|<2.4,>=2" + "symfony/browser-kit": "<4.4", + "symfony/cache": "<5.0", + "symfony/config": "<5.0", + "symfony/console": "<4.4", + "symfony/dependency-injection": "<4.4", + "symfony/doctrine-bridge": "<5.0", + "symfony/form": "<5.0", + "symfony/http-client": "<5.0", + "symfony/mailer": "<5.0", + "symfony/messenger": "<5.0", + "symfony/translation": "<5.0", + "symfony/twig-bridge": "<5.0", + "symfony/validator": "<5.0", + "twig/twig": "<2.4" }, "provide": { "psr/log-implementation": "1.0" }, "require-dev": { "psr/cache": "~1.0", - "symfony/browser-kit": "~3.4|~4.0", - "symfony/config": "~3.4|~4.0", - "symfony/console": "~3.4|~4.0", - "symfony/css-selector": "~3.4|~4.0", - "symfony/dependency-injection": "^4.1", - "symfony/dom-crawler": "~3.4|~4.0", - "symfony/expression-language": "~3.4|~4.0", - "symfony/finder": "~3.4|~4.0", - "symfony/process": "~3.4|~4.0", - "symfony/routing": "~3.4|~4.0", - "symfony/stopwatch": "~3.4|~4.0", - "symfony/templating": "~3.4|~4.0", - "symfony/translation": "~3.4|~4.0", - "symfony/var-dumper": "^4.1.1" + "symfony/browser-kit": "^4.4|^5.0", + "symfony/config": "^5.0", + "symfony/console": "^4.4|^5.0", + "symfony/css-selector": "^4.4|^5.0", + "symfony/dependency-injection": "^4.4|^5.0", + "symfony/dom-crawler": "^4.4|^5.0", + "symfony/expression-language": "^4.4|^5.0", + "symfony/finder": "^4.4|^5.0", + "symfony/process": "^4.4|^5.0", + "symfony/routing": "^4.4|^5.0", + "symfony/stopwatch": "^4.4|^5.0", + "symfony/translation": "^4.4|^5.0", + "symfony/translation-contracts": "^1.1|^2", + "twig/twig": "^2.4|^3.0" }, "suggest": { "symfony/browser-kit": "", "symfony/config": "", "symfony/console": "", - "symfony/dependency-injection": "", - "symfony/var-dumper": "" + "symfony/dependency-injection": "" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "5.1-dev" } }, "autoload": { @@ -3492,20 +4215,111 @@ ], "description": "Symfony HttpKernel Component", "homepage": "https://symfony.com", - "time": "2018-11-03T11:11:23+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-06-15T13:51:38+00:00" }, { - "name": "symfony/polyfill-ctype", - "version": "v1.10.0", + "name": "symfony/mime", + "version": "v5.1.2", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "e3d826245268269cd66f8326bd8bc066687b4a19" + "url": "https://github.com/symfony/mime.git", + "reference": "c0c418f05e727606e85b482a8591519c4712cf45" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/e3d826245268269cd66f8326bd8bc066687b4a19", - "reference": "e3d826245268269cd66f8326bd8bc066687b4a19", + "url": "https://api.github.com/repos/symfony/mime/zipball/c0c418f05e727606e85b482a8591519c4712cf45", + "reference": "c0c418f05e727606e85b482a8591519c4712cf45", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-intl-idn": "^1.10", + "symfony/polyfill-mbstring": "^1.0", + "symfony/polyfill-php80": "^1.15" + }, + "conflict": { + "symfony/mailer": "<4.4" + }, + "require-dev": { + "egulias/email-validator": "^2.1.10", + "symfony/dependency-injection": "^4.4|^5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Mime\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A library to manipulate MIME messages", + "homepage": "https://symfony.com", + "keywords": [ + "mime", + "mime-type" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-06-09T15:07:35+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.17.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "2edd75b8b35d62fd3eeabba73b26b8f1f60ce13d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/2edd75b8b35d62fd3eeabba73b26b8f1f60ce13d", + "reference": "2edd75b8b35d62fd3eeabba73b26b8f1f60ce13d", "shasum": "" }, "require": { @@ -3517,7 +4331,11 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.9-dev" + "dev-master": "1.17-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" } }, "autoload": { @@ -3533,13 +4351,13 @@ "MIT" ], "authors": [ - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - }, { "name": "Gert de Pagter", "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], "description": "Symfony polyfill for ctype functions", @@ -3550,20 +4368,350 @@ "polyfill", "portable" ], - "time": "2018-08-06T14:22:27+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-06-06T08:46:27+00:00" }, { - "name": "symfony/polyfill-mbstring", - "version": "v1.10.0", + "name": "symfony/polyfill-iconv", + "version": "v1.17.1", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "c79c051f5b3a46be09205c73b80b346e4153e494" + "url": "https://github.com/symfony/polyfill-iconv.git", + "reference": "ba6c9c18db36235b859cc29b8372d1c01298c035" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/c79c051f5b3a46be09205c73b80b346e4153e494", - "reference": "c79c051f5b3a46be09205c73b80b346e4153e494", + "url": "https://api.github.com/repos/symfony/polyfill-iconv/zipball/ba6c9c18db36235b859cc29b8372d1c01298c035", + "reference": "ba6c9c18db36235b859cc29b8372d1c01298c035", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-iconv": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.17-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Iconv\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Iconv extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "iconv", + "polyfill", + "portable", + "shim" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-06-06T08:46:27+00:00" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.17.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "6e4dbcf5e81eba86e36731f94fe56b1726835846" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/6e4dbcf5e81eba86e36731f94fe56b1726835846", + "reference": "6e4dbcf5e81eba86e36731f94fe56b1726835846", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.17-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-06-06T08:46:27+00:00" + }, + { + "name": "symfony/polyfill-intl-idn", + "version": "v1.17.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-idn.git", + "reference": "a57f8161502549a742a63c09f0a604997bf47027" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/a57f8161502549a742a63c09f0a604997bf47027", + "reference": "a57f8161502549a742a63c09f0a604997bf47027", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "symfony/polyfill-mbstring": "^1.3", + "symfony/polyfill-php72": "^1.10" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.17-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Intl\\Idn\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Laurent Bassin", + "email": "laurent@bassin.info" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "idn", + "intl", + "polyfill", + "portable", + "shim" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-06-06T08:46:27+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.17.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "40309d1700e8f72447bb9e7b54af756eeea35620" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/40309d1700e8f72447bb9e7b54af756eeea35620", + "reference": "40309d1700e8f72447bb9e7b54af756eeea35620", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.17-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "files": [ + "bootstrap.php" + ], + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-06-14T14:40:37+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.17.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "7110338d81ce1cbc3e273136e4574663627037a7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/7110338d81ce1cbc3e273136e4574663627037a7", + "reference": "7110338d81ce1cbc3e273136e4574663627037a7", "shasum": "" }, "require": { @@ -3575,7 +4723,11 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.9-dev" + "dev-master": "1.17-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" } }, "autoload": { @@ -3609,20 +4761,34 @@ "portable", "shim" ], - "time": "2018-09-21T13:07:52+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-06-06T08:46:27+00:00" }, { "name": "symfony/polyfill-php56", - "version": "v1.10.0", + "version": "v1.17.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php56.git", - "reference": "ff208829fe1aa48ab9af356992bb7199fed551af" + "reference": "a25861bb3c79b0ec2da9ede51de2ea573818b943" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php56/zipball/ff208829fe1aa48ab9af356992bb7199fed551af", - "reference": "ff208829fe1aa48ab9af356992bb7199fed551af", + "url": "https://api.github.com/repos/symfony/polyfill-php56/zipball/a25861bb3c79b0ec2da9ede51de2ea573818b943", + "reference": "a25861bb3c79b0ec2da9ede51de2ea573818b943", "shasum": "" }, "require": { @@ -3632,7 +4798,11 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.9-dev" + "dev-master": "1.17-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" } }, "autoload": { @@ -3665,20 +4835,34 @@ "portable", "shim" ], - "time": "2018-09-21T06:26:08+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-06-06T08:46:27+00:00" }, { "name": "symfony/polyfill-php72", - "version": "v1.10.0", + "version": "v1.17.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php72.git", - "reference": "9050816e2ca34a8e916c3a0ae8b9c2fccf68b631" + "reference": "f048e612a3905f34931127360bdd2def19a5e582" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/9050816e2ca34a8e916c3a0ae8b9c2fccf68b631", - "reference": "9050816e2ca34a8e916c3a0ae8b9c2fccf68b631", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/f048e612a3905f34931127360bdd2def19a5e582", + "reference": "f048e612a3905f34931127360bdd2def19a5e582", "shasum": "" }, "require": { @@ -3687,7 +4871,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.9-dev" + "dev-master": "1.17-dev" } }, "autoload": { @@ -3720,20 +4904,34 @@ "portable", "shim" ], - "time": "2018-09-21T13:07:52+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-05-12T16:47:27+00:00" }, { - "name": "symfony/polyfill-util", - "version": "v1.10.0", + "name": "symfony/polyfill-php73", + "version": "v1.17.1", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-util.git", - "reference": "3b58903eae668d348a7126f999b0da0f2f93611c" + "url": "https://github.com/symfony/polyfill-php73.git", + "reference": "fa0837fe02d617d31fbb25f990655861bb27bd1a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-util/zipball/3b58903eae668d348a7126f999b0da0f2f93611c", - "reference": "3b58903eae668d348a7126f999b0da0f2f93611c", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/fa0837fe02d617d31fbb25f990655861bb27bd1a", + "reference": "fa0837fe02d617d31fbb25f990655861bb27bd1a", "shasum": "" }, "require": { @@ -3742,7 +4940,167 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.9-dev" + "dev-master": "1.17-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php73\\": "" + }, + "files": [ + "bootstrap.php" + ], + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-06-06T08:46:27+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.17.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "4a5b6bba3259902e386eb80dd1956181ee90b5b2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/4a5b6bba3259902e386eb80dd1956181ee90b5b2", + "reference": "4a5b6bba3259902e386eb80dd1956181ee90b5b2", + "shasum": "" + }, + "require": { + "php": ">=7.0.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.17-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "files": [ + "bootstrap.php" + ], + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-06-06T08:46:27+00:00" + }, + { + "name": "symfony/polyfill-util", + "version": "v1.17.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-util.git", + "reference": "6dd644eda43cd2f3daa883d728d8ab4120a05af6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-util/zipball/6dd644eda43cd2f3daa883d728d8ab4120a05af6", + "reference": "6dd644eda43cd2f3daa883d728d8ab4120a05af6", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.17-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" } }, "autoload": { @@ -3772,29 +5130,44 @@ "polyfill", "shim" ], - "time": "2018-09-30T16:36:12+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-06-06T08:46:27+00:00" }, { "name": "symfony/process", - "version": "v4.1.7", + "version": "v5.1.2", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "3e83acef94d979b1de946599ef86b3a352abcdc9" + "reference": "7f6378c1fa2147eeb1b4c385856ce9de0d46ebd1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/3e83acef94d979b1de946599ef86b3a352abcdc9", - "reference": "3e83acef94d979b1de946599ef86b3a352abcdc9", + "url": "https://api.github.com/repos/symfony/process/zipball/7f6378c1fa2147eeb1b4c385856ce9de0d46ebd1", + "reference": "7f6378c1fa2147eeb1b4c385856ce9de0d46ebd1", "shasum": "" }, "require": { - "php": "^7.1.3" + "php": ">=7.2.5", + "symfony/polyfill-php80": "^1.15" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "5.1-dev" } }, "autoload": { @@ -3821,43 +5194,58 @@ ], "description": "Symfony Process Component", "homepage": "https://symfony.com", - "time": "2018-10-14T20:48:13+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-05-30T20:35:19+00:00" }, { "name": "symfony/routing", - "version": "v4.1.7", + "version": "v5.1.2", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "d4a3c14cfbe6b9c05a1d6e948654022d4d1ad3fd" + "reference": "bbd0ba121d623f66d165a55a108008968911f3eb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/d4a3c14cfbe6b9c05a1d6e948654022d4d1ad3fd", - "reference": "d4a3c14cfbe6b9c05a1d6e948654022d4d1ad3fd", + "url": "https://api.github.com/repos/symfony/routing/zipball/bbd0ba121d623f66d165a55a108008968911f3eb", + "reference": "bbd0ba121d623f66d165a55a108008968911f3eb", "shasum": "" }, "require": { - "php": "^7.1.3" + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1", + "symfony/polyfill-php80": "^1.15" }, "conflict": { - "symfony/config": "<3.4", - "symfony/dependency-injection": "<3.4", - "symfony/yaml": "<3.4" + "symfony/config": "<5.0", + "symfony/dependency-injection": "<4.4", + "symfony/yaml": "<4.4" }, "require-dev": { - "doctrine/annotations": "~1.0", + "doctrine/annotations": "~1.2", "psr/log": "~1.0", - "symfony/config": "~3.4|~4.0", - "symfony/dependency-injection": "~3.4|~4.0", - "symfony/expression-language": "~3.4|~4.0", - "symfony/http-foundation": "~3.4|~4.0", - "symfony/yaml": "~3.4|~4.0" + "symfony/config": "^5.0", + "symfony/dependency-injection": "^4.4|^5.0", + "symfony/expression-language": "^4.4|^5.0", + "symfony/http-foundation": "^4.4|^5.0", + "symfony/yaml": "^4.4|^5.0" }, "suggest": { "doctrine/annotations": "For using the annotation loader", "symfony/config": "For using the all-in-one router or any loader", - "symfony/dependency-injection": "For loading routes from a service", "symfony/expression-language": "For using expression matching", "symfony/http-foundation": "For using a Symfony Request object", "symfony/yaml": "For using the YAML loader" @@ -3865,7 +5253,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "5.1-dev" } }, "autoload": { @@ -3898,39 +5286,219 @@ "uri", "url" ], - "time": "2018-10-28T18:38:52+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-06-10T11:49:58+00:00" }, { - "name": "symfony/translation", - "version": "v4.1.7", + "name": "symfony/service-contracts", + "version": "v2.1.2", "source": { "type": "git", - "url": "https://github.com/symfony/translation.git", - "reference": "aa04dc1c75b7d3da7bd7003104cd0cfc5dff635c" + "url": "https://github.com/symfony/service-contracts.git", + "reference": "66a8f0957a3ca54e4f724e49028ab19d75a8918b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/aa04dc1c75b7d3da7bd7003104cd0cfc5dff635c", - "reference": "aa04dc1c75b7d3da7bd7003104cd0cfc5dff635c", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/66a8f0957a3ca54e4f724e49028ab19d75a8918b", + "reference": "66a8f0957a3ca54e4f724e49028ab19d75a8918b", "shasum": "" }, "require": { - "php": "^7.1.3", - "symfony/polyfill-mbstring": "~1.0" + "php": ">=7.2.5", + "psr/container": "^1.0" + }, + "suggest": { + "symfony/service-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-05-20T17:43:50+00:00" + }, + { + "name": "symfony/string", + "version": "v5.1.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "ac70459db781108db7c6d8981dd31ce0e29e3298" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/ac70459db781108db7c6d8981dd31ce0e29e3298", + "reference": "ac70459db781108db7c6d8981dd31ce0e29e3298", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php80": "~1.15" + }, + "require-dev": { + "symfony/error-handler": "^4.4|^5.0", + "symfony/http-client": "^4.4|^5.0", + "symfony/translation-contracts": "^1.1|^2", + "symfony/var-exporter": "^4.4|^5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "files": [ + "Resources/functions.php" + ], + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony String component", + "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-06-11T12:16:36+00:00" + }, + { + "name": "symfony/translation", + "version": "v5.1.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation.git", + "reference": "d387f07d4c15f9c09439cf3f13ddbe0b2c5e8be2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation/zipball/d387f07d4c15f9c09439cf3f13ddbe0b2c5e8be2", + "reference": "d387f07d4c15f9c09439cf3f13ddbe0b2c5e8be2", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php80": "^1.15", + "symfony/translation-contracts": "^2" }, "conflict": { - "symfony/config": "<3.4", - "symfony/dependency-injection": "<3.4", - "symfony/yaml": "<3.4" + "symfony/config": "<4.4", + "symfony/dependency-injection": "<5.0", + "symfony/http-kernel": "<5.0", + "symfony/twig-bundle": "<5.0", + "symfony/yaml": "<4.4" + }, + "provide": { + "symfony/translation-implementation": "2.0" }, "require-dev": { "psr/log": "~1.0", - "symfony/config": "~3.4|~4.0", - "symfony/console": "~3.4|~4.0", - "symfony/dependency-injection": "~3.4|~4.0", - "symfony/finder": "~2.8|~3.0|~4.0", - "symfony/intl": "~3.4|~4.0", - "symfony/yaml": "~3.4|~4.0" + "symfony/config": "^4.4|^5.0", + "symfony/console": "^4.4|^5.0", + "symfony/dependency-injection": "^5.0", + "symfony/finder": "^4.4|^5.0", + "symfony/http-kernel": "^5.0", + "symfony/intl": "^4.4|^5.0", + "symfony/service-contracts": "^1.1.2|^2", + "symfony/yaml": "^4.4|^5.0" }, "suggest": { "psr/log-implementation": "To use logging capability in translator", @@ -3940,7 +5508,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "5.1-dev" } }, "autoload": { @@ -3967,35 +5535,121 @@ ], "description": "Symfony Translation Component", "homepage": "https://symfony.com", - "time": "2018-10-28T18:38:52+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-05-30T20:35:19+00:00" }, { - "name": "symfony/var-dumper", - "version": "v4.1.7", + "name": "symfony/translation-contracts", + "version": "v2.1.2", "source": { "type": "git", - "url": "https://github.com/symfony/var-dumper.git", - "reference": "60319b45653580b0cdacca499344577d87732f16" + "url": "https://github.com/symfony/translation-contracts.git", + "reference": "e5ca07c8f817f865f618aa072c2fe8e0e637340e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/60319b45653580b0cdacca499344577d87732f16", - "reference": "60319b45653580b0cdacca499344577d87732f16", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/e5ca07c8f817f865f618aa072c2fe8e0e637340e", + "reference": "e5ca07c8f817f865f618aa072c2fe8e0e637340e", "shasum": "" }, "require": { - "php": "^7.1.3", + "php": ">=7.2.5" + }, + "suggest": { + "symfony/translation-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Translation\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to translation", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-05-20T17:43:50+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v5.1.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "46a942903059b0b05e601f00eb64179e05578c0f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/46a942903059b0b05e601f00eb64179e05578c0f", + "reference": "46a942903059b0b05e601f00eb64179e05578c0f", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php72": "~1.5" + "symfony/polyfill-php80": "^1.15" }, "conflict": { - "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0", - "symfony/console": "<3.4" + "phpunit/phpunit": "<5.4.3", + "symfony/console": "<4.4" }, "require-dev": { "ext-iconv": "*", - "symfony/process": "~3.4|~4.0", - "twig/twig": "~1.34|~2.4" + "symfony/console": "^4.4|^5.0", + "symfony/process": "^4.4|^5.0", + "twig/twig": "^2.4|^3.0" }, "suggest": { "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", @@ -4008,7 +5662,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "5.1-dev" } }, "autoload": { @@ -4042,25 +5696,114 @@ "debug", "dump" ], - "time": "2018-10-02T16:36:10+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-05-30T20:35:19+00:00" }, { - "name": "tijsverkoyen/css-to-inline-styles", - "version": "2.2.1", + "name": "symfony/yaml", + "version": "v4.4.10", "source": { "type": "git", - "url": "https://github.com/tijsverkoyen/CssToInlineStyles.git", - "reference": "0ed4a2ea4e0902dac0489e6436ebcd5bbcae9757" + "url": "https://github.com/symfony/yaml.git", + "reference": "c2d2cc66e892322cfcc03f8f12f8340dbd7a3f8a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/tijsverkoyen/CssToInlineStyles/zipball/0ed4a2ea4e0902dac0489e6436ebcd5bbcae9757", - "reference": "0ed4a2ea4e0902dac0489e6436ebcd5bbcae9757", + "url": "https://api.github.com/repos/symfony/yaml/zipball/c2d2cc66e892322cfcc03f8f12f8340dbd7a3f8a", + "reference": "c2d2cc66e892322cfcc03f8f12f8340dbd7a3f8a", "shasum": "" }, "require": { + "php": ">=7.1.3", + "symfony/polyfill-ctype": "~1.8" + }, + "conflict": { + "symfony/console": "<3.4" + }, + "require-dev": { + "symfony/console": "^3.4|^4.0|^5.0" + }, + "suggest": { + "symfony/console": "For validating YAML files using the lint command" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Yaml Component", + "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-05-20T08:37:50+00:00" + }, + { + "name": "tijsverkoyen/css-to-inline-styles", + "version": "2.2.2", + "source": { + "type": "git", + "url": "https://github.com/tijsverkoyen/CssToInlineStyles.git", + "reference": "dda2ee426acd6d801d5b7fd1001cde9b5f790e15" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/tijsverkoyen/CssToInlineStyles/zipball/dda2ee426acd6d801d5b7fd1001cde9b5f790e15", + "reference": "dda2ee426acd6d801d5b7fd1001cde9b5f790e15", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", "php": "^5.5 || ^7.0", - "symfony/css-selector": "^2.7 || ^3.0 || ^4.0" + "symfony/css-selector": "^2.7 || ^3.0 || ^4.0 || ^5.0" }, "require-dev": { "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" @@ -4089,32 +5832,41 @@ ], "description": "CssToInlineStyles is a class that enables you to convert HTML-pages/files into HTML-pages/files with inline styles. This is very useful when you're sending emails.", "homepage": "https://github.com/tijsverkoyen/CssToInlineStyles", - "time": "2017-11-27T11:13:29+00:00" + "time": "2019-10-24T08:53:34+00:00" }, { "name": "vlucas/phpdotenv", - "version": "v2.5.1", + "version": "v4.1.7", "source": { "type": "git", "url": "https://github.com/vlucas/phpdotenv.git", - "reference": "8abb4f9aa89ddea9d52112c65bbe8d0125e2fa8e" + "reference": "db63b2ea280fdcf13c4ca392121b0b2450b51193" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/8abb4f9aa89ddea9d52112c65bbe8d0125e2fa8e", - "reference": "8abb4f9aa89ddea9d52112c65bbe8d0125e2fa8e", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/db63b2ea280fdcf13c4ca392121b0b2450b51193", + "reference": "db63b2ea280fdcf13c4ca392121b0b2450b51193", "shasum": "" }, "require": { - "php": ">=5.3.9" + "php": "^5.5.9 || ^7.0 || ^8.0", + "phpoption/phpoption": "^1.7.3", + "symfony/polyfill-ctype": "^1.16" }, "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.0" + "bamarni/composer-bin-plugin": "^1.4.1", + "ext-filter": "*", + "ext-pcre": "*", + "phpunit/phpunit": "^4.8.35 || ^5.7.27 || ^6.5.6 || ^7.0" + }, + "suggest": { + "ext-filter": "Required to use the boolean validator.", + "ext-pcre": "Required to use most of the library." }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.5-dev" + "dev-master": "4.1-dev" } }, "autoload": { @@ -4127,10 +5879,15 @@ "BSD-3-Clause" ], "authors": [ + { + "name": "Graham Campbell", + "email": "graham@alt-three.com", + "homepage": "https://gjcampbell.co.uk/" + }, { "name": "Vance Lucas", "email": "vance@vancelucas.com", - "homepage": "http://www.vancelucas.com" + "homepage": "https://vancelucas.com/" } ], "description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.", @@ -4139,35 +5896,110 @@ "env", "environment" ], - "time": "2018-07-29T20:33:41+00:00" + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/vlucas/phpdotenv", + "type": "tidelift" + } + ], + "time": "2020-06-07T18:25:35+00:00" }, { - "name": "webmozart/assert", - "version": "1.3.0", + "name": "voku/portable-ascii", + "version": "1.5.2", "source": { "type": "git", - "url": "https://github.com/webmozart/assert.git", - "reference": "0df1908962e7a3071564e857d86874dad1ef204a" + "url": "https://github.com/voku/portable-ascii.git", + "reference": "618631dc601d8eb6ea0a9fbf654ec82f066c4e97" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozart/assert/zipball/0df1908962e7a3071564e857d86874dad1ef204a", - "reference": "0df1908962e7a3071564e857d86874dad1ef204a", + "url": "https://api.github.com/repos/voku/portable-ascii/zipball/618631dc601d8eb6ea0a9fbf654ec82f066c4e97", + "reference": "618631dc601d8eb6ea0a9fbf654ec82f066c4e97", "shasum": "" }, "require": { - "php": "^5.3.3 || ^7.0" + "php": ">=7.0.0" }, "require-dev": { - "phpunit/phpunit": "^4.6", - "sebastian/version": "^1.0.1" + "phpunit/phpunit": "~6.0 || ~7.0" + }, + "suggest": { + "ext-intl": "Use Intl for transliterator_transliterate() support" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.3-dev" + "autoload": { + "psr-4": { + "voku\\": "src/voku/" } }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Lars Moelleken", + "homepage": "http://www.moelleken.org/" + } + ], + "description": "Portable ASCII library - performance optimized (ascii) string functions for php.", + "homepage": "https://github.com/voku/portable-ascii", + "keywords": [ + "ascii", + "clean", + "php" + ], + "funding": [ + { + "url": "https://www.paypal.me/moelleken", + "type": "custom" + }, + { + "url": "https://github.com/voku", + "type": "github" + }, + { + "url": "https://www.patreon.com/voku", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/voku/portable-ascii", + "type": "tidelift" + } + ], + "time": "2020-06-15T23:49:30+00:00" + }, + { + "name": "webmozart/assert", + "version": "1.9.0", + "source": { + "type": "git", + "url": "https://github.com/webmozart/assert.git", + "reference": "9dc4f203e36f2b486149058bade43c851dd97451" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozart/assert/zipball/9dc4f203e36f2b486149058bade43c851dd97451", + "reference": "9dc4f203e36f2b486149058bade43c851dd97451", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "phpstan/phpstan": "<0.12.20", + "vimeo/psalm": "<3.9.1" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.36 || ^7.5.13" + }, + "type": "library", "autoload": { "psr-4": { "Webmozart\\Assert\\": "src/" @@ -4189,32 +6021,77 @@ "check", "validate" ], - "time": "2018-01-29T19:49:41+00:00" + "time": "2020-06-16T10:16:42+00:00" } ], "packages-dev": [ { - "name": "barryvdh/laravel-debugbar", - "version": "v3.2.1", + "name": "2bj/phanybar", + "version": "v1.0.0", "source": { "type": "git", - "url": "https://github.com/barryvdh/laravel-debugbar.git", - "reference": "9d5caf43c5f3a3aea2178942f281054805872e7c" + "url": "https://github.com/2bj/Phanybar.git", + "reference": "88ff671e18f30c2047a34f8cf2465a7ff93c819b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/9d5caf43c5f3a3aea2178942f281054805872e7c", - "reference": "9d5caf43c5f3a3aea2178942f281054805872e7c", + "url": "https://api.github.com/repos/2bj/Phanybar/zipball/88ff671e18f30c2047a34f8cf2465a7ff93c819b", + "reference": "88ff671e18f30c2047a34f8cf2465a7ff93c819b", "shasum": "" }, "require": { - "illuminate/routing": "5.5.x|5.6.x|5.7.x", - "illuminate/session": "5.5.x|5.6.x|5.7.x", - "illuminate/support": "5.5.x|5.6.x|5.7.x", - "maximebf/debugbar": "~1.15.0", + "php": ">=5.3.0" + }, + "bin": [ + "bin/phanybar" + ], + "type": "library", + "autoload": { + "psr-4": { + "Bakyt\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bakyt Turgumbaev", + "email": "dev2bj@gmail.com" + } + ], + "description": "Control AnyBar from your php", + "keywords": [ + "anybar", + "phanybar" + ], + "time": "2015-03-06T12:14:28+00:00" + }, + { + "name": "barryvdh/laravel-debugbar", + "version": "v3.3.3", + "source": { + "type": "git", + "url": "https://github.com/barryvdh/laravel-debugbar.git", + "reference": "57f2219f6d9efe41ed1bc880d86701c52f261bf5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/57f2219f6d9efe41ed1bc880d86701c52f261bf5", + "reference": "57f2219f6d9efe41ed1bc880d86701c52f261bf5", + "shasum": "" + }, + "require": { + "illuminate/routing": "^5.5|^6|^7", + "illuminate/session": "^5.5|^6|^7", + "illuminate/support": "^5.5|^6|^7", + "maximebf/debugbar": "^1.15.1", "php": ">=7.0", - "symfony/debug": "^3|^4", - "symfony/finder": "^3|^4" + "symfony/debug": "^3|^4|^5", + "symfony/finder": "^3|^4|^5" }, "require-dev": { "laravel/framework": "5.5.x" @@ -4259,46 +6136,49 @@ "profiler", "webprofiler" ], - "time": "2018-11-09T08:37:55+00:00" + "funding": [ + { + "url": "https://github.com/barryvdh", + "type": "github" + } + ], + "time": "2020-05-05T10:53:32+00:00" }, { "name": "barryvdh/laravel-ide-helper", - "version": "v2.5.2", + "version": "v2.7.0", "source": { "type": "git", "url": "https://github.com/barryvdh/laravel-ide-helper.git", - "reference": "981ff45b43e0cf808af0a5a5f40f6369e0e29499" + "reference": "5f677edc14bdcfdcac36633e6eea71b2728a4dbc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/981ff45b43e0cf808af0a5a5f40f6369e0e29499", - "reference": "981ff45b43e0cf808af0a5a5f40f6369e0e29499", + "url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/5f677edc14bdcfdcac36633e6eea71b2728a4dbc", + "reference": "5f677edc14bdcfdcac36633e6eea71b2728a4dbc", "shasum": "" }, "require": { - "barryvdh/reflection-docblock": "^2.0.4", + "barryvdh/reflection-docblock": "^2.0.6", "composer/composer": "^1.6", - "illuminate/console": "^5.5,<5.8", - "illuminate/filesystem": "^5.5,<5.8", - "illuminate/support": "^5.5,<5.8", - "php": ">=7" + "doctrine/dbal": "~2.3", + "illuminate/console": "^5.5|^6|^7", + "illuminate/filesystem": "^5.5|^6|^7", + "illuminate/support": "^5.5|^6|^7", + "php": ">=7.2" }, "require-dev": { - "doctrine/dbal": "~2.3", - "illuminate/config": "^5.1,<5.8", - "illuminate/view": "^5.1,<5.8", - "phpro/grumphp": "^0.14", - "phpunit/phpunit": "4.*", - "scrutinizer/ocular": "~1.1", + "illuminate/config": "^5.5|^6|^7", + "illuminate/view": "^5.5|^6|^7", + "mockery/mockery": "^1.3", + "orchestra/testbench": "^3|^4|^5", + "phpro/grumphp": "^0.17.1", "squizlabs/php_codesniffer": "^3" }, - "suggest": { - "doctrine/dbal": "Load information from the database about models for phpdocs (~2.3)" - }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.5-dev" + "dev-master": "2.6-dev" }, "laravel": { "providers": [ @@ -4333,20 +6213,26 @@ "phpstorm", "sublime" ], - "time": "2018-10-06T09:35:51+00:00" + "funding": [ + { + "url": "https://github.com/barryvdh", + "type": "github" + } + ], + "time": "2020-04-22T09:57:26+00:00" }, { "name": "barryvdh/reflection-docblock", - "version": "v2.0.5", + "version": "v2.0.6", "source": { "type": "git", "url": "https://github.com/barryvdh/ReflectionDocBlock.git", - "reference": "64165bd4ba9a550d11ea57569463b7c722dc6b0a" + "reference": "6b69015d83d3daf9004a71a89f26e27d27ef6a16" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/barryvdh/ReflectionDocBlock/zipball/64165bd4ba9a550d11ea57569463b7c722dc6b0a", - "reference": "64165bd4ba9a550d11ea57569463b7c722dc6b0a", + "url": "https://api.github.com/repos/barryvdh/ReflectionDocBlock/zipball/6b69015d83d3daf9004a71a89f26e27d27ef6a16", + "reference": "6b69015d83d3daf9004a71a89f26e27d27ef6a16", "shasum": "" }, "require": { @@ -4382,20 +6268,20 @@ "email": "mike.vanriel@naenius.com" } ], - "time": "2018-10-25T19:09:52+00:00" + "time": "2018-12-13T10:34:14+00:00" }, { "name": "codedungeon/php-cli-colors", - "version": "1.10.7", + "version": "1.11.0", "source": { "type": "git", "url": "https://github.com/mikeerickson/php-cli-colors.git", - "reference": "5649ef76ec0c9ed626e95bf40fdfaf4b8efcf79b" + "reference": "9f60ac692cc790755dad47b01c1d607fe5f43b94" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/mikeerickson/php-cli-colors/zipball/5649ef76ec0c9ed626e95bf40fdfaf4b8efcf79b", - "reference": "5649ef76ec0c9ed626e95bf40fdfaf4b8efcf79b", + "url": "https://api.github.com/repos/mikeerickson/php-cli-colors/zipball/9f60ac692cc790755dad47b01c1d607fe5f43b94", + "reference": "9f60ac692cc790755dad47b01c1d607fe5f43b94", "shasum": "" }, "require-dev": { @@ -4426,31 +6312,31 @@ "package", "php" ], - "time": "2018-05-17T01:34:14+00:00" + "time": "2019-12-29T22:29:29+00:00" }, { "name": "codedungeon/phpunit-result-printer", - "version": "0.17.1", + "version": "0.28.0", "source": { "type": "git", "url": "https://github.com/mikeerickson/phpunit-pretty-result-printer.git", - "reference": "aac73dbc502e70d42059d74a5aced6911982797b" + "reference": "bc023b0311589bee19047425083163ffa3f0cf88" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/mikeerickson/phpunit-pretty-result-printer/zipball/aac73dbc502e70d42059d74a5aced6911982797b", - "reference": "aac73dbc502e70d42059d74a5aced6911982797b", + "url": "https://api.github.com/repos/mikeerickson/phpunit-pretty-result-printer/zipball/bc023b0311589bee19047425083163ffa3f0cf88", + "reference": "bc023b0311589bee19047425083163ffa3f0cf88", "shasum": "" }, "require": { - "codedungeon/php-cli-colors": "^1.10", - "hassankhan/config": "^0.10.0", + "2bj/phanybar": "^1.0", + "codedungeon/php-cli-colors": "^1.10.2", + "hassankhan/config": "^0.11.2", "php": "^7.1", - "symfony/yaml": "^2.7|^3.0|^4.0" + "symfony/yaml": "^2.7|^3.0|^4.0|^5.0" }, "require-dev": { - "phpunit/phpunit": "7.1.1", - "spatie/phpunit-watcher": "^1.5" + "spatie/phpunit-watcher": "^1.6" }, "type": "library", "autoload": { @@ -4478,31 +6364,31 @@ "result-printer", "testing" ], - "time": "2018-05-09T02:10:52+00:00" + "time": "2020-06-24T00:16:05+00:00" }, { "name": "composer/ca-bundle", - "version": "1.1.3", + "version": "1.2.7", "source": { "type": "git", "url": "https://github.com/composer/ca-bundle.git", - "reference": "8afa52cd417f4ec417b4bfe86b68106538a87660" + "reference": "95c63ab2117a72f48f5a55da9740a3273d45b7fd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/ca-bundle/zipball/8afa52cd417f4ec417b4bfe86b68106538a87660", - "reference": "8afa52cd417f4ec417b4bfe86b68106538a87660", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/95c63ab2117a72f48f5a55da9740a3273d45b7fd", + "reference": "95c63ab2117a72f48f5a55da9740a3273d45b7fd", "shasum": "" }, "require": { "ext-openssl": "*", "ext-pcre": "*", - "php": "^5.3.2 || ^7.0" + "php": "^5.3.2 || ^7.0 || ^8.0" }, "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5", + "phpunit/phpunit": "^4.8.35 || ^5.7 || 6.5 - 8", "psr/log": "^1.0", - "symfony/process": "^2.5 || ^3.0 || ^4.0" + "symfony/process": "^2.5 || ^3.0 || ^4.0 || ^5.0" }, "type": "library", "extra": { @@ -4534,20 +6420,30 @@ "ssl", "tls" ], - "time": "2018-10-18T06:09:13+00:00" + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2020-04-08T08:27:21+00:00" }, { "name": "composer/composer", - "version": "1.7.3", + "version": "1.10.7", "source": { "type": "git", "url": "https://github.com/composer/composer.git", - "reference": "e965b9aaa8854c3067f1ed2ae45f436572d73eb7" + "reference": "956608ea4f7de9e58c53dfb019d85ae62b193c39" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/composer/zipball/e965b9aaa8854c3067f1ed2ae45f436572d73eb7", - "reference": "e965b9aaa8854c3067f1ed2ae45f436572d73eb7", + "url": "https://api.github.com/repos/composer/composer/zipball/956608ea4f7de9e58c53dfb019d85ae62b193c39", + "reference": "956608ea4f7de9e58c53dfb019d85ae62b193c39", "shasum": "" }, "require": { @@ -4555,22 +6451,23 @@ "composer/semver": "^1.0", "composer/spdx-licenses": "^1.2", "composer/xdebug-handler": "^1.1", - "justinrainbow/json-schema": "^3.0 || ^4.0 || ^5.0", + "justinrainbow/json-schema": "^5.2.10", "php": "^5.3.2 || ^7.0", "psr/log": "^1.0", "seld/jsonlint": "^1.4", "seld/phar-utils": "^1.0", - "symfony/console": "^2.7 || ^3.0 || ^4.0", - "symfony/filesystem": "^2.7 || ^3.0 || ^4.0", - "symfony/finder": "^2.7 || ^3.0 || ^4.0", - "symfony/process": "^2.7 || ^3.0 || ^4.0" + "symfony/console": "^2.7 || ^3.0 || ^4.0 || ^5.0", + "symfony/filesystem": "^2.7 || ^3.0 || ^4.0 || ^5.0", + "symfony/finder": "^2.7 || ^3.0 || ^4.0 || ^5.0", + "symfony/process": "^2.7 || ^3.0 || ^4.0 || ^5.0" }, "conflict": { - "symfony/console": "2.8.38" + "symfony/console": "2.8.38", + "symfony/phpunit-bridge": "3.4.40" }, "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.7", - "phpunit/phpunit-mock-objects": "^2.3 || ^3.0" + "phpspec/prophecy": "^1.10", + "symfony/phpunit-bridge": "^3.4" }, "suggest": { "ext-openssl": "Enabling the openssl extension allows you to access https URLs for repositories and packages", @@ -4583,7 +6480,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.7-dev" + "dev-master": "1.10-dev" } }, "autoload": { @@ -4607,35 +6504,48 @@ "homepage": "http://seld.be" } ], - "description": "Composer helps you declare, manage and install dependencies of PHP projects, ensuring you have the right stack everywhere.", + "description": "Composer helps you declare, manage and install dependencies of PHP projects. It ensures you have the right stack everywhere.", "homepage": "https://getcomposer.org/", "keywords": [ "autoload", "dependency", "package" ], - "time": "2018-11-01T09:05:06+00:00" + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2020-06-03T08:03:56+00:00" }, { "name": "composer/semver", - "version": "1.4.2", + "version": "1.5.1", "source": { "type": "git", "url": "https://github.com/composer/semver.git", - "reference": "c7cb9a2095a074d131b65a8a0cd294479d785573" + "reference": "c6bea70230ef4dd483e6bbcab6005f682ed3a8de" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/c7cb9a2095a074d131b65a8a0cd294479d785573", - "reference": "c7cb9a2095a074d131b65a8a0cd294479d785573", + "url": "https://api.github.com/repos/composer/semver/zipball/c6bea70230ef4dd483e6bbcab6005f682ed3a8de", + "reference": "c6bea70230ef4dd483e6bbcab6005f682ed3a8de", "shasum": "" }, "require": { "php": "^5.3.2 || ^7.0" }, "require-dev": { - "phpunit/phpunit": "^4.5 || ^5.0.5", - "phpunit/phpunit-mock-objects": "2.3.0 || ^3.0" + "phpunit/phpunit": "^4.5 || ^5.0.5" }, "type": "library", "extra": { @@ -4676,28 +6586,27 @@ "validation", "versioning" ], - "time": "2016-08-30T16:08:34+00:00" + "time": "2020-01-13T12:06:48+00:00" }, { "name": "composer/spdx-licenses", - "version": "1.5.0", + "version": "1.5.3", "source": { "type": "git", "url": "https://github.com/composer/spdx-licenses.git", - "reference": "7a9556b22bd9d4df7cad89876b00af58ef20d3a2" + "reference": "0c3e51e1880ca149682332770e25977c70cf9dae" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/spdx-licenses/zipball/7a9556b22bd9d4df7cad89876b00af58ef20d3a2", - "reference": "7a9556b22bd9d4df7cad89876b00af58ef20d3a2", + "url": "https://api.github.com/repos/composer/spdx-licenses/zipball/0c3e51e1880ca149682332770e25977c70cf9dae", + "reference": "0c3e51e1880ca149682332770e25977c70cf9dae", "shasum": "" }, "require": { - "php": "^5.3.2 || ^7.0" + "php": "^5.3.2 || ^7.0 || ^8.0" }, "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5", - "phpunit/phpunit-mock-objects": "2.3.0 || ^3.0" + "phpunit/phpunit": "^4.8.35 || ^5.7 || 6.5 - 7" }, "type": "library", "extra": { @@ -4737,28 +6646,28 @@ "spdx", "validator" ], - "time": "2018-11-01T09:45:54+00:00" + "time": "2020-02-14T07:44:31+00:00" }, { "name": "composer/xdebug-handler", - "version": "1.3.0", + "version": "1.4.2", "source": { "type": "git", "url": "https://github.com/composer/xdebug-handler.git", - "reference": "b8e9745fb9b06ea6664d8872c4505fb16df4611c" + "reference": "fa2aaf99e2087f013a14f7432c1cd2dd7d8f1f51" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/b8e9745fb9b06ea6664d8872c4505fb16df4611c", - "reference": "b8e9745fb9b06ea6664d8872c4505fb16df4611c", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/fa2aaf99e2087f013a14f7432c1cd2dd7d8f1f51", + "reference": "fa2aaf99e2087f013a14f7432c1cd2dd7d8f1f51", "shasum": "" }, "require": { - "php": "^5.3.2 || ^7.0", + "php": "^5.3.2 || ^7.0 || ^8.0", "psr/log": "^1.0" }, "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5" + "phpunit/phpunit": "^4.8.35 || ^5.7 || 6.5 - 8" }, "type": "library", "autoload": { @@ -4776,39 +6685,54 @@ "email": "john-stevenson@blueyonder.co.uk" } ], - "description": "Restarts a process without xdebug.", + "description": "Restarts a process without Xdebug.", "keywords": [ "Xdebug", "performance" ], - "time": "2018-08-31T19:07:57+00:00" + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2020-06-04T11:16:35+00:00" }, { "name": "doctrine/annotations", - "version": "v1.6.0", + "version": "1.10.3", "source": { "type": "git", "url": "https://github.com/doctrine/annotations.git", - "reference": "c7f2050c68a9ab0bdb0f98567ec08d80ea7d24d5" + "reference": "5db60a4969eba0e0c197a19c077780aadbc43c5d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/annotations/zipball/c7f2050c68a9ab0bdb0f98567ec08d80ea7d24d5", - "reference": "c7f2050c68a9ab0bdb0f98567ec08d80ea7d24d5", + "url": "https://api.github.com/repos/doctrine/annotations/zipball/5db60a4969eba0e0c197a19c077780aadbc43c5d", + "reference": "5db60a4969eba0e0c197a19c077780aadbc43c5d", "shasum": "" }, "require": { "doctrine/lexer": "1.*", - "php": "^7.1" + "ext-tokenizer": "*", + "php": "^7.1 || ^8.0" }, "require-dev": { "doctrine/cache": "1.*", - "phpunit/phpunit": "^6.4" + "phpunit/phpunit": "^7.5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.6.x-dev" + "dev-master": "1.9.x-dev" } }, "autoload": { @@ -4821,6 +6745,10 @@ "MIT" ], "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, { "name": "Roman Borschel", "email": "roman@code-factory.org" @@ -4829,10 +6757,6 @@ "name": "Benjamin Eberlei", "email": "kontakt@beberlei.de" }, - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, { "name": "Jonathan Wage", "email": "jonwage@gmail.com" @@ -4849,31 +6773,33 @@ "docblock", "parser" ], - "time": "2017-12-06T07:11:42+00:00" + "time": "2020-05-25T17:24:27+00:00" }, { "name": "doctrine/instantiator", - "version": "1.1.0", + "version": "1.3.1", "source": { "type": "git", "url": "https://github.com/doctrine/instantiator.git", - "reference": "185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda" + "reference": "f350df0268e904597e3bd9c4685c53e0e333feea" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda", - "reference": "185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/f350df0268e904597e3bd9c4685c53e0e333feea", + "reference": "f350df0268e904597e3bd9c4685c53e0e333feea", "shasum": "" }, "require": { - "php": "^7.1" + "php": "^7.1 || ^8.0" }, "require-dev": { - "athletic/athletic": "~0.1.8", + "doctrine/coding-standard": "^6.0", "ext-pdo": "*", "ext-phar": "*", - "phpunit/phpunit": "^6.2.3", - "squizlabs/php_codesniffer": "^3.0.2" + "phpbench/phpbench": "^0.13", + "phpstan/phpstan-phpunit": "^0.11", + "phpstan/phpstan-shim": "^0.11", + "phpunit/phpunit": "^7.0" }, "type": "library", "extra": { @@ -4898,86 +6824,39 @@ } ], "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", - "homepage": "https://github.com/doctrine/instantiator", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", "keywords": [ "constructor", "instantiate" ], - "time": "2017-07-22T11:58:36+00:00" - }, - { - "name": "filp/whoops", - "version": "2.3.1", - "source": { - "type": "git", - "url": "https://github.com/filp/whoops.git", - "reference": "bc0fd11bc455cc20ee4b5edabc63ebbf859324c7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/filp/whoops/zipball/bc0fd11bc455cc20ee4b5edabc63ebbf859324c7", - "reference": "bc0fd11bc455cc20ee4b5edabc63ebbf859324c7", - "shasum": "" - }, - "require": { - "php": "^5.5.9 || ^7.0", - "psr/log": "^1.0.1" - }, - "require-dev": { - "mockery/mockery": "^0.9 || ^1.0", - "phpunit/phpunit": "^4.8.35 || ^5.7", - "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0" - }, - "suggest": { - "symfony/var-dumper": "Pretty print complex values better with var-dumper available", - "whoops/soap": "Formats errors as SOAP responses" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.2-dev" - } - }, - "autoload": { - "psr-4": { - "Whoops\\": "src/Whoops/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ + "funding": [ { - "name": "Filipe Dobreira", - "homepage": "https://github.com/filp", - "role": "Developer" + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", + "type": "tidelift" } ], - "description": "php error handling for cool kids", - "homepage": "https://filp.github.io/whoops/", - "keywords": [ - "error", - "exception", - "handling", - "library", - "throwable", - "whoops" - ], - "time": "2018-10-23T09:00:00+00:00" + "time": "2020-05-29T17:27:14+00:00" }, { "name": "friendsofphp/php-cs-fixer", - "version": "v2.15.1", + "version": "v2.16.1", "source": { "type": "git", "url": "https://github.com/FriendsOfPHP/PHP-CS-Fixer.git", - "reference": "20064511ab796593a3990669eff5f5b535001f7c" + "reference": "c8afb599858876e95e8ebfcd97812d383fa23f02" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/20064511ab796593a3990669eff5f5b535001f7c", - "reference": "20064511ab796593a3990669eff5f5b535001f7c", + "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/c8afb599858876e95e8ebfcd97812d383fa23f02", + "reference": "c8afb599858876e95e8ebfcd97812d383fa23f02", "shasum": "" }, "require": { @@ -4988,15 +6867,15 @@ "ext-tokenizer": "*", "php": "^5.6 || ^7.0", "php-cs-fixer/diff": "^1.3", - "symfony/console": "^3.4.17 || ^4.1.6", - "symfony/event-dispatcher": "^3.0 || ^4.0", - "symfony/filesystem": "^3.0 || ^4.0", - "symfony/finder": "^3.0 || ^4.0", - "symfony/options-resolver": "^3.0 || ^4.0", + "symfony/console": "^3.4.17 || ^4.1.6 || ^5.0", + "symfony/event-dispatcher": "^3.0 || ^4.0 || ^5.0", + "symfony/filesystem": "^3.0 || ^4.0 || ^5.0", + "symfony/finder": "^3.0 || ^4.0 || ^5.0", + "symfony/options-resolver": "^3.0 || ^4.0 || ^5.0", "symfony/polyfill-php70": "^1.0", "symfony/polyfill-php72": "^1.4", - "symfony/process": "^3.0 || ^4.0", - "symfony/stopwatch": "^3.0 || ^4.0" + "symfony/process": "^3.0 || ^4.0 || ^5.0", + "symfony/stopwatch": "^3.0 || ^4.0 || ^5.0" }, "require-dev": { "johnkary/phpunit-speedtrap": "^1.1 || ^2.0 || ^3.0", @@ -5007,9 +6886,10 @@ "php-cs-fixer/accessible-object": "^1.0", "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.1", "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.1", - "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1", + "phpunit/phpunit": "^5.7.27 || ^6.5.14 || ^7.1", "phpunitgoodpractices/traits": "^1.8", - "symfony/phpunit-bridge": "^4.3" + "symfony/phpunit-bridge": "^4.3 || ^5.0", + "symfony/yaml": "^3.0 || ^4.0 || ^5.0" }, "suggest": { "ext-mbstring": "For handling non-UTF8 characters in cache signature.", @@ -5042,30 +6922,30 @@ "MIT" ], "authors": [ - { - "name": "Dariusz Rumiński", - "email": "dariusz.ruminski@gmail.com" - }, { "name": "Fabien Potencier", "email": "fabien@symfony.com" + }, + { + "name": "Dariusz Rumiński", + "email": "dariusz.ruminski@gmail.com" } ], "description": "A tool to automatically fix PHP code style", - "time": "2019-06-01T10:32:12+00:00" + "time": "2019-11-25T22:10:32+00:00" }, { "name": "fzaninotto/faker", - "version": "v1.8.0", + "version": "v1.9.1", "source": { "type": "git", "url": "https://github.com/fzaninotto/Faker.git", - "reference": "f72816b43e74063c8b10357394b6bba8cb1c10de" + "reference": "fc10d778e4b84d5bd315dad194661e091d307c6f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/fzaninotto/Faker/zipball/f72816b43e74063c8b10357394b6bba8cb1c10de", - "reference": "f72816b43e74063c8b10357394b6bba8cb1c10de", + "url": "https://api.github.com/repos/fzaninotto/Faker/zipball/fc10d778e4b84d5bd315dad194661e091d307c6f", + "reference": "fc10d778e4b84d5bd315dad194661e091d307c6f", "shasum": "" }, "require": { @@ -5074,12 +6954,12 @@ "require-dev": { "ext-intl": "*", "phpunit/phpunit": "^4.8.35 || ^5.7", - "squizlabs/php_codesniffer": "^1.5" + "squizlabs/php_codesniffer": "^2.9.2" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.8-dev" + "dev-master": "1.9-dev" } }, "autoload": { @@ -5102,7 +6982,7 @@ "faker", "fixtures" ], - "time": "2018-07-12T10:23:15+00:00" + "time": "2019-12-12T13:22:17+00:00" }, { "name": "hamcrest/hamcrest-php", @@ -5154,16 +7034,16 @@ }, { "name": "hassankhan/config", - "version": "0.10.0", + "version": "0.11.2", "source": { "type": "git", "url": "https://github.com/hassankhan/config.git", - "reference": "06ac500348af033f1a2e44dc357ca86282626d4a" + "reference": "7fbc236c32dc6cc53a7b00992a2739cf8b41c085" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/hassankhan/config/zipball/06ac500348af033f1a2e44dc357ca86282626d4a", - "reference": "06ac500348af033f1a2e44dc357ca86282626d4a", + "url": "https://api.github.com/repos/hassankhan/config/zipball/7fbc236c32dc6cc53a7b00992a2739cf8b41c085", + "reference": "7fbc236c32dc6cc53a7b00992a2739cf8b41c085", "shasum": "" }, "require": { @@ -5207,27 +7087,27 @@ "yaml", "yml" ], - "time": "2016-02-11T16:21:17+00:00" + "time": "2017-11-07T22:49:43+00:00" }, { "name": "justinrainbow/json-schema", - "version": "5.2.7", + "version": "5.2.10", "source": { "type": "git", "url": "https://github.com/justinrainbow/json-schema.git", - "reference": "8560d4314577199ba51bf2032f02cd1315587c23" + "reference": "2ba9c8c862ecd5510ed16c6340aa9f6eadb4f31b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/justinrainbow/json-schema/zipball/8560d4314577199ba51bf2032f02cd1315587c23", - "reference": "8560d4314577199ba51bf2032f02cd1315587c23", + "url": "https://api.github.com/repos/justinrainbow/json-schema/zipball/2ba9c8c862ecd5510ed16c6340aa9f6eadb4f31b", + "reference": "2ba9c8c862ecd5510ed16c6340aa9f6eadb4f31b", "shasum": "" }, "require": { "php": ">=5.3.3" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^2.1", + "friendsofphp/php-cs-fixer": "~2.2.20||~2.15.1", "json-schema/json-schema-test-suite": "1.2.0", "phpunit/phpunit": "^4.8.35" }, @@ -5273,29 +7153,97 @@ "json", "schema" ], - "time": "2018-02-14T22:26:30+00:00" + "time": "2020-05-27T16:41:55+00:00" }, { - "name": "maximebf/debugbar", - "version": "v1.15.0", + "name": "laravel/dusk", + "version": "v6.3.0", "source": { "type": "git", - "url": "https://github.com/maximebf/php-debugbar.git", - "reference": "30e7d60937ee5f1320975ca9bc7bcdd44d500f07" + "url": "https://github.com/laravel/dusk.git", + "reference": "5481bfd50c80599d26529b7f2c9adeb2154a57fc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/30e7d60937ee5f1320975ca9bc7bcdd44d500f07", - "reference": "30e7d60937ee5f1320975ca9bc7bcdd44d500f07", + "url": "https://api.github.com/repos/laravel/dusk/zipball/5481bfd50c80599d26529b7f2c9adeb2154a57fc", + "reference": "5481bfd50c80599d26529b7f2c9adeb2154a57fc", "shasum": "" }, "require": { - "php": ">=5.3.0", - "psr/log": "^1.0", - "symfony/var-dumper": "^2.6|^3.0|^4.0" + "ext-json": "*", + "ext-zip": "*", + "illuminate/console": "^6.0|^7.0", + "illuminate/support": "^6.0|^7.0", + "nesbot/carbon": "^2.0", + "php": "^7.2", + "php-webdriver/webdriver": "^1.8.1", + "symfony/console": "^4.3|^5.0", + "symfony/finder": "^4.3|^5.0", + "symfony/process": "^4.3|^5.0", + "vlucas/phpdotenv": "^3.0|^4.0" }, "require-dev": { - "phpunit/phpunit": "^4.0|^5.0" + "mockery/mockery": "^1.0", + "phpunit/phpunit": "^7.5.15|^8.4|^9.0" + }, + "suggest": { + "ext-pcntl": "Used to gracefully terminate Dusk when tests are running." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "6.x-dev" + }, + "laravel": { + "providers": [ + "Laravel\\Dusk\\DuskServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Laravel\\Dusk\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "Laravel Dusk provides simple end-to-end testing and browser automation.", + "keywords": [ + "laravel", + "testing", + "webdriver" + ], + "time": "2020-06-16T19:05:20+00:00" + }, + { + "name": "maximebf/debugbar", + "version": "v1.16.3", + "source": { + "type": "git", + "url": "https://github.com/maximebf/php-debugbar.git", + "reference": "1a1605b8e9bacb34cc0c6278206d699772e1d372" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/1a1605b8e9bacb34cc0c6278206d699772e1d372", + "reference": "1a1605b8e9bacb34cc0c6278206d699772e1d372", + "shasum": "" + }, + "require": { + "php": "^7.1", + "psr/log": "^1.0", + "symfony/var-dumper": "^2.6|^3|^4|^5" + }, + "require-dev": { + "phpunit/phpunit": "^5" }, "suggest": { "kriswallsmith/assetic": "The best way to manage assets", @@ -5305,7 +7253,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.14-dev" + "dev-master": "1.16-dev" } }, "autoload": { @@ -5334,34 +7282,37 @@ "debug", "debugbar" ], - "time": "2017-12-15T11:13:46+00:00" + "time": "2020-05-06T07:06:27+00:00" }, { "name": "mockery/mockery", - "version": "1.2.0", + "version": "1.4.0", "source": { "type": "git", "url": "https://github.com/mockery/mockery.git", - "reference": "100633629bf76d57430b86b7098cd6beb996a35a" + "reference": "6c6a7c533469873deacf998237e7649fc6b36223" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/mockery/mockery/zipball/100633629bf76d57430b86b7098cd6beb996a35a", - "reference": "100633629bf76d57430b86b7098cd6beb996a35a", + "url": "https://api.github.com/repos/mockery/mockery/zipball/6c6a7c533469873deacf998237e7649fc6b36223", + "reference": "6c6a7c533469873deacf998237e7649fc6b36223", "shasum": "" }, "require": { "hamcrest/hamcrest-php": "~2.0", "lib-pcre": ">=7.0", - "php": ">=5.6.0" + "php": "^7.3.0" + }, + "conflict": { + "phpunit/phpunit": "<8.0" }, "require-dev": { - "phpunit/phpunit": "~5.7.10|~6.5|~7.0" + "phpunit/phpunit": "^8.0.0 || ^9.0.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "1.4.x-dev" } }, "autoload": { @@ -5399,20 +7350,20 @@ "test double", "testing" ], - "time": "2018-10-02T21:52:37+00:00" + "time": "2020-05-19T14:25:16+00:00" }, { "name": "myclabs/deep-copy", - "version": "1.8.1", + "version": "1.9.5", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "3e01bdad3e18354c3dce54466b7fbe33a9f9f7f8" + "reference": "b2c28789e80a97badd14145fda39b545d83ca3ef" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3e01bdad3e18354c3dce54466b7fbe33a9f9f7f8", - "reference": "3e01bdad3e18354c3dce54466b7fbe33a9f9f7f8", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/b2c28789e80a97badd14145fda39b545d83ca3ef", + "reference": "b2c28789e80a97badd14145fda39b545d83ca3ef", "shasum": "" }, "require": { @@ -5447,71 +7398,7 @@ "object", "object graph" ], - "time": "2018-06-11T23:09:50+00:00" - }, - { - "name": "nunomaduro/collision", - "version": "v2.1.1", - "source": { - "type": "git", - "url": "https://github.com/nunomaduro/collision.git", - "reference": "b5feb0c0d92978ec7169232ce5d70d6da6b29f63" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/collision/zipball/b5feb0c0d92978ec7169232ce5d70d6da6b29f63", - "reference": "b5feb0c0d92978ec7169232ce5d70d6da6b29f63", - "shasum": "" - }, - "require": { - "filp/whoops": "^2.1.4", - "jakub-onderka/php-console-highlighter": "0.3.*|0.4.*", - "php": "^7.1", - "symfony/console": "~2.8|~3.3|~4.0" - }, - "require-dev": { - "laravel/framework": "5.7.*", - "nunomaduro/larastan": "^0.3.0", - "phpstan/phpstan": "^0.10", - "phpunit/phpunit": "~7.3" - }, - "type": "library", - "extra": { - "laravel": { - "providers": [ - "NunoMaduro\\Collision\\Adapters\\Laravel\\CollisionServiceProvider" - ] - } - }, - "autoload": { - "psr-4": { - "NunoMaduro\\Collision\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nuno Maduro", - "email": "enunomaduro@gmail.com" - } - ], - "description": "Cli error handling for console/command-line PHP applications.", - "keywords": [ - "artisan", - "cli", - "command-line", - "console", - "error", - "handling", - "laravel", - "laravel-zero", - "php", - "symfony" - ], - "time": "2018-11-21T21:40:54+00:00" + "time": "2020-01-17T21:11:47+00:00" }, { "name": "phar-io/manifest", @@ -5668,33 +7555,36 @@ }, { "name": "php-mock/php-mock", - "version": "2.0.0", + "version": "2.2.2", "source": { "type": "git", "url": "https://github.com/php-mock/php-mock.git", - "reference": "22d297231118e6fd5b9db087fbe1ef866c2b95d2" + "reference": "890d3e32e3a5f29715a8fd17debd87a0c9e614a0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-mock/php-mock/zipball/22d297231118e6fd5b9db087fbe1ef866c2b95d2", - "reference": "22d297231118e6fd5b9db087fbe1ef866c2b95d2", + "url": "https://api.github.com/repos/php-mock/php-mock/zipball/890d3e32e3a5f29715a8fd17debd87a0c9e614a0", + "reference": "890d3e32e3a5f29715a8fd17debd87a0c9e614a0", "shasum": "" }, "require": { - "php": ">=5.6", - "phpunit/php-text-template": "^1" + "php": "^5.6 || ^7.0", + "phpunit/php-text-template": "^1 || ^2" }, "replace": { "malkusch/php-mock": "*" }, "require-dev": { - "phpunit/phpunit": "^5.7" + "phpunit/phpunit": "^5.7 || ^6.5 || ^7.5 || ^8.0 || ^9.0" }, "suggest": { "php-mock/php-mock-phpunit": "Allows integration into PHPUnit testcase with the trait PHPMock." }, "type": "library", "autoload": { + "files": [ + "autoload.php" + ], "psr-4": { "phpmock\\": [ "classes/", @@ -5725,29 +7615,29 @@ "test", "test double" ], - "time": "2017-02-17T20:52:52+00:00" + "time": "2020-04-17T16:39:00+00:00" }, { "name": "php-mock/php-mock-integration", - "version": "2.0.0", + "version": "2.1.0", "source": { "type": "git", "url": "https://github.com/php-mock/php-mock-integration.git", - "reference": "5a0d7d7755f823bc2a230cfa45058b40f9013bc4" + "reference": "003d585841e435958a02e9b986953907b8b7609b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-mock/php-mock-integration/zipball/5a0d7d7755f823bc2a230cfa45058b40f9013bc4", - "reference": "5a0d7d7755f823bc2a230cfa45058b40f9013bc4", + "url": "https://api.github.com/repos/php-mock/php-mock-integration/zipball/003d585841e435958a02e9b986953907b8b7609b", + "reference": "003d585841e435958a02e9b986953907b8b7609b", "shasum": "" }, "require": { "php": ">=5.6", - "php-mock/php-mock": "^2", - "phpunit/php-text-template": "^1" + "php-mock/php-mock": "^2.2", + "phpunit/php-text-template": "^1 || ^2" }, "require-dev": { - "phpunit/phpunit": "^4|^5" + "phpunit/phpunit": "^5.7.27 || ^6 || ^7 || ^8 || ^9" }, "type": "library", "autoload": { @@ -5778,26 +7668,26 @@ "test", "test double" ], - "time": "2017-02-17T21:31:34+00:00" + "time": "2020-02-08T14:40:25+00:00" }, { "name": "php-mock/php-mock-phpunit", - "version": "2.1.2", + "version": "2.6.0", "source": { "type": "git", "url": "https://github.com/php-mock/php-mock-phpunit.git", - "reference": "57b92e621f14c2c07a4567cd29ed4e87de0d2912" + "reference": "2877a0e58f12e91b64bf36ccd080a209dcbf6c30" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-mock/php-mock-phpunit/zipball/57b92e621f14c2c07a4567cd29ed4e87de0d2912", - "reference": "57b92e621f14c2c07a4567cd29ed4e87de0d2912", + "url": "https://api.github.com/repos/php-mock/php-mock-phpunit/zipball/2877a0e58f12e91b64bf36ccd080a209dcbf6c30", + "reference": "2877a0e58f12e91b64bf36ccd080a209dcbf6c30", "shasum": "" }, "require": { "php": ">=7", - "php-mock/php-mock-integration": "^2", - "phpunit/phpunit": "^6 || ^7" + "php-mock/php-mock-integration": "^2.1", + "phpunit/phpunit": "^6 || ^7 || ^8 || ^9" }, "type": "library", "autoload": { @@ -5832,39 +7722,99 @@ "test", "test double" ], - "time": "2018-10-07T14:38:37+00:00" + "time": "2020-02-08T15:44:47+00:00" }, { - "name": "phpdocumentor/reflection-common", - "version": "1.0.1", + "name": "php-webdriver/webdriver", + "version": "1.8.2", "source": { "type": "git", - "url": "https://github.com/phpDocumentor/ReflectionCommon.git", - "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6" + "url": "https://github.com/php-webdriver/php-webdriver.git", + "reference": "3308a70be084d6d7fd1ee5787b4c2e6eb4b70aab" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", - "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", + "url": "https://api.github.com/repos/php-webdriver/php-webdriver/zipball/3308a70be084d6d7fd1ee5787b4c2e6eb4b70aab", + "reference": "3308a70be084d6d7fd1ee5787b4c2e6eb4b70aab", "shasum": "" }, "require": { - "php": ">=5.5" + "ext-curl": "*", + "ext-json": "*", + "ext-zip": "*", + "php": "^5.6 || ~7.0", + "symfony/polyfill-mbstring": "^1.12", + "symfony/process": "^2.8 || ^3.1 || ^4.0 || ^5.0" }, "require-dev": { - "phpunit/phpunit": "^4.6" + "friendsofphp/php-cs-fixer": "^2.0", + "jakub-onderka/php-parallel-lint": "^1.0", + "php-coveralls/php-coveralls": "^2.0", + "php-mock/php-mock-phpunit": "^1.1", + "phpunit/phpunit": "^5.7", + "sebastian/environment": "^1.3.4 || ^2.0 || ^3.0", + "sminnee/phpunit-mock-objects": "^3.4", + "squizlabs/php_codesniffer": "^3.5", + "symfony/var-dumper": "^3.3 || ^4.0 || ^5.0" + }, + "suggest": { + "ext-SimpleXML": "For Firefox profile creation" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "1.8.x-dev" } }, "autoload": { "psr-4": { - "phpDocumentor\\Reflection\\": [ - "src" - ] + "Facebook\\WebDriver\\": "lib/" + }, + "files": [ + "lib/Exception/TimeoutException.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A PHP client for Selenium WebDriver. Previously facebook/webdriver.", + "homepage": "https://github.com/php-webdriver/php-webdriver", + "keywords": [ + "Chromedriver", + "geckodriver", + "php", + "selenium", + "webdriver" + ], + "time": "2020-03-04T14:40:12+00:00" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "2.1.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "6568f4687e5b41b054365f9ae03fcb1ed5f2069b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/6568f4687e5b41b054365f9ae03fcb1ed5f2069b", + "reference": "6568f4687e5b41b054365f9ae03fcb1ed5f2069b", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -5886,44 +7836,42 @@ "reflection", "static analysis" ], - "time": "2017-09-11T18:02:19+00:00" + "time": "2020-04-27T09:25:28+00:00" }, { "name": "phpdocumentor/reflection-docblock", - "version": "4.3.0", + "version": "5.1.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "94fd0001232e47129dd3504189fa1c7225010d08" + "reference": "cd72d394ca794d3466a3b2fc09d5a6c1dc86b47e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/94fd0001232e47129dd3504189fa1c7225010d08", - "reference": "94fd0001232e47129dd3504189fa1c7225010d08", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/cd72d394ca794d3466a3b2fc09d5a6c1dc86b47e", + "reference": "cd72d394ca794d3466a3b2fc09d5a6c1dc86b47e", "shasum": "" }, "require": { - "php": "^7.0", - "phpdocumentor/reflection-common": "^1.0.0", - "phpdocumentor/type-resolver": "^0.4.0", - "webmozart/assert": "^1.0" + "ext-filter": "^7.1", + "php": "^7.2", + "phpdocumentor/reflection-common": "^2.0", + "phpdocumentor/type-resolver": "^1.0", + "webmozart/assert": "^1" }, "require-dev": { - "doctrine/instantiator": "~1.0.5", - "mockery/mockery": "^1.0", - "phpunit/phpunit": "^6.4" + "doctrine/instantiator": "^1", + "mockery/mockery": "^1" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.x-dev" + "dev-master": "5.x-dev" } }, "autoload": { "psr-4": { - "phpDocumentor\\Reflection\\": [ - "src/" - ] + "phpDocumentor\\Reflection\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -5934,44 +7882,46 @@ { "name": "Mike van Riel", "email": "me@mikevanriel.com" + }, + { + "name": "Jaap van Otterdijk", + "email": "account@ijaap.nl" } ], "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", - "time": "2017-11-30T07:14:17+00:00" + "time": "2020-02-22T12:28:44+00:00" }, { "name": "phpdocumentor/type-resolver", - "version": "0.4.0", + "version": "1.2.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7" + "reference": "30441f2752e493c639526b215ed81d54f369d693" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/9c977708995954784726e25d0cd1dddf4e65b0f7", - "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/30441f2752e493c639526b215ed81d54f369d693", + "reference": "30441f2752e493c639526b215ed81d54f369d693", "shasum": "" }, "require": { - "php": "^5.5 || ^7.0", - "phpdocumentor/reflection-common": "^1.0" + "php": "^7.2", + "phpdocumentor/reflection-common": "^2.0" }, "require-dev": { - "mockery/mockery": "^0.9.4", - "phpunit/phpunit": "^5.2||^4.8.24" + "ext-tokenizer": "^7.2", + "mockery/mockery": "~1" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-1.x": "1.x-dev" } }, "autoload": { "psr-4": { - "phpDocumentor\\Reflection\\": [ - "src/" - ] + "phpDocumentor\\Reflection\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -5984,42 +7934,43 @@ "email": "me@mikevanriel.com" } ], - "time": "2017-07-14T14:27:02+00:00" + "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", + "time": "2020-06-19T20:22:09+00:00" }, { "name": "phpspec/prophecy", - "version": "1.8.0", + "version": "v1.10.3", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "4ba436b55987b4bf311cb7c6ba82aa528aac0a06" + "reference": "451c3cd1418cf640de218914901e51b064abb093" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/4ba436b55987b4bf311cb7c6ba82aa528aac0a06", - "reference": "4ba436b55987b4bf311cb7c6ba82aa528aac0a06", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/451c3cd1418cf640de218914901e51b064abb093", + "reference": "451c3cd1418cf640de218914901e51b064abb093", "shasum": "" }, "require": { "doctrine/instantiator": "^1.0.2", "php": "^5.3|^7.0", - "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0", - "sebastian/comparator": "^1.1|^2.0|^3.0", - "sebastian/recursion-context": "^1.0|^2.0|^3.0" + "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0|^5.0", + "sebastian/comparator": "^1.2.3|^2.0|^3.0|^4.0", + "sebastian/recursion-context": "^1.0|^2.0|^3.0|^4.0" }, "require-dev": { - "phpspec/phpspec": "^2.5|^3.2", + "phpspec/phpspec": "^2.5 || ^3.2", "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.8.x-dev" + "dev-master": "1.10.x-dev" } }, "autoload": { - "psr-0": { - "Prophecy\\": "src/" + "psr-4": { + "Prophecy\\": "src/Prophecy" } }, "notification-url": "https://packagist.org/downloads/", @@ -6047,44 +7998,44 @@ "spy", "stub" ], - "time": "2018-08-05T17:53:17+00:00" + "time": "2020-03-05T15:02:03+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "6.1.4", + "version": "7.0.10", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "807e6013b00af69b6c5d9ceb4282d0393dbb9d8d" + "reference": "f1884187926fbb755a9aaf0b3836ad3165b478bf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/807e6013b00af69b6c5d9ceb4282d0393dbb9d8d", - "reference": "807e6013b00af69b6c5d9ceb4282d0393dbb9d8d", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/f1884187926fbb755a9aaf0b3836ad3165b478bf", + "reference": "f1884187926fbb755a9aaf0b3836ad3165b478bf", "shasum": "" }, "require": { "ext-dom": "*", "ext-xmlwriter": "*", - "php": "^7.1", - "phpunit/php-file-iterator": "^2.0", + "php": "^7.2", + "phpunit/php-file-iterator": "^2.0.2", "phpunit/php-text-template": "^1.2.1", - "phpunit/php-token-stream": "^3.0", + "phpunit/php-token-stream": "^3.1.1", "sebastian/code-unit-reverse-lookup": "^1.0.1", - "sebastian/environment": "^3.1 || ^4.0", + "sebastian/environment": "^4.2.2", "sebastian/version": "^2.0.1", - "theseer/tokenizer": "^1.1" + "theseer/tokenizer": "^1.1.3" }, "require-dev": { - "phpunit/phpunit": "^7.0" + "phpunit/phpunit": "^8.2.2" }, "suggest": { - "ext-xdebug": "^2.6.0" + "ext-xdebug": "^2.7.2" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "6.1-dev" + "dev-master": "7.0-dev" } }, "autoload": { @@ -6099,8 +8050,8 @@ "authors": [ { "name": "Sebastian Bergmann", - "role": "lead", - "email": "sebastian@phpunit.de" + "email": "sebastian@phpunit.de", + "role": "lead" } ], "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", @@ -6110,7 +8061,7 @@ "testing", "xunit" ], - "time": "2018-10-31T16:06:48+00:00" + "time": "2019-11-20T13:55:58+00:00" }, { "name": "phpunit/php-file-iterator", @@ -6205,16 +8156,16 @@ }, { "name": "phpunit/php-timer", - "version": "2.0.0", + "version": "2.1.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "8b8454ea6958c3dee38453d3bd571e023108c91f" + "reference": "1038454804406b0b5f5f520358e78c1c2f71501e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/8b8454ea6958c3dee38453d3bd571e023108c91f", - "reference": "8b8454ea6958c3dee38453d3bd571e023108c91f", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/1038454804406b0b5f5f520358e78c1c2f71501e", + "reference": "1038454804406b0b5f5f520358e78c1c2f71501e", "shasum": "" }, "require": { @@ -6226,7 +8177,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-master": "2.1-dev" } }, "autoload": { @@ -6250,20 +8201,20 @@ "keywords": [ "timer" ], - "time": "2018-02-01T13:07:23+00:00" + "time": "2019-06-07T04:22:29+00:00" }, { "name": "phpunit/php-token-stream", - "version": "3.0.1", + "version": "3.1.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-token-stream.git", - "reference": "c99e3be9d3e85f60646f152f9002d46ed7770d18" + "reference": "995192df77f63a59e47f025390d2d1fdf8f425ff" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/c99e3be9d3e85f60646f152f9002d46ed7770d18", - "reference": "c99e3be9d3e85f60646f152f9002d46ed7770d18", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/995192df77f63a59e47f025390d2d1fdf8f425ff", + "reference": "995192df77f63a59e47f025390d2d1fdf8f425ff", "shasum": "" }, "require": { @@ -6276,7 +8227,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-master": "3.1-dev" } }, "autoload": { @@ -6299,57 +8250,56 @@ "keywords": [ "tokenizer" ], - "time": "2018-10-30T05:52:18+00:00" + "time": "2019-09-17T06:23:10+00:00" }, { "name": "phpunit/phpunit", - "version": "7.4.4", + "version": "8.5.8", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "b1be2c8530c4c29c3519a052c9fb6cee55053bbd" + "reference": "34c18baa6a44f1d1fbf0338907139e9dce95b997" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/b1be2c8530c4c29c3519a052c9fb6cee55053bbd", - "reference": "b1be2c8530c4c29c3519a052c9fb6cee55053bbd", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/34c18baa6a44f1d1fbf0338907139e9dce95b997", + "reference": "34c18baa6a44f1d1fbf0338907139e9dce95b997", "shasum": "" }, "require": { - "doctrine/instantiator": "^1.1", + "doctrine/instantiator": "^1.2.0", "ext-dom": "*", "ext-json": "*", "ext-libxml": "*", "ext-mbstring": "*", "ext-xml": "*", - "myclabs/deep-copy": "^1.7", - "phar-io/manifest": "^1.0.2", - "phar-io/version": "^2.0", - "php": "^7.1", - "phpspec/prophecy": "^1.7", - "phpunit/php-code-coverage": "^6.0.7", - "phpunit/php-file-iterator": "^2.0.1", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.9.1", + "phar-io/manifest": "^1.0.3", + "phar-io/version": "^2.0.1", + "php": "^7.2", + "phpspec/prophecy": "^1.8.1", + "phpunit/php-code-coverage": "^7.0.7", + "phpunit/php-file-iterator": "^2.0.2", "phpunit/php-text-template": "^1.2.1", - "phpunit/php-timer": "^2.0", - "sebastian/comparator": "^3.0", - "sebastian/diff": "^3.0", - "sebastian/environment": "^3.1 || ^4.0", - "sebastian/exporter": "^3.1", - "sebastian/global-state": "^2.0", + "phpunit/php-timer": "^2.1.2", + "sebastian/comparator": "^3.0.2", + "sebastian/diff": "^3.0.2", + "sebastian/environment": "^4.2.2", + "sebastian/exporter": "^3.1.1", + "sebastian/global-state": "^3.0.0", "sebastian/object-enumerator": "^3.0.3", - "sebastian/resource-operations": "^2.0", + "sebastian/resource-operations": "^2.0.1", + "sebastian/type": "^1.1.3", "sebastian/version": "^2.0.1" }, - "conflict": { - "phpunit/phpunit-mock-objects": "*" - }, "require-dev": { "ext-pdo": "*" }, "suggest": { "ext-soap": "*", "ext-xdebug": "*", - "phpunit/php-invoker": "^2.0" + "phpunit/php-invoker": "^2.0.0" }, "bin": [ "phpunit" @@ -6357,7 +8307,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "7.4-dev" + "dev-master": "8.5-dev" } }, "autoload": { @@ -6372,8 +8322,8 @@ "authors": [ { "name": "Sebastian Bergmann", - "role": "lead", - "email": "sebastian@phpunit.de" + "email": "sebastian@phpunit.de", + "role": "lead" } ], "description": "The PHP Unit Testing framework.", @@ -6383,7 +8333,17 @@ "testing", "xunit" ], - "time": "2018-11-14T16:52:02+00:00" + "funding": [ + { + "url": "https://phpunit.de/donate.html", + "type": "custom" + }, + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-06-22T07:06:58+00:00" }, { "name": "sebastian/code-unit-reverse-lookup", @@ -6496,23 +8456,23 @@ }, { "name": "sebastian/diff", - "version": "3.0.1", + "version": "3.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "366541b989927187c4ca70490a35615d3fef2dce" + "reference": "720fcc7e9b5cf384ea68d9d930d480907a0c1a29" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/366541b989927187c4ca70490a35615d3fef2dce", - "reference": "366541b989927187c4ca70490a35615d3fef2dce", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/720fcc7e9b5cf384ea68d9d930d480907a0c1a29", + "reference": "720fcc7e9b5cf384ea68d9d930d480907a0c1a29", "shasum": "" }, "require": { "php": "^7.1" }, "require-dev": { - "phpunit/phpunit": "^7.0", + "phpunit/phpunit": "^7.5 || ^8.0", "symfony/process": "^2 || ^3.3 || ^4" }, "type": "library", @@ -6548,32 +8508,35 @@ "unidiff", "unified diff" ], - "time": "2018-06-10T07:54:39+00:00" + "time": "2019-02-04T06:01:07+00:00" }, { "name": "sebastian/environment", - "version": "4.0.1", + "version": "4.2.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "febd209a219cea7b56ad799b30ebbea34b71eb8f" + "reference": "464c90d7bdf5ad4e8a6aea15c091fec0603d4368" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/febd209a219cea7b56ad799b30ebbea34b71eb8f", - "reference": "febd209a219cea7b56ad799b30ebbea34b71eb8f", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/464c90d7bdf5ad4e8a6aea15c091fec0603d4368", + "reference": "464c90d7bdf5ad4e8a6aea15c091fec0603d4368", "shasum": "" }, "require": { "php": "^7.1" }, "require-dev": { - "phpunit/phpunit": "^7.4" + "phpunit/phpunit": "^7.5" + }, + "suggest": { + "ext-posix": "*" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-master": "4.2-dev" } }, "autoload": { @@ -6598,20 +8561,20 @@ "environment", "hhvm" ], - "time": "2018-11-25T09:31:21+00:00" + "time": "2019-11-20T08:46:58+00:00" }, { "name": "sebastian/exporter", - "version": "3.1.0", + "version": "3.1.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "234199f4528de6d12aaa58b612e98f7d36adb937" + "reference": "68609e1261d215ea5b21b7987539cbfbe156ec3e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/234199f4528de6d12aaa58b612e98f7d36adb937", - "reference": "234199f4528de6d12aaa58b612e98f7d36adb937", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/68609e1261d215ea5b21b7987539cbfbe156ec3e", + "reference": "68609e1261d215ea5b21b7987539cbfbe156ec3e", "shasum": "" }, "require": { @@ -6638,6 +8601,10 @@ "BSD-3-Clause" ], "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, { "name": "Jeff Welch", "email": "whatthejeff@gmail.com" @@ -6646,17 +8613,13 @@ "name": "Volker Dusch", "email": "github@wallbash.com" }, - { - "name": "Bernhard Schussek", - "email": "bschussek@2bepublished.at" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, { "name": "Adam Harvey", "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" } ], "description": "Provides the functionality to export PHP variables for visualization", @@ -6665,27 +8628,30 @@ "export", "exporter" ], - "time": "2017-04-03T13:19:02+00:00" + "time": "2019-09-14T09:02:43+00:00" }, { "name": "sebastian/global-state", - "version": "2.0.0", + "version": "3.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4" + "reference": "edf8a461cf1d4005f19fb0b6b8b95a9f7fa0adc4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", - "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/edf8a461cf1d4005f19fb0b6b8b95a9f7fa0adc4", + "reference": "edf8a461cf1d4005f19fb0b6b8b95a9f7fa0adc4", "shasum": "" }, "require": { - "php": "^7.0" + "php": "^7.2", + "sebastian/object-reflector": "^1.1.1", + "sebastian/recursion-context": "^3.0" }, "require-dev": { - "phpunit/phpunit": "^6.0" + "ext-dom": "*", + "phpunit/phpunit": "^8.0" }, "suggest": { "ext-uopz": "*" @@ -6693,7 +8659,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-master": "3.0-dev" } }, "autoload": { @@ -6716,7 +8682,7 @@ "keywords": [ "global state" ], - "time": "2017-04-27T15:39:26+00:00" + "time": "2019-02-01T05:30:01+00:00" }, { "name": "sebastian/object-enumerator", @@ -6905,6 +8871,52 @@ "homepage": "https://www.github.com/sebastianbergmann/resource-operations", "time": "2018-10-04T04:07:39+00:00" }, + { + "name": "sebastian/type", + "version": "1.1.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "3aaaa15fa71d27650d62a948be022fe3b48541a3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/3aaaa15fa71d27650d62a948be022fe3b48541a3", + "reference": "3aaaa15fa71d27650d62a948be022fe3b48541a3", + "shasum": "" + }, + "require": { + "php": "^7.2" + }, + "require-dev": { + "phpunit/phpunit": "^8.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "time": "2019-07-02T08:10:15+00:00" + }, { "name": "sebastian/version", "version": "2.0.1", @@ -6950,20 +8962,20 @@ }, { "name": "seld/jsonlint", - "version": "1.7.1", + "version": "1.8.0", "source": { "type": "git", "url": "https://github.com/Seldaek/jsonlint.git", - "reference": "d15f59a67ff805a44c50ea0516d2341740f81a38" + "reference": "ff2aa5420bfbc296cf6a0bc785fa5b35736de7c1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/d15f59a67ff805a44c50ea0516d2341740f81a38", - "reference": "d15f59a67ff805a44c50ea0516d2341740f81a38", + "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/ff2aa5420bfbc296cf6a0bc785fa5b35736de7c1", + "reference": "ff2aa5420bfbc296cf6a0bc785fa5b35736de7c1", "shasum": "" }, "require": { - "php": "^5.3 || ^7.0" + "php": "^5.3 || ^7.0 || ^8.0" }, "require-dev": { "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" @@ -6995,20 +9007,30 @@ "parser", "validator" ], - "time": "2018-01-24T12:46:19+00:00" + "funding": [ + { + "url": "https://github.com/Seldaek", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/seld/jsonlint", + "type": "tidelift" + } + ], + "time": "2020-04-30T19:05:18+00:00" }, { "name": "seld/phar-utils", - "version": "1.0.1", + "version": "1.1.0", "source": { "type": "git", "url": "https://github.com/Seldaek/phar-utils.git", - "reference": "7009b5139491975ef6486545a39f3e6dad5ac30a" + "reference": "8800503d56b9867d43d9c303b9cbcc26016e82f0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/phar-utils/zipball/7009b5139491975ef6486545a39f3e6dad5ac30a", - "reference": "7009b5139491975ef6486545a39f3e6dad5ac30a", + "url": "https://api.github.com/repos/Seldaek/phar-utils/zipball/8800503d56b9867d43d9c303b9cbcc26016e82f0", + "reference": "8800503d56b9867d43d9c303b9cbcc26016e82f0", "shasum": "" }, "require": { @@ -7037,32 +9059,103 @@ ], "description": "PHAR file format utilities, for when PHP phars you up", "keywords": [ - "phra" + "phar" ], - "time": "2015-10-13T18:44:15+00:00" + "time": "2020-02-14T15:25:33+00:00" }, { - "name": "symfony/filesystem", - "version": "v4.1.7", + "name": "symfony/debug", + "version": "v4.4.10", "source": { "type": "git", - "url": "https://github.com/symfony/filesystem.git", - "reference": "fd7bd6535beb1f0a0a9e3ee960666d0598546981" + "url": "https://github.com/symfony/debug.git", + "reference": "28f92d08bb6d1fddf8158e02c194ad43870007e6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/fd7bd6535beb1f0a0a9e3ee960666d0598546981", - "reference": "fd7bd6535beb1f0a0a9e3ee960666d0598546981", + "url": "https://api.github.com/repos/symfony/debug/zipball/28f92d08bb6d1fddf8158e02c194ad43870007e6", + "reference": "28f92d08bb6d1fddf8158e02c194ad43870007e6", "shasum": "" }, "require": { - "php": "^7.1.3", + "php": ">=7.1.3", + "psr/log": "~1.0", + "symfony/polyfill-php80": "^1.15" + }, + "conflict": { + "symfony/http-kernel": "<3.4" + }, + "require-dev": { + "symfony/http-kernel": "^3.4|^4.0|^5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Debug\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Debug Component", + "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-05-24T08:33:35+00:00" + }, + { + "name": "symfony/filesystem", + "version": "v5.1.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "6e4320f06d5f2cce0d96530162491f4465179157" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/6e4320f06d5f2cce0d96530162491f4465179157", + "reference": "6e4320f06d5f2cce0d96530162491f4465179157", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", "symfony/polyfill-ctype": "~1.8" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "5.1-dev" } }, "autoload": { @@ -7089,29 +9182,45 @@ ], "description": "Symfony Filesystem Component", "homepage": "https://symfony.com", - "time": "2018-10-30T13:18:25+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-05-30T20:35:19+00:00" }, { "name": "symfony/options-resolver", - "version": "v4.1.7", + "version": "v5.1.2", "source": { "type": "git", "url": "https://github.com/symfony/options-resolver.git", - "reference": "40f0e40d37c1c8a762334618dea597d64bbb75ff" + "reference": "663f5dd5e14057d1954fe721f9709d35837f2447" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/40f0e40d37c1c8a762334618dea597d64bbb75ff", - "reference": "40f0e40d37c1c8a762334618dea597d64bbb75ff", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/663f5dd5e14057d1954fe721f9709d35837f2447", + "reference": "663f5dd5e14057d1954fe721f9709d35837f2447", "shasum": "" }, "require": { - "php": "^7.1.3" + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1", + "symfony/polyfill-php80": "^1.15" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "5.1-dev" } }, "autoload": { @@ -7143,20 +9252,34 @@ "configuration", "options" ], - "time": "2018-09-18T12:45:12+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-05-23T13:08:13+00:00" }, { "name": "symfony/polyfill-php70", - "version": "v1.10.0", + "version": "v1.17.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php70.git", - "reference": "6b88000cdd431cd2e940caa2cb569201f3f84224" + "reference": "471b096aede7025bace8eb356b9ac801aaba7e2d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/6b88000cdd431cd2e940caa2cb569201f3f84224", - "reference": "6b88000cdd431cd2e940caa2cb569201f3f84224", + "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/471b096aede7025bace8eb356b9ac801aaba7e2d", + "reference": "471b096aede7025bace8eb356b9ac801aaba7e2d", "shasum": "" }, "require": { @@ -7166,7 +9289,11 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.9-dev" + "dev-master": "1.17-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" } }, "autoload": { @@ -7202,29 +9329,44 @@ "portable", "shim" ], - "time": "2018-09-21T06:26:08+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-06-06T08:46:27+00:00" }, { "name": "symfony/stopwatch", - "version": "v4.1.7", + "version": "v5.1.2", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", - "reference": "5bfc064125b73ff81229e19381ce1c34d3416f4b" + "reference": "0f7c58cf81dbb5dd67d423a89d577524a2ec0323" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/5bfc064125b73ff81229e19381ce1c34d3416f4b", - "reference": "5bfc064125b73ff81229e19381ce1c34d3416f4b", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/0f7c58cf81dbb5dd67d423a89d577524a2ec0323", + "reference": "0f7c58cf81dbb5dd67d423a89d577524a2ec0323", "shasum": "" }, "require": { - "php": "^7.1.3" + "php": ">=7.2.5", + "symfony/service-contracts": "^1.0|^2" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "5.1-dev" } }, "autoload": { @@ -7251,79 +9393,34 @@ ], "description": "Symfony Stopwatch Component", "homepage": "https://symfony.com", - "time": "2018-10-02T12:40:59+00:00" - }, - { - "name": "symfony/yaml", - "version": "v4.1.7", - "source": { - "type": "git", - "url": "https://github.com/symfony/yaml.git", - "reference": "367e689b2fdc19965be435337b50bc8adf2746c9" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/367e689b2fdc19965be435337b50bc8adf2746c9", - "reference": "367e689b2fdc19965be435337b50bc8adf2746c9", - "shasum": "" - }, - "require": { - "php": "^7.1.3", - "symfony/polyfill-ctype": "~1.8" - }, - "conflict": { - "symfony/console": "<3.4" - }, - "require-dev": { - "symfony/console": "~3.4|~4.0" - }, - "suggest": { - "symfony/console": "For validating YAML files using the lint command" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.1-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Component\\Yaml\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ + "funding": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "url": "https://symfony.com/sponsor", + "type": "custom" }, { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" } ], - "description": "Symfony Yaml Component", - "homepage": "https://symfony.com", - "time": "2018-10-02T16:36:10+00:00" + "time": "2020-05-20T17:43:50+00:00" }, { "name": "theseer/tokenizer", - "version": "1.1.0", + "version": "1.1.3", "source": { "type": "git", "url": "https://github.com/theseer/tokenizer.git", - "reference": "cb2f008f3f05af2893a87208fe6a6c4985483f8b" + "reference": "11336f6f84e16a720dae9d8e6ed5019efa85a0f9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/cb2f008f3f05af2893a87208fe6a6c4985483f8b", - "reference": "cb2f008f3f05af2893a87208fe6a6c4985483f8b", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/11336f6f84e16a720dae9d8e6ed5019efa85a0f9", + "reference": "11336f6f84e16a720dae9d8e6ed5019efa85a0f9", "shasum": "" }, "require": { @@ -7345,12 +9442,12 @@ "authors": [ { "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" + "role": "Developer", + "email": "arne@blankerts.de" } ], "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", - "time": "2017-04-07T12:08:54+00:00" + "time": "2019-06-13T22:48:21+00:00" } ], "aliases": [], @@ -7359,10 +9456,11 @@ "prefer-stable": true, "prefer-lowest": false, "platform": { - "php": ">=7.2", + "php": "^7.2", "ext-mbstring": "*", "ext-pdo_mysql": "*", "ext-zip": "*" }, - "platform-dev": [] + "platform-dev": [], + "plugin-api-version": "1.1.0" } diff --git a/config/app.php b/config/app.php index 34360e10..ccf5e458 100644 --- a/config/app.php +++ b/config/app.php @@ -9,7 +9,7 @@ return [ | change this value if you are not maintaining your own internal versions. */ - 'version' => '0.7.17', + 'version' => '1.0.0', /* |-------------------------------------------------------------------------- diff --git a/config/backups.php b/config/backups.php new file mode 100644 index 00000000..fde85621 --- /dev/null +++ b/config/backups.php @@ -0,0 +1,39 @@ + env('APP_BACKUP_DRIVER', Backup::ADAPTER_WINGS), + + 'disks' => [ + // There is no configuration for the local disk for Wings. That configuration + // is determined by the Daemon configuration, and not the Panel. + 'wings' => [ + 'adapter' => Backup::ADAPTER_WINGS, + ], + + // Configuration for storing backups in Amazon S3. This uses the same credentials + // specified in filesystems.php but does include some more specific settings for + // backups, notably bucket, location, and use_accelerate_endpoint. + 's3' => [ + 'adapter' => Backup::ADAPTER_AWS_S3, + + 'region' => env('AWS_DEFAULT_REGION'), + 'key' => env('AWS_ACCESS_KEY_ID'), + 'secret' => env('AWS_SECRET_ACCESS_KEY'), + + // The S3 bucket to use for backups. + 'bucket' => env('AWS_BACKUPS_BUCKET'), + + // The location within the S3 bucket where backups will be stored. Backups + // are stored within a folder using the server's UUID as the name. Each + // backup for that server lives within that folder. + 'prefix' => env('AWS_BACKUPS_BUCKET') ?? '', + + 'use_accelerate_endpoint' => env('AWS_BACKUPS_USE_ACCELERATE', false), + ], + ], +]; diff --git a/config/database.php b/config/database.php index 63b0b9cb..32fca758 100644 --- a/config/database.php +++ b/config/database.php @@ -96,6 +96,8 @@ return [ 'client' => 'predis', 'default' => [ + 'scheme' => env('REDIS_SCHEME', 'tcp'), + 'path' => env('REDIS_PATH', '/run/redis/redis.sock'), 'host' => env('REDIS_HOST', 'localhost'), 'password' => env('REDIS_PASSWORD', null), 'port' => env('REDIS_PORT', 6379), @@ -103,6 +105,8 @@ return [ ], 'sessions' => [ + 'scheme' => env('REDIS_SCHEME', 'tcp'), + 'path' => env('REDIS_PATH', '/run/redis/redis.sock'), 'host' => env('REDIS_HOST', 'localhost'), 'password' => env('REDIS_PASSWORD', null), 'port' => env('REDIS_PORT', 6379), diff --git a/config/http.php b/config/http.php new file mode 100644 index 00000000..b3984523 --- /dev/null +++ b/config/http.php @@ -0,0 +1,21 @@ + [ + 'client_period' => 1, + 'client' => env('APP_API_CLIENT_RATELIMIT', 240), + + 'application_period' => 1, + 'application' => env('APP_API_APPLICATION_RATELIMIT', 240), + ], +]; diff --git a/config/ide-helper.php b/config/ide-helper.php index 9f10873f..5922f533 100644 --- a/config/ide-helper.php +++ b/config/ide-helper.php @@ -168,8 +168,8 @@ return [ | Cast the given "real type" to the given "type". | */ - 'type_overrides' => [ + 'type_overrides' => [ 'integer' => 'int', 'boolean' => 'bool', - ], + ], ]; diff --git a/config/laroute.php b/config/laroute.php deleted file mode 100644 index b2b4a2f3..00000000 --- a/config/laroute.php +++ /dev/null @@ -1,56 +0,0 @@ - 'public/js', - - /* - * The destination filename for the javascript file. - */ - 'filename' => 'laroute', - - /* - * The namespace for the helper functions. By default this will bind them to - * `window.laroute`. - */ - 'namespace' => 'Router', - - /* - * Generate absolute URLs - * - * Set the Application URL in config/app.php - */ - 'absolute' => false, - - /* - * The Filter Method - * - * 'all' => All routes except "'laroute' => false" - * 'only' => Only "'laroute' => true" routes - * 'force' => All routes, ignored "laroute" route parameter - */ - 'filter' => 'all', - - /* - * Controller Namespace - * - * Set here your controller namespace (see RouteServiceProvider -> $namespace) for cleaner action calls - * e.g. 'App\Http\Controllers' - */ - 'action_namespace' => '', - - /* - * The path to the template `laroute.js` file. This is the file that contains - * the ported helper Laravel url/route functions and the route data to go - * with them. - */ - 'template' => 'vendor/lord/laroute/src/templates/laroute.js', - - /* - * Appends a prefix to URLs. By default the prefix is an empty string. - * - */ - 'prefix' => '', -]; diff --git a/config/logging.php b/config/logging.php index 33b6a5d0..bb4470c0 100644 --- a/config/logging.php +++ b/config/logging.php @@ -1,9 +1,9 @@ 'errorlog', 'level' => 'debug', ], - ], + 'null' => [ + 'driver' => 'monolog', + 'handler' => NullHandler::class, + ], + ], ]; diff --git a/config/pterodactyl.php b/config/pterodactyl.php index 4379c753..aba0d729 100644 --- a/config/pterodactyl.php +++ b/config/pterodactyl.php @@ -10,7 +10,7 @@ return [ | setup on the panel. When set to true, configurations stored in the | database will not be applied. */ - 'load_environment_only' => (bool) env('APP_ENVIRONMENT_ONLY', false), + 'load_environment_only' => (bool)env('APP_ENVIRONMENT_ONLY', false), /* |-------------------------------------------------------------------------- @@ -56,7 +56,6 @@ return [ 'admin' => [ 'servers' => env('APP_PAGINATE_ADMIN_SERVERS', 25), 'users' => env('APP_PAGINATE_ADMIN_USERS', 25), - 'packs' => env('APP_PAGINATE_ADMIN_PACKS', 50), ], 'api' => [ 'nodes' => env('APP_PAGINATE_API_NODES', 25), @@ -85,8 +84,8 @@ return [ | Configure the timeout to be used for Guzzle connections here. */ 'guzzle' => [ - 'timeout' => env('GUZZLE_TIMEOUT', 5), - 'connect_timeout' => env('GUZZLE_CONNECT_TIMEOUT', 3), + 'timeout' => env('GUZZLE_TIMEOUT', 30), + 'connect_timeout' => env('GUZZLE_CONNECT_TIMEOUT', 10), ], /* @@ -102,29 +101,6 @@ return [ 'high' => env('QUEUE_HIGH', 'high'), ], - /* - |-------------------------------------------------------------------------- - | Console Configuration - |-------------------------------------------------------------------------- - | - | Configure the speed at which data is rendered to the console. - */ - 'console' => [ - 'count' => env('CONSOLE_PUSH_COUNT', 10), - 'frequency' => env('CONSOLE_PUSH_FREQ', 200), - ], - - /* - |-------------------------------------------------------------------------- - | Daemon Connection Details - |-------------------------------------------------------------------------- - | - | Configuration for support of the new Golang based daemon. - */ - 'daemon' => [ - 'use_new_daemon' => (bool) env('APP_USE_NEW_DAEMON', false), - ], - /* |-------------------------------------------------------------------------- | Task Timers @@ -162,6 +138,11 @@ return [ 'enabled' => env('PTERODACTYL_CLIENT_DATABASES_ENABLED', true), 'allow_random' => env('PTERODACTYL_CLIENT_DATABASES_ALLOW_RANDOM', true), ], + + 'schedules' => [ + // The total number of tasks that can exist for any given schedule at once. + 'per_schedule_task_limit' => 10, + ], ], /* @@ -172,21 +153,7 @@ return [ | This array includes the MIME filetypes that can be edited via the web. */ 'files' => [ - 'max_edit_size' => env('PTERODACTYL_FILES_MAX_EDIT_SIZE', 50000), - 'editable' => [ - 'application/json', - 'application/javascript', - 'application/xml', - 'application/xhtml+xml', - 'inode/x-empty', - 'text/xml', - 'text/css', - 'text/html', - 'text/plain', - 'text/x-perl', - 'text/x-shellscript', - 'text/x-python', - ], + 'max_edit_size' => env('PTERODACTYL_FILES_MAX_EDIT_SIZE', 1024 * 1024 * 4), ], /* @@ -218,5 +185,18 @@ return [ | | 'P_SERVER_CREATED_AT' => 'created_at' */ - 'environment_variables' => [], + 'environment_variables' => [ + 'P_SERVER_ALLOCATION_LIMIT' => 'allocation_limit', + ], + + /* + |-------------------------------------------------------------------------- + | Asset Verification + |-------------------------------------------------------------------------- + | + | This section controls the output format for JS & CSS assets. + */ + 'assets' => [ + 'use_hash' => env('PTERODACTYL_USE_ASSET_HASH', false), + ], ]; diff --git a/config/session.php b/config/session.php index 4f3a2e6d..2007acb2 100644 --- a/config/session.php +++ b/config/session.php @@ -28,7 +28,7 @@ return [ | */ - 'lifetime' => env('SESSION_LIFETIME', 10080), + 'lifetime' => env('SESSION_LIFETIME', 720), 'expire_on_close' => false, diff --git a/config/themes.php b/config/themes.php deleted file mode 100644 index 55942e5c..00000000 --- a/config/themes.php +++ /dev/null @@ -1,63 +0,0 @@ - true, - - /* - |-------------------------------------------------------------------------- - | Root path where theme Views will be located. - | Can be outside default views path e.g.: resources/themes - | Leave it null if you will put your themes in the default views folder - | (as defined in config\views.php) - |-------------------------------------------------------------------------- - */ - 'themes_path' => realpath(base_path('resources/themes')), - - /* - |-------------------------------------------------------------------------- - | Set behavior if an asset is not found in a Theme hierarchy. - | Available options: THROW_EXCEPTION | LOG_ERROR | IGNORE - |-------------------------------------------------------------------------- - */ - 'asset_not_found' => 'LOG_ERROR', - - /* - |-------------------------------------------------------------------------- - | Do we want a theme activated by default? Can be set at runtime with: - | Theme::set('theme-name'); - |-------------------------------------------------------------------------- - */ - 'default' => env('APP_THEME', 'pterodactyl'), - - /* - |-------------------------------------------------------------------------- - | Cache theme.json configuration files that are located in each theme's folder - | in order to avoid searching theme settings in the filesystem for each request - |-------------------------------------------------------------------------- - */ - 'cache' => true, - - /* - |-------------------------------------------------------------------------- - | Define available themes. Format: - | - | 'theme-name' => [ - | 'extends' => 'theme-to-extend', // optional - | 'views-path' => 'path-to-views', // defaults to: resources/views/theme-name - | 'asset-path' => 'path-to-assets', // defaults to: public/theme-name - | - | // You can add your own custom keys - | // Use Theme::getSetting('key') & Theme::setSetting('key', 'value') to access them - | 'key' => 'value', - | ], - | - |-------------------------------------------------------------------------- - */ - 'themes' => [ - 'pterodactyl' => [ - 'extends' => null, - 'views-path' => 'pterodactyl', - 'asset-path' => 'themes/pterodactyl', - ], - ], -]; diff --git a/config/vue-i18n-generator.php b/config/vue-i18n-generator.php new file mode 100644 index 00000000..92a20fd7 --- /dev/null +++ b/config/vue-i18n-generator.php @@ -0,0 +1,50 @@ + '/resources/lang', + + /* + |-------------------------------------------------------------------------- + | Laravel translation files + |-------------------------------------------------------------------------- + | + | You can choose which translation files to be generated. + | Note: leave this empty for all the translation files to be generated. + | + */ + + 'langFiles' => [], + + /* + |-------------------------------------------------------------------------- + | Output file + |-------------------------------------------------------------------------- + | + | The javascript path where I will place the generated file. + | Note: the path will be prepended to point to the App directory. + | + */ + 'jsPath' => '/resources/lang/i18n', + 'jsFile' => '/resources/lang/locales.js', + + /* + |-------------------------------------------------------------------------- + | i18n library + |-------------------------------------------------------------------------- + | + | Specify the library you use for localization. + | Options are vue-i18n or vuex-i18n. + | + */ + 'i18nLib' => 'vuex-i18n', +]; diff --git a/database/factories/ModelFactory.php b/database/factories/ModelFactory.php index 8866fa55..4997a9b6 100644 --- a/database/factories/ModelFactory.php +++ b/database/factories/ModelFactory.php @@ -1,9 +1,14 @@ define(Pterodactyl\Models\Server::class, function (Faker $faker) { return [ - 'id' => $faker->unique()->randomNumber(), - 'node_id' => $faker->randomNumber(), - 'uuid' => $faker->unique()->uuid, + 'uuid' => Uuid::uuid4()->toString(), 'uuidShort' => str_random(8), 'name' => $faker->firstName, 'description' => implode(' ', $faker->sentences()), @@ -31,15 +34,11 @@ $factory->define(Pterodactyl\Models\Server::class, function (Faker $faker) { 'io' => 500, 'cpu' => 0, 'oom_disabled' => 0, - 'allocation_id' => $faker->randomNumber(), - 'nest_id' => $faker->randomNumber(), - 'egg_id' => $faker->randomNumber(), - 'pack_id' => null, 'installed' => 1, 'database_limit' => null, 'allocation_limit' => null, - 'created_at' => \Carbon\Carbon::now(), - 'updated_at' => \Carbon\Carbon::now(), + 'created_at' => Carbon::now(), + 'updated_at' => Carbon::now(), ]; }); @@ -47,7 +46,6 @@ $factory->define(Pterodactyl\Models\User::class, function (Faker $faker) { static $password; return [ - 'id' => $faker->unique()->randomNumber(), 'external_id' => $faker->unique()->isbn10, 'uuid' => $faker->uuid, 'username' => $faker->userName, @@ -71,15 +69,14 @@ $factory->state(Pterodactyl\Models\User::class, 'admin', function () { $factory->define(Pterodactyl\Models\Location::class, function (Faker $faker) { return [ - 'id' => $faker->unique()->randomNumber(), - 'short' => $faker->unique()->domainWord, + 'short' => Str::random(8), 'long' => $faker->catchPhrase, ]; }); $factory->define(Pterodactyl\Models\Node::class, function (Faker $faker) { return [ - 'id' => $faker->unique()->randomNumber(), + 'uuid' => Uuid::uuid4()->toString(), 'public' => true, 'name' => $faker->firstName, 'fqdn' => $faker->ipv4, @@ -90,16 +87,16 @@ $factory->define(Pterodactyl\Models\Node::class, function (Faker $faker) { 'disk' => 10240, 'disk_overallocate' => 0, 'upload_size' => 100, - 'daemonSecret' => $faker->uuid, + 'daemon_token_id' => Str::random(Node::DAEMON_TOKEN_ID_LENGTH), + 'daemon_token' => encrypt(Str::random(Node::DAEMON_TOKEN_LENGTH)), 'daemonListen' => 8080, 'daemonSFTP' => 2022, - 'daemonBase' => '/srv/daemon', + 'daemonBase' => '/var/lib/pterodactyl/volumes', ]; }); $factory->define(Pterodactyl\Models\Nest::class, function (Faker $faker) { return [ - 'id' => $faker->unique()->randomNumber(), 'uuid' => $faker->unique()->uuid, 'author' => 'testauthor@example.com', 'name' => $faker->word, @@ -109,9 +106,7 @@ $factory->define(Pterodactyl\Models\Nest::class, function (Faker $faker) { $factory->define(Pterodactyl\Models\Egg::class, function (Faker $faker) { return [ - 'id' => $faker->unique()->randomNumber(), 'uuid' => $faker->unique()->uuid, - 'nest_id' => $faker->unique()->randomNumber(), 'name' => $faker->name, 'description' => implode(' ', $faker->sentences(3)), 'startup' => 'java -jar test.jar', @@ -120,7 +115,6 @@ $factory->define(Pterodactyl\Models\Egg::class, function (Faker $faker) { $factory->define(Pterodactyl\Models\EggVariable::class, function (Faker $faker) { return [ - 'id' => $faker->unique()->randomNumber(), 'name' => $faker->firstName, 'description' => $faker->sentence(), 'env_variable' => strtoupper(str_replace(' ', '_', $faker->words(2, true))), @@ -139,32 +133,12 @@ $factory->state(Pterodactyl\Models\EggVariable::class, 'editable', function () { return ['user_editable' => 1]; }); -$factory->define(Pterodactyl\Models\Pack::class, function (Faker $faker) { - return [ - 'id' => $faker->unique()->randomNumber(), - 'egg_id' => $faker->randomNumber(), - 'uuid' => $faker->uuid, - 'name' => $faker->word, - 'description' => null, - 'version' => $faker->randomNumber(), - 'selectable' => 1, - 'visible' => 1, - 'locked' => 0, - ]; -}); - $factory->define(Pterodactyl\Models\Subuser::class, function (Faker $faker) { - return [ - 'id' => $faker->unique()->randomNumber(), - 'user_id' => $faker->randomNumber(), - 'server_id' => $faker->randomNumber(), - ]; + return []; }); $factory->define(Pterodactyl\Models\Allocation::class, function (Faker $faker) { return [ - 'id' => $faker->unique()->randomNumber(), - 'node_id' => $faker->randomNumber(), 'ip' => $faker->ipv4, 'port' => $faker->randomNumber(5), ]; @@ -172,13 +146,11 @@ $factory->define(Pterodactyl\Models\Allocation::class, function (Faker $faker) { $factory->define(Pterodactyl\Models\DatabaseHost::class, function (Faker $faker) { return [ - 'id' => $faker->unique()->randomNumber(), 'name' => $faker->colorName, 'host' => $faker->unique()->ipv4, 'port' => 3306, 'username' => $faker->colorName, 'password' => Crypt::encrypt($faker->word), - 'node_id' => $faker->randomNumber(), ]; }); @@ -186,30 +158,23 @@ $factory->define(Pterodactyl\Models\Database::class, function (Faker $faker) { static $password; return [ - 'id' => $faker->unique()->randomNumber(), - 'server_id' => $faker->randomNumber(), - 'database_host_id' => $faker->randomNumber(), 'database' => str_random(10), 'username' => str_random(10), 'remote' => '%', 'password' => $password ?: bcrypt('test123'), - 'created_at' => \Carbon\Carbon::now()->toDateTimeString(), - 'updated_at' => \Carbon\Carbon::now()->toDateTimeString(), + 'created_at' => Carbon::now()->toDateTimeString(), + 'updated_at' => Carbon::now()->toDateTimeString(), ]; }); $factory->define(Pterodactyl\Models\Schedule::class, function (Faker $faker) { return [ - 'id' => $faker->unique()->randomNumber(), - 'server_id' => $faker->randomNumber(), 'name' => $faker->firstName(), ]; }); $factory->define(Pterodactyl\Models\Task::class, function (Faker $faker) { return [ - 'id' => $faker->unique()->randomNumber(), - 'schedule_id' => $faker->randomNumber(), 'sequence_id' => $faker->randomNumber(1), 'action' => 'command', 'payload' => 'test command', @@ -218,28 +183,16 @@ $factory->define(Pterodactyl\Models\Task::class, function (Faker $faker) { ]; }); -$factory->define(Pterodactyl\Models\DaemonKey::class, function (Faker $faker) { - return [ - 'id' => $faker->unique()->randomNumber(), - 'server_id' => $faker->randomNumber(), - 'user_id' => $faker->randomNumber(), - 'secret' => 'i_' . str_random(40), - 'expires_at' => \Carbon\Carbon::now()->addMinutes(10)->toDateTimeString(), - ]; -}); - $factory->define(Pterodactyl\Models\ApiKey::class, function (Faker $faker) { static $token; return [ - 'id' => $faker->unique()->randomNumber(), - 'user_id' => $faker->randomNumber(), 'key_type' => ApiKey::TYPE_APPLICATION, 'identifier' => str_random(Pterodactyl\Models\ApiKey::IDENTIFIER_LENGTH), 'token' => $token ?: $token = encrypt(str_random(Pterodactyl\Models\ApiKey::KEY_LENGTH)), 'allowed_ips' => null, 'memo' => 'Test Function Key', - 'created_at' => \Carbon\Carbon::now()->toDateTimeString(), - 'updated_at' => \Carbon\Carbon::now()->toDateTimeString(), + 'created_at' => Carbon::now()->toDateTimeString(), + 'updated_at' => Carbon::now()->toDateTimeString(), ]; }); diff --git a/database/migrations/2017_07_24_194433_DeleteTaskWhenParentServerIsDeleted.php b/database/migrations/2017_07_24_194433_DeleteTaskWhenParentServerIsDeleted.php index 042e7564..89e11022 100644 --- a/database/migrations/2017_07_24_194433_DeleteTaskWhenParentServerIsDeleted.php +++ b/database/migrations/2017_07_24_194433_DeleteTaskWhenParentServerIsDeleted.php @@ -23,6 +23,5 @@ class DeleteTaskWhenParentServerIsDeleted extends Migration */ public function down() { - // } } diff --git a/database/migrations/2017_10_02_202000_ChangeServicesToUseAMoreUniqueIdentifier.php b/database/migrations/2017_10_02_202000_ChangeServicesToUseAMoreUniqueIdentifier.php index 6bb36813..dffa7687 100644 --- a/database/migrations/2017_10_02_202000_ChangeServicesToUseAMoreUniqueIdentifier.php +++ b/database/migrations/2017_10_02_202000_ChangeServicesToUseAMoreUniqueIdentifier.php @@ -26,8 +26,8 @@ class ChangeServicesToUseAMoreUniqueIdentifier extends Migration DB::table('services')->get(['id', 'author', 'uuid'])->each(function ($service) { DB::table('services')->where('id', $service->id)->update([ - 'author' => ($service->author === 'ptrdctyl-v040-11e6-8b77-86f30ca893d3') ? 'support@pterodactyl.io' : 'unknown@unknown-author.com', - 'uuid' => Uuid::uuid4()->toString(), + 'author' => ($service->author === 'ptrdctyl-v040-11e6-8b77-86f30ca893d3') ? 'support@pterodactyl.io' : 'unknown@unknown-author.com', + 'uuid' => Uuid::uuid4()->toString(), ]); }); diff --git a/database/migrations/2020_03_22_163911_merge_permissions_table_into_subusers.php b/database/migrations/2020_03_22_163911_merge_permissions_table_into_subusers.php new file mode 100644 index 00000000..67461ecc --- /dev/null +++ b/database/migrations/2020_03_22_163911_merge_permissions_table_into_subusers.php @@ -0,0 +1,128 @@ + P::ACTION_CONTROL_START, + 'power-stop' => P::ACTION_CONTROL_STOP, + 'power-restart' => P::ACTION_CONTROL_RESTART, + 'power-kill' => P::ACTION_CONTROL_STOP, + 'send-command' => P::ACTION_CONTROL_CONSOLE, + 'list-subusers' => P::ACTION_USER_READ, + 'view-subuser' => P::ACTION_USER_READ, + 'edit-subuser' => P::ACTION_USER_UPDATE, + 'create-subuser' => P::ACTION_USER_CREATE, + 'delete-subuser' => P::ACTION_USER_DELETE, + 'view-allocations' => P::ACTION_ALLOCATION_READ, + 'edit-allocation' => P::ACTION_ALLOCATION_UPDATE, + 'view-startup' => P::ACTION_STARTUP_READ, + 'edit-startup' => P::ACTION_STARTUP_UPDATE, + 'view-databases' => P::ACTION_DATABASE_READ, + // Better to just break this flow a bit than accidentally grant a dangerous permission. + 'reset-db-password' => P::ACTION_DATABASE_UPDATE, + 'delete-database' => P::ACTION_DATABASE_DELETE, + 'create-database' => P::ACTION_DATABASE_CREATE, + 'access-sftp' => P::ACTION_FILE_SFTP, + 'list-files' => P::ACTION_FILE_READ, + 'edit-files' => P::ACTION_FILE_READ_CONTENT, + 'save-files' => P::ACTION_FILE_UPDATE, + 'create-files' => P::ACTION_FILE_CREATE, + 'delete-files' => P::ACTION_FILE_DELETE, + 'compress-files' => P::ACTION_FILE_ARCHIVE, + 'list-schedules' => P::ACTION_SCHEDULE_READ, + 'view-schedule' => P::ACTION_SCHEDULE_READ, + 'edit-schedule' => P::ACTION_SCHEDULE_UPDATE, + 'create-schedule' => P::ACTION_SCHEDULE_CREATE, + 'delete-schedule' => P::ACTION_SCHEDULE_DELETE, + // Skipping these permissions as they are granted if you have more specific read/write permissions. + 'move-files' => null, + 'copy-files' => null, + 'decompress-files' => null, + 'upload-files' => null, + 'download-files' => null, + // These permissions do not exist in 1.0 + 'toggle-schedule' => null, + 'queue-schedule' => null, + ]; + + /** + * Run the migrations. + * + * @return void + */ + public function up() + { + Schema::table('subusers', function (Blueprint $table) { + $table->json('permissions')->nullable()->after('server_id'); + }); + + $cursor = DB::table('permissions') + ->select(['subuser_id']) + ->selectRaw('GROUP_CONCAT(permission) as permissions') + ->from('permissions') + ->groupBy(['subuser_id']) + ->cursor(); + + DB::transaction(function () use (&$cursor) { + $cursor->each(function ($datum) { + $updated = Collection::make(explode(',', $datum->permissions)) + ->map(function ($value) { + return self::$permissionsMap[$value] ?? null; + })->filter(function ($value) { + return !is_null($value) && $value !== Permission::ACTION_WEBSOCKET_CONNECT; + }) + // All subusers get this permission, so make sure it gets pushed into the array. + ->merge([ Permission::ACTION_WEBSOCKET_CONNECT ]) + ->unique() + ->values() + ->toJson(); + + DB::update('UPDATE subusers SET permissions = ? WHERE id = ?', [$updated, $datum->subuser_id]); + }); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + $flipped = array_flip(self::$permissionsMap); + + foreach (DB::select('SELECT id, permissions FROM subusers') as $datum) { + $values = []; + foreach (json_decode($datum->permissions, true) as $permission) { + if (!empty($v = $flipped[$permission])) { + $values[] = $datum->id; + $values[] = $v; + } + } + + if (! empty($values)) { + $string = 'VALUES ' . implode(', ', array_fill(0, count($values) / 2, '(?, ?)')); + + DB::insert('INSERT INTO permissions(`subuser_id`, `permission`) ' . $string, $values); + } + } + + Schema::table('subusers', function (Blueprint $table) { + $table->dropColumn('permissions'); + }); + } +} diff --git a/database/migrations/2020_03_22_164814_drop_permissions_table.php b/database/migrations/2020_03_22_164814_drop_permissions_table.php new file mode 100644 index 00000000..da9d677a --- /dev/null +++ b/database/migrations/2020_03_22_164814_drop_permissions_table.php @@ -0,0 +1,34 @@ +increments('id'); + $table->unsignedInteger('subuser_id'); + $table->string('permission'); + + $table->foreign('subuser_id')->references('id')->on('subusers')->onDelete('cascade'); + }); + } +} diff --git a/database/migrations/2020_04_03_203624_add_threads_column_to_servers_table.php b/database/migrations/2020_04_03_203624_add_threads_column_to_servers_table.php new file mode 100644 index 00000000..9b0202ca --- /dev/null +++ b/database/migrations/2020_04_03_203624_add_threads_column_to_servers_table.php @@ -0,0 +1,32 @@ +string('threads')->nullable()->after('cpu'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('servers', function (Blueprint $table) { + $table->dropColumn('threads'); + }); + } +} diff --git a/database/migrations/2020_04_03_230614_create_backups_table.php b/database/migrations/2020_04_03_230614_create_backups_table.php new file mode 100644 index 00000000..68eeee2c --- /dev/null +++ b/database/migrations/2020_04_03_230614_create_backups_table.php @@ -0,0 +1,59 @@ +TABLE_NAME, $result->TABLE_NAME. '_plugin_bak'); + } + + Schema::create('backups', function (Blueprint $table) { + $table->bigIncrements('id'); + $table->unsignedInteger('server_id'); + $table->char('uuid', 36); + $table->string('name'); + $table->text('ignored_files'); + $table->string('disk'); + $table->string('sha256_hash')->nullable(); + $table->integer('bytes')->default(0); + $table->timestamp('completed_at')->nullable(); + $table->timestamps(); + $table->softDeletes(); + + $table->unique('uuid'); + $table->foreign('server_id')->references('id')->on('servers')->onDelete('cascade'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('backups'); + } +} diff --git a/database/migrations/2020_04_04_131016_add_table_server_transfers.php b/database/migrations/2020_04_04_131016_add_table_server_transfers.php new file mode 100644 index 00000000..ae0a57ca --- /dev/null +++ b/database/migrations/2020_04_04_131016_add_table_server_transfers.php @@ -0,0 +1,41 @@ +increments('id'); + $table->integer('server_id')->unsigned(); + $table->tinyInteger('successful')->unsigned()->default(0); + $table->integer('old_node')->unsigned(); + $table->integer('new_node')->unsigned(); + $table->integer('old_allocation')->unsigned(); + $table->integer('new_allocation')->unsigned(); + $table->string('old_additional_allocations')->nullable(); + $table->string('new_additional_allocations')->nullable(); + $table->timestamps(); + + $table->foreign('server_id')->references('id')->on('servers')->onDelete('cascade'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('server_transfers'); + } +} diff --git a/database/migrations/2020_04_10_141024_store_node_tokens_as_encrypted_value.php b/database/migrations/2020_04_10_141024_store_node_tokens_as_encrypted_value.php new file mode 100644 index 00000000..ff81f863 --- /dev/null +++ b/database/migrations/2020_04_10_141024_store_node_tokens_as_encrypted_value.php @@ -0,0 +1,89 @@ +dropUnique(['daemonSecret']); + }); + + Schema::table('nodes', function (Blueprint $table) { + $table->char('uuid', 36)->after('id'); + $table->char('daemon_token_id', 16)->after('upload_size'); + $table->renameColumn('daemonSecret', 'daemon_token'); + }); + + Schema::table('nodes', function (Blueprint $table) { + $table->text('daemon_token')->change(); + }); + + DB::transaction(function () { + /** @var \Illuminate\Contracts\Encryption\Encrypter $encrypter */ + $encrypter = Container::getInstance()->make(Encrypter::class); + + foreach (DB::select('SELECT id, daemon_token FROM nodes') as $datum) { + DB::update('UPDATE nodes SET uuid = ?, daemon_token_id = ?, daemon_token = ? WHERE id = ?', [ + Uuid::uuid4()->toString(), + substr($datum->daemon_token, 0, 16), + $encrypter->encrypt(substr($datum->daemon_token, 16)), + $datum->id, + ]); + } + }); + + Schema::table('nodes', function (Blueprint $table) { + $table->unique(['uuid']); + $table->unique(['daemon_token_id']); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + DB::transaction(function () { + /** @var \Illuminate\Contracts\Encryption\Encrypter $encrypter */ + $encrypter = Container::getInstance()->make(Encrypter::class); + + foreach (DB::select('SELECT id, daemon_token_id, daemon_token FROM nodes') as $datum) { + DB::update('UPDATE nodes SET daemon_token = ? WHERE id = ?', [ + $datum->daemon_token_id . $encrypter->decrypt($datum->daemon_token), + $datum->id, + ]); + } + }); + + Schema::table('nodes', function (Blueprint $table) { + $table->dropUnique(['uuid']); + $table->dropUnique(['daemon_token_id']); + }); + + Schema::table('nodes', function (Blueprint $table) { + $table->dropColumn(['uuid', 'daemon_token_id']); + $table->renameColumn('daemon_token', 'daemonSecret'); + }); + + Schema::table('nodes', function (Blueprint $table) { + $table->string('daemonSecret', 36)->change(); + $table->unique(['daemonSecret']); + }); + } +} diff --git a/database/migrations/2020_04_17_203438_allow_nullable_descriptions.php b/database/migrations/2020_04_17_203438_allow_nullable_descriptions.php new file mode 100644 index 00000000..dfd55fb4 --- /dev/null +++ b/database/migrations/2020_04_17_203438_allow_nullable_descriptions.php @@ -0,0 +1,56 @@ +text('description')->nullable()->change(); + }); + + Schema::table('nests', function (Blueprint $table) { + $table->text('description')->nullable()->change(); + }); + + Schema::table('nodes', function (Blueprint $table) { + $table->text('description')->nullable()->change(); + }); + + Schema::table('locations', function (Blueprint $table) { + $table->text('long')->nullable()->change(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('eggs', function (Blueprint $table) { + $table->text('description')->nullable(false)->change(); + }); + + Schema::table('nests', function (Blueprint $table) { + $table->text('description')->nullable(false)->change(); + }); + + Schema::table('nodes', function (Blueprint $table) { + $table->text('description')->nullable(false)->change(); + }); + + Schema::table('locations', function (Blueprint $table) { + $table->text('long')->nullable(false)->change(); + }); + } +} diff --git a/database/migrations/2020_04_22_055500_add_max_connections_column.php b/database/migrations/2020_04_22_055500_add_max_connections_column.php new file mode 100644 index 00000000..02253dfd --- /dev/null +++ b/database/migrations/2020_04_22_055500_add_max_connections_column.php @@ -0,0 +1,32 @@ +integer('max_connections')->nullable()->default(0)->after('password'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('databases', function (Blueprint $table) { + $table->dropColumn('max_connections'); + }); + } +} diff --git a/database/migrations/2020_04_26_111208_add_backup_limit_to_servers.php b/database/migrations/2020_04_26_111208_add_backup_limit_to_servers.php new file mode 100644 index 00000000..92121b7d --- /dev/null +++ b/database/migrations/2020_04_26_111208_add_backup_limit_to_servers.php @@ -0,0 +1,47 @@ +unsignedInteger('backup_limit')->default(0)->change(); + }); + } else { + Schema::table('servers', function (Blueprint $table) { + $table->unsignedInteger('backup_limit')->default(0)->after('database_limit'); + }); + } + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('servers', function (Blueprint $table) { + $table->dropColumn('backup_limit'); + }); + } +} diff --git a/database/migrations/2020_05_20_234655_add_mounts_table.php b/database/migrations/2020_05_20_234655_add_mounts_table.php new file mode 100644 index 00000000..09846a0a --- /dev/null +++ b/database/migrations/2020_05_20_234655_add_mounts_table.php @@ -0,0 +1,53 @@ +increments('id')->unique(); + $table->char('uuid', 36)->unique(); + $table->string('name')->unique(); + $table->text('description')->nullable(); + $table->string('source'); + $table->string('target'); + $table->tinyInteger('read_only')->unsigned(); + $table->tinyInteger('user_mountable')->unsigned(); + }); + + Schema::create('egg_mount', function (Blueprint $table) { + $table->integer('egg_id'); + $table->integer('mount_id'); + + $table->unique(['egg_id', 'mount_id']); + }); + + Schema::create('mount_node', function (Blueprint $table) { + $table->integer('node_id'); + $table->integer('mount_id'); + + $table->unique(['node_id', 'mount_id']); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('mount_node'); + Schema::dropIfExists('egg_mount'); + Schema::dropIfExists('mounts'); + } +} diff --git a/database/migrations/2020_05_21_192756_add_mount_server_table.php b/database/migrations/2020_05_21_192756_add_mount_server_table.php new file mode 100644 index 00000000..682bd578 --- /dev/null +++ b/database/migrations/2020_05_21_192756_add_mount_server_table.php @@ -0,0 +1,33 @@ +integer('server_id'); + $table->integer('mount_id'); + + $table->unique(['server_id', 'mount_id']); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('mount_server'); + } +} diff --git a/database/migrations/2020_07_02_213612_create_user_recovery_tokens_table.php b/database/migrations/2020_07_02_213612_create_user_recovery_tokens_table.php new file mode 100644 index 00000000..df0f3491 --- /dev/null +++ b/database/migrations/2020_07_02_213612_create_user_recovery_tokens_table.php @@ -0,0 +1,35 @@ +id(); + $table->unsignedInteger('user_id'); + $table->string('token'); + $table->timestamp('created_at')->nullable(); + + $table->foreign('user_id')->references('id')->on('users')->cascadeOnDelete(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('recovery_tokens'); + } +} diff --git a/database/migrations/2020_07_09_201845_add_notes_column_for_allocations.php b/database/migrations/2020_07_09_201845_add_notes_column_for_allocations.php new file mode 100644 index 00000000..ed71f9c2 --- /dev/null +++ b/database/migrations/2020_07_09_201845_add_notes_column_for_allocations.php @@ -0,0 +1,32 @@ +string('notes')->nullable()->after('server_id'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('allocations', function (Blueprint $table) { + $table->dropColumn('notes'); + }); + } +} diff --git a/database/migrations/2020_08_20_205533_add_backup_state_column_to_backups.php b/database/migrations/2020_08_20_205533_add_backup_state_column_to_backups.php new file mode 100644 index 00000000..a0856849 --- /dev/null +++ b/database/migrations/2020_08_20_205533_add_backup_state_column_to_backups.php @@ -0,0 +1,32 @@ +boolean('is_successful')->after('uuid')->default(true); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('backups', function (Blueprint $table) { + $table->dropColumn('is_successful'); + }); + } +} diff --git a/database/migrations/2020_08_22_132500_update_bytes_to_unsigned_bigint.php b/database/migrations/2020_08_22_132500_update_bytes_to_unsigned_bigint.php new file mode 100644 index 00000000..802994eb --- /dev/null +++ b/database/migrations/2020_08_22_132500_update_bytes_to_unsigned_bigint.php @@ -0,0 +1,32 @@ +unsignedBigInteger('bytes')->default(0)->change(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('backups', function (Blueprint $table) { + $table->integer('bytes')->default(0)->change(); + }); + } +} diff --git a/database/migrations/2020_08_23_175331_modify_checksums_column_for_backups.php b/database/migrations/2020_08_23_175331_modify_checksums_column_for_backups.php new file mode 100644 index 00000000..8a9013cd --- /dev/null +++ b/database/migrations/2020_08_23_175331_modify_checksums_column_for_backups.php @@ -0,0 +1,41 @@ +renameColumn('sha256_hash', 'checksum'); + }); + + Schema::table('backups', function (Blueprint $table) { + DB::update('UPDATE backups SET checksum = CONCAT(\'sha256:\', checksum)'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('backups', function (Blueprint $table) { + $table->renameColumn('checksum', 'sha256_hash'); + }); + + Schema::table('backups', function (Blueprint $table) { + DB::update('UPDATE backups SET sha256_hash = SUBSTRING(sha256_hash, 8)'); + }); + } +} diff --git a/database/migrations/2020_09_13_110007_drop_packs_from_servers.php b/database/migrations/2020_09_13_110007_drop_packs_from_servers.php new file mode 100644 index 00000000..b1d2c1bf --- /dev/null +++ b/database/migrations/2020_09_13_110007_drop_packs_from_servers.php @@ -0,0 +1,34 @@ +dropForeign(['pack_id']); + $table->dropColumn('pack_id'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('servers', function (Blueprint $table) { + $table->unsignedInteger('pack_id')->after('egg_id')->nullable(); + $table->foreign('pack_id')->references('id')->on('packs'); + }); + } +} diff --git a/database/migrations/2020_09_13_110021_drop_packs_from_api_key_permissions.php b/database/migrations/2020_09_13_110021_drop_packs_from_api_key_permissions.php new file mode 100644 index 00000000..879a64bd --- /dev/null +++ b/database/migrations/2020_09_13_110021_drop_packs_from_api_key_permissions.php @@ -0,0 +1,32 @@ +dropColumn('r_packs'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('api_keys', function (Blueprint $table) { + $table->unsignedTinyInteger('r_packs')->default(0); + }); + } +} diff --git a/database/migrations/2020_09_13_110047_drop_packs_table.php b/database/migrations/2020_09_13_110047_drop_packs_table.php new file mode 100644 index 00000000..2ab85a5d --- /dev/null +++ b/database/migrations/2020_09_13_110047_drop_packs_table.php @@ -0,0 +1,43 @@ +increments('id'); + $table->unsignedInteger('egg_id'); + $table->char('uuid', 36)->unique(); + $table->string('name'); + $table->string('version'); + $table->text('description')->nullable(); + $table->tinyInteger('selectable')->default(1); + $table->tinyInteger('visible')->default(1); + $table->tinyInteger('locked')->default(0); + $table->timestamps(); + }); + + Schema::table('packs', function (Blueprint $table) { + $table->foreign('egg_id')->references('id')->on('eggs')->cascadeOnDelete(); + }); + } +} diff --git a/database/migrations/2020_09_13_113503_drop_daemon_key_table.php b/database/migrations/2020_09_13_113503_drop_daemon_key_table.php new file mode 100644 index 00000000..e418a85e --- /dev/null +++ b/database/migrations/2020_09_13_113503_drop_daemon_key_table.php @@ -0,0 +1,40 @@ +increments('id'); + $table->unsignedInteger('server_id'); + $table->unsignedInteger('user_id'); + $table->string('secret')->unique(); + $table->timestamp('expires_at'); + $table->timestamps(); + }); + + Schema::table('daemon_keys', function (Blueprint $table) { + $table->foreign('server_id')->references('id')->on('servers')->cascadeOnDelete(); + $table->foreign('user_id')->references('id')->on('users')->cascadeOnDelete(); + }); + } +} diff --git a/database/migrations/2020_10_10_165437_change_unique_database_name_to_account_for_server.php b/database/migrations/2020_10_10_165437_change_unique_database_name_to_account_for_server.php new file mode 100644 index 00000000..a32d52e6 --- /dev/null +++ b/database/migrations/2020_10_10_165437_change_unique_database_name_to_account_for_server.php @@ -0,0 +1,41 @@ +dropUnique(['database_host_id', 'database']); + }); + + Schema::table('databases', function (Blueprint $table) { + $table->unique(['database_host_id', 'server_id', 'database']); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('databases', function (Blueprint $table) { + $table->dropUnique(['database_host_id', 'server_id', 'database']); + }); + + Schema::table('databases', function (Blueprint $table) { + $table->unique(['database_host_id', 'database']); + }); + + } +} diff --git a/database/seeds/EggSeeder.php b/database/seeds/EggSeeder.php index 245d74ac..dea5b675 100644 --- a/database/seeds/EggSeeder.php +++ b/database/seeds/EggSeeder.php @@ -112,14 +112,16 @@ class EggSeeder extends Seeder $files = $this->filesystem->allFiles(database_path('seeds/eggs/' . kebab_case($nest->name))); $this->command->alert('Updating Eggs for Nest: ' . $nest->name); - collect($files)->each(function ($file) use ($nest) { + Collection::make($files)->each(function ($file) use ($nest) { /* @var \Symfony\Component\Finder\SplFileInfo $file */ $decoded = json_decode($file->getContents()); if (json_last_error() !== JSON_ERROR_NONE) { - return $this->command->error('JSON decode exception for ' . $file->getFilename() . ': ' . json_last_error_msg()); + $this->command->error('JSON decode exception for ' . $file->getFilename() . ': ' . json_last_error_msg()); + + return; } - $file = new UploadedFile($file->getPathname(), $file->getFilename(), 'application/json', $file->getSize()); + $file = new UploadedFile($file->getPathname(), $file->getFilename(), 'application/json'); try { $egg = $this->repository->setColumns('id')->findFirstWhere([ @@ -128,13 +130,13 @@ class EggSeeder extends Seeder ['nest_id', '=', $nest->id], ]); - $this->updateImporterService->handle($egg->id, $file); + $this->updateImporterService->handle($egg, $file); - return $this->command->info('Updated ' . $decoded->name); + $this->command->info('Updated ' . $decoded->name); } catch (RecordNotFoundException $exception) { $this->importerService->handle($file, $nest->id); - return $this->command->comment('Created ' . $decoded->name); + $this->command->comment('Created ' . $decoded->name); } }); diff --git a/database/seeds/eggs/minecraft/egg-bungeecord.json b/database/seeds/eggs/minecraft/egg-bungeecord.json index d527024f..b0c07e50 100644 --- a/database/seeds/eggs/minecraft/egg-bungeecord.json +++ b/database/seeds/eggs/minecraft/egg-bungeecord.json @@ -3,14 +3,14 @@ "meta": { "version": "PTDL_v1" }, - "exported_at": "2017-11-03T22:15:10-05:00", + "exported_at": "2020-04-12T16:00:51-07:00", "name": "Bungeecord", "author": "support@pterodactyl.io", "description": "For a long time, Minecraft server owners have had a dream that encompasses a free, easy, and reliable way to connect multiple Minecraft servers together. BungeeCord is the answer to said dream. Whether you are a small server wishing to string multiple game-modes together, or the owner of the ShotBow Network, BungeeCord is the ideal solution for you. With the help of BungeeCord, you will be able to unlock your community's full potential.", "image": "quay.io\/pterodactyl\/core:java", "startup": "java -Xms128M -Xmx{{SERVER_MEMORY}}M -jar {{SERVER_JARFILE}}", "config": { - "files": "{\r\n \"config.yml\": {\r\n \"parser\": \"yaml\",\r\n \"find\": {\r\n \"listeners[0].query_enabled\": true,\r\n \"listeners[0].query_port\": \"{{server.build.default.port}}\",\r\n \"listeners[0].host\": \"0.0.0.0:{{server.build.default.port}}\",\r\n \"servers.*.address\": {\r\n \"127.0.0.1\": \"{{config.docker.interface}}\",\r\n \"localhost\": \"{{config.docker.interface}}\"\r\n }\r\n }\r\n }\r\n}", + "files": "{\r\n \"config.yml\": {\r\n \"parser\": \"yaml\",\r\n \"find\": {\r\n \"listeners[0].query_enabled\": true,\r\n \"listeners[0].query_port\": \"{{server.build.default.port}}\",\r\n \"listeners[0].host\": \"0.0.0.0:{{server.build.default.port}}\",\r\n \"servers.*.address\": {\r\n \"regex:^(127\\\\.0\\\\.0\\\\.1|localhost)(:\\\\d{1,5})?$\": \"{{config.docker.interface}}$2\"\r\n }\r\n }\r\n }\r\n}", "startup": "{\r\n \"done\": \"Listening on \",\r\n \"userInteraction\": [\r\n \"Listening on \/0.0.0.0:25577\"\r\n ]\r\n}", "logs": "{\r\n \"custom\": false,\r\n \"location\": \"proxy.log.0\"\r\n}", "stop": "end" @@ -42,4 +42,4 @@ "rules": "required|regex:\/^([\\w\\d._-]+)(\\.jar)$\/" } ] -} +} \ No newline at end of file diff --git a/database/seeds/eggs/minecraft/egg-forge-minecraft.json b/database/seeds/eggs/minecraft/egg-forge-minecraft.json index 2e8b43bf..96b707e5 100644 --- a/database/seeds/eggs/minecraft/egg-forge-minecraft.json +++ b/database/seeds/eggs/minecraft/egg-forge-minecraft.json @@ -3,7 +3,7 @@ "meta": { "version": "PTDL_v1" }, - "exported_at": "2019-02-07T07:47:35-05:00", + "exported_at": "2020-05-24T12:15:13-04:00", "name": "Forge Minecraft", "author": "support@pterodactyl.io", "description": "Minecraft Forge Server. Minecraft Forge is a modding API (Application Programming Interface), which makes it easier to create mods, and also make sure mods are compatible with each other.", @@ -17,9 +17,9 @@ }, "scripts": { "installation": { - "script": "#!\/bin\/ash\r\n# Forge Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\napk update\r\napk add curl\r\n\r\nif [ -z \"$MC_VERSION\" ] || [ \"$MC_VERSION\" == \"latest\" ]; then\r\n FORGE_VERSION=$(echo $(curl -sSl http:\/\/files.minecraftforge.net\/maven\/net\/minecraftforge\/forge\/ | grep -A2 Latest | grep small) | grep -o -e '[1].[0-9]*.[0-9]* - [0-9]*.[0-9]*.[0-9]*.[0-9]*' | sed 's\/ \/\/g')\r\nelse\r\n FORGE_VERSION=$(echo $(curl -sl http:\/\/files.minecraftforge.net\/maven\/net\/minecraftforge\/forge\/index_$MC_VERSION.html | grep -A2 Latest | grep small) | grep -o -e '[1].[0-9]*.[0-9]* - [0-9]*.[0-9]*.[0-9]*.[0-9]*' | sed 's\/ \/\/g')\r\nfi\r\n\r\ncd \/mnt\/server\r\n\r\necho -e \"\\nDownloading Forge Version $FORGE_VERSION\\n\"\r\ncurl -sS http:\/\/files.minecraftforge.net\/maven\/net\/minecraftforge\/forge\/$FORGE_VERSION\/forge-$FORGE_VERSION-installer.jar -o installer.jar\r\ncurl -sS http:\/\/files.minecraftforge.net\/maven\/net\/minecraftforge\/forge\/$FORGE_VERSION\/forge-$FORGE_VERSION-universal.jar -o $SERVER_JARFILE\r\n\r\necho -e \"\\nInstalling forge server usint the installer jar file.\\n\"\r\njava -jar installer.jar --installServer\r\n\r\necho -e \"\\nDeleting installer jar file and cleaning up.\\n\"\r\nrm -rf installer.jar", - "container": "openjdk:8-alpine", - "entrypoint": "ash" + "script": "#!\/bin\/bash\r\n# Forge Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\napt update\r\napt install -y curl jq\r\n\r\n#Go into main direction\r\nif [ ! -d \/mnt\/server ]; then\r\n mkdir \/mnt\/server\r\nfi\r\n\r\ncd \/mnt\/server\r\n\r\nif [ ! -z ${FORGE_VERSION} ]; then\r\n DOWNLOAD_LINK=https:\/\/files.minecraftforge.net\/maven\/net\/minecraftforge\/forge\/${FORGE_VERSION}\/forge-${FORGE_VERSION}\r\nelse\r\n JSON_DATA=$(curl -sSL https:\/\/files.minecraftforge.net\/maven\/net\/minecraftforge\/forge\/promotions_slim.json)\r\n\r\n if [ \"${MC_VERSION}\" == \"latest\" ] || [ \"${MC_VERSION}\" == \"\" ] ; then\r\n echo -e \"getting latest recommended version of forge.\"\r\n MC_VERSION=$(echo -e ${JSON_DATA} | jq -r '.promos | del(.\"latest-1.7.10\") | del(.\"1.7.10-latest-1.7.10\") | to_entries[] | .key | select(contains(\"recommended\")) | split(\"-\")[0]' | sort -t. -k 1,1n -k 2,2n -k 3,3n -k 4,4n | tail -1)\r\n \tBUILD_TYPE=recommended\r\n fi\r\n\r\n if [ \"${BUILD_TYPE}\" != \"recommended\" ] && [ \"${BUILD_TYPE}\" != \"latest\" ]; then\r\n BUILD_TYPE=recommended\r\n fi\r\n\r\n echo -e \"minecraft version: ${MC_VERSION}\"\r\n echo -e \"build type: ${BUILD_TYPE}\"\r\n\r\n ## some variables for getting versions and things\r\n FILE_SITE=$(echo -e ${JSON_DATA} | jq -r '.homepage' | sed \"s\/http:\/https:\/g\")\r\n VERSION_KEY=$(echo -e ${JSON_DATA} | jq -r --arg MC_VERSION \"${MC_VERSION}\" --arg BUILD_TYPE \"${BUILD_TYPE}\" '.promos | del(.\"latest-1.7.10\") | del(.\"1.7.10-latest-1.7.10\") | to_entries[] | .key | select(contains($MC_VERSION)) | select(contains($BUILD_TYPE))')\r\n\r\n ## locating the forge version\r\n if [ \"${VERSION_KEY}\" == \"\" ] && [ \"${BUILD_TYPE}\" == \"recommended\" ]; then\r\n echo -e \"dropping back to latest from recommended due to there not being a recommended version of forge for the mc version requested.\"\r\n VERSION_KEY=$(echo -e ${JSON_DATA} | jq -r --arg MC_VERSION \"${MC_VERSION}\" '.promos | del(.\"latest-1.7.10\") | del(.\"1.7.10-latest-1.7.10\") | to_entries[] | .key | select(contains($MC_VERSION)) | select(contains(\"recommended\"))')\r\n fi\r\n\r\n ## Error if the mc version set wasn't valid.\r\n if [ \"${VERSION_KEY}\" == \"\" ] || [ \"${VERSION_KEY}\" == \"null\" ]; then\r\n \techo -e \"The install failed because there is no valid version of forge for the version on minecraft selected.\"\r\n \texit 1\r\n fi\r\n\r\n FORGE_VERSION=$(echo -e ${JSON_DATA} | jq -r --arg VERSION_KEY \"$VERSION_KEY\" '.promos | .[$VERSION_KEY]')\r\n\r\n if [ \"${MC_VERSION}\" == \"1.7.10\" ] || [ \"${MC_VERSION}\" == \"1.8.9\" ]; then\r\n DOWNLOAD_LINK=${FILE_SITE}${MC_VERSION}-${FORGE_VERSION}-${MC_VERSION}\/forge-${MC_VERSION}-${FORGE_VERSION}-${MC_VERSION}\r\n FORGE_JAR=forge-${MC_VERSION}-${FORGE_VERSION}-${MC_VERSION}.jar\r\n if [ \"${MC_VERSION}\" == \"1.7.10\" ]; then\r\n FORGE_JAR=forge-${MC_VERSION}-${FORGE_VERSION}-${MC_VERSION}-universal.jar\r\n fi\r\n else\r\n DOWNLOAD_LINK=${FILE_SITE}${MC_VERSION}-${FORGE_VERSION}\/forge-${MC_VERSION}-${FORGE_VERSION}\r\n FORGE_JAR=forge-${MC_VERSION}-${FORGE_VERSION}.jar\r\n fi\r\nfi\r\n\r\n\r\n#Adding .jar when not eding by SERVER_JARFILE\r\nif [[ ! $SERVER_JARFILE = *\\.jar ]]; then\r\n SERVER_JARFILE=\"$SERVER_JARFILE.jar\"\r\nfi\r\n\r\n#Downloading jars\r\necho -e \"Downloading forge version ${FORGE_VERSION}\"\r\nif [ ! -z \"${DOWNLOAD_LINK}\" ]; then \r\n if curl --output \/dev\/null --silent --head --fail ${DOWNLOAD_LINK}-installer.jar; then\r\n echo -e \"installer jar download link is valid.\"\r\n else\r\n echo -e \"link is invalid closing out\"\r\n exit 2\r\n fi\r\n\r\n echo -e \"no download link closing out\"\r\n exit 3\r\nfi\r\n\r\ncurl -s -o installer.jar -sS ${DOWNLOAD_LINK}-installer.jar\r\n\r\n#Checking if downloaded jars exist\r\nif [ ! -f .\/installer.jar ]; then\r\n echo \"!!! Error by downloading forge version ${FORGE_VERSION} !!!\"\r\n exit\r\nfi\r\n\r\n#Installing server\r\necho -e \"Installing forge server.\\n\"\r\njava -jar installer.jar --installServer || { echo -e \"install failed\"; exit 4; }\r\n\r\nmv $FORGE_JAR $SERVER_JARFILE\r\n\r\n#Deleting installer.jar\r\necho -e \"Deleting installer.jar file.\\n\"\r\nrm -rf installer.jar", + "container": "openjdk:8-jdk-slim", + "entrypoint": "bash" } }, "variables": [ @@ -33,12 +33,30 @@ "rules": "required|regex:\/^([\\w\\d._-]+)(\\.jar)$\/" }, { - "name": "Minecraft version", - "description": "The version of minecraft that you want to run. Example (1.10.2).", + "name": "Forge version", + "description": "The version of minecraft you want to install for.\r\n\r\nLeaving latest will install the latest recommended version.", "env_variable": "MC_VERSION", "default_value": "latest", "user_viewable": 1, "user_editable": 1, + "rules": "required|string|max:9" + }, + { + "name": "Build Type", + "description": "The type of server jar to download from forge.\r\n\r\nValid types are \"recommended\" and \"latest\".", + "env_variable": "BUILD_TYPE", + "default_value": "recommended", + "user_viewable": 1, + "user_editable": 1, + "rules": "required|string|max:20" + }, + { + "name": "Forge Version", + "description": "Gets an exact version.\r\n\r\nEx. 1.15.2-31.2.4\r\n\r\nOverrides MC_VERSION and BUILD_TYPE. If it fails to download the server files it will fail to install.", + "env_variable": "FORGE_VERSION", + "default_value": "", + "user_viewable": 1, + "user_editable": 1, "rules": "required|string|max:20" } ] diff --git a/database/seeds/eggs/minecraft/egg-vanilla-minecraft.json b/database/seeds/eggs/minecraft/egg-vanilla-minecraft.json index 5937fc17..f613c815 100644 --- a/database/seeds/eggs/minecraft/egg-vanilla-minecraft.json +++ b/database/seeds/eggs/minecraft/egg-vanilla-minecraft.json @@ -3,22 +3,22 @@ "meta": { "version": "PTDL_v1" }, - "exported_at": "2018-06-19T17:09:18-04:00", + "exported_at": "2019-12-25T19:55:01-05:00", "name": "Vanilla Minecraft", "author": "support@pterodactyl.io", "description": "Minecraft is a game about placing blocks and going on adventures. Explore randomly generated worlds and build amazing things from the simplest of homes to the grandest of castles. Play in Creative Mode with unlimited resources or mine deep in Survival Mode, crafting weapons and armor to fend off dangerous mobs. Do all this alone or with friends.", "image": "quay.io\/pterodactyl\/core:java", "startup": "java -Xms128M -Xmx{{SERVER_MEMORY}}M -jar {{SERVER_JARFILE}}", "config": { - "files": "{\r\n \"server.properties\": {\r\n \"parser\": \"properties\",\r\n \"find\": {\r\n \"server-ip\": \"0.0.0.0\",\r\n \"enable-query\": \"true\",\r\n \"server-port\": \"{{server.build.default.port}}\",\r\n \"query.port\": \"{{server.build.default.port}}\"\r\n }\r\n }\r\n}", + "files": "{\r\n \"server.properties\": {\r\n \"parser\": \"properties\",\r\n \"find\": {\r\n \"server-ip\": \"0.0.0.0\",\r\n \"enable-query\": \"true\",\r\n \"server-port\": \"{{server.build.default.port}}\"\r\n }\r\n }\r\n}", "startup": "{\r\n \"done\": \")! For help, type \",\r\n \"userInteraction\": [\r\n \"Go to eula.txt for more info.\"\r\n ]\r\n}", "logs": "{\r\n \"custom\": false,\r\n \"location\": \"logs\/latest.log\"\r\n}", "stop": "stop" }, "scripts": { "installation": { - "script": "#!\/bin\/ash\r\n# Vanilla MC Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\napk update\r\napk add curl jq\r\n\r\ncd \/mnt\/server\r\n\r\nLATEST_VERSION=`curl https:\/\/launchermeta.mojang.com\/mc\/game\/version_manifest.json | jq -r '.latest.release'`\r\n\r\nif [ -z \"$VANILLA_VERSION\" ] || [ \"$VANILLA_VERSION\" == \"latest\" ]; then\r\n MANIFEST_URL=$(curl https:\/\/launchermeta.mojang.com\/mc\/game\/version_manifest.json | jq .versions | jq -r '.[] | select(.id == \"'$LATEST_VERSION'\") | .url')\r\nelse\r\n MANIFEST_URL=$(curl https:\/\/launchermeta.mojang.com\/mc\/game\/version_manifest.json | jq .versions | jq -r '.[] | select(.id == \"'$VANILLA_VERSION'\") | .url')\r\nfi\r\n\r\nDOWNLOAD_URL=`curl $MANIFEST_URL | jq .downloads.server | jq -r '. | .url'`\r\n\r\ncurl -o ${SERVER_JARFILE} $DOWNLOAD_URL", - "container": "alpine:3.9", + "script": "#!\/bin\/ash\r\n# Vanilla MC Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\napk add curl --no-cache --update jq\r\n\r\nmkdir -p \/mnt\/server\r\ncd \/mnt\/server\r\n\r\nLATEST_VERSION=`curl https:\/\/launchermeta.mojang.com\/mc\/game\/version_manifest.json | jq -r '.latest.release'`\r\n\r\necho -e \"latest version is $LATEST_VERSION\"\r\n\r\nif [ -z \"$VANILLA_VERSION\" ] || [ \"$VANILLA_VERSION\" == \"latest\" ]; then\r\n MANIFEST_URL=$(curl -sSL https:\/\/launchermeta.mojang.com\/mc\/game\/version_manifest.json | jq --arg VERSION $LATEST_VERSION -r '.versions | .[] | select(.id== $VERSION )|.url')\r\nelse\r\n MANIFEST_URL=$(curl -sSL https:\/\/launchermeta.mojang.com\/mc\/game\/version_manifest.json | jq --arg VERSION $VANILLA_VERSION -r '.versions | .[] | select(.id== $VERSION )|.url')\r\nfi\r\n\r\nDOWNLOAD_URL=$(curl ${MANIFEST_URL} | jq .downloads.server | jq -r '. | .url')\r\n\r\necho -e \"running: curl -o ${SERVER_JARFILE} $DOWNLOAD_URL\"\r\ncurl -o ${SERVER_JARFILE} $DOWNLOAD_URL\r\n\r\necho -e \"Install Complete\"", + "container": "alpine:3.10", "entrypoint": "ash" } }, @@ -42,4 +42,4 @@ "rules": "required|string|between:3,15" } ] -} +} \ No newline at end of file diff --git a/database/seeds/eggs/source-engine/egg-ark--survival-evolved.json b/database/seeds/eggs/source-engine/egg-ark--survival-evolved.json index 35028b20..e86a89a9 100644 --- a/database/seeds/eggs/source-engine/egg-ark--survival-evolved.json +++ b/database/seeds/eggs/source-engine/egg-ark--survival-evolved.json @@ -3,26 +3,35 @@ "meta": { "version": "PTDL_v1" }, - "exported_at": "2019-02-13T12:30:54-05:00", + "exported_at": "2018-10-29T20:51:32+01:00", "name": "Ark: Survival Evolved", "author": "support@pterodactyl.io", "description": "As a man or woman stranded, naked, freezing, and starving on the unforgiving shores of a mysterious island called ARK, use your skill and cunning to kill or tame and ride the plethora of leviathan dinosaurs and other primeval creatures roaming the land. Hunt, harvest resources, craft items, grow crops, research technologies, and build shelters to withstand the elements and store valuables, all while teaming up with (or preying upon) hundreds of other players to survive, dominate... and escape! \u2014 Gamepedia: ARK", "image": "quay.io\/pterodactyl\/core:source", - "startup": ".\/ShooterGame\/Binaries\/Linux\/ShooterGameServer {{SERVER_MAP}}?listen?ServerPassword={{ARK_PASSWORD}}?ServerAdminPassword={{ARK_ADMIN_PASSWORD}}?Port={{SERVER_PORT}}?MaxPlayers={{SERVER_MAX_PLAYERS}}", + "startup": "\"cd ShooterGame\/Binaries\/Linux && .\/ShooterGameServer {{SERVER_MAP}}?listen?SessionName='{{SESSION_NAME}}'?ServerPassword={{ARK_PASSWORD}}?ServerAdminPassword={{ARK_ADMIN_PASSWORD}}?Port={{PORT}}?MaxPlayers={{SERVER_MAX_PLAYERS}}?RCONPort={{RCON_PORT}}?QueryPort={{QUERY_PORT}}?RCONEnabled={{ENABLE_RCON}} -server -log\"", "config": { "files": "{}", "startup": "{\r\n \"done\": \"Setting breakpad minidump AppID = 346110\",\r\n \"userInteraction\": []\r\n}", "logs": "{\r\n \"custom\": true,\r\n \"location\": \"logs\/latest.log\"\r\n}", - "stop": "quit" + "stop": "^C" }, "scripts": { "installation": { - "script": "#!\/bin\/bash\n# ARK: Installation Script\n#\n# Server Files: \/mnt\/server\napt -y update\napt -y --no-install-recommends install curl lib32gcc1 ca-certificates\n\ncd \/tmp\ncurl -sSL -o steamcmd.tar.gz http:\/\/media.steampowered.com\/installer\/steamcmd_linux.tar.gz\n\nmkdir -p \/mnt\/server\/steamcmd\nmkdir -p \/mnt\/server\/Engine\/Binaries\/ThirdParty\/SteamCMD\/Linux\n\ntar -xzvf steamcmd.tar.gz -C \/mnt\/server\/steamcmd\ntar -xzvf steamcmd.tar.gz -C \/mnt\/server\/Engine\/Binaries\/ThirdParty\/SteamCMD\/Linux\n\ncd \/mnt\/server\/steamcmd\n\n# SteamCMD fails otherwise for some reason, even running as root.\n# This is changed at the end of the install process anyways.\nchown -R root:root \/mnt\n\nexport HOME=\/mnt\/server\n.\/steamcmd.sh +login anonymous +force_install_dir \/mnt\/server +app_update 376030 +quit\n\nmkdir -p \/mnt\/server\/.steam\/sdk32\ncp -v linux32\/steamclient.so ..\/.steam\/sdk32\/steamclient.so", + "script": "#!\/bin\/bash\r\n# ARK: Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\napt -y update\r\napt -y --no-install-recommends install curl lib32gcc1 ca-certificates\r\n\r\ncd \/tmp\r\ncurl -sSL -o steamcmd.tar.gz http:\/\/media.steampowered.com\/installer\/steamcmd_linux.tar.gz\r\n\r\nmkdir -p \/mnt\/server\/steamcmd\r\nmkdir -p \/mnt\/server\/Engine\/Binaries\/ThirdParty\/SteamCMD\/Linux\r\n\r\ntar -xzvf steamcmd.tar.gz -C \/mnt\/server\/steamcmd\r\ntar -xzvf steamcmd.tar.gz -C \/mnt\/server\/Engine\/Binaries\/ThirdParty\/SteamCMD\/Linux\r\n\r\ncd \/mnt\/server\/steamcmd\r\n\r\n# SteamCMD fails otherwise for some reason, even running as root.\r\n# This is changed at the end of the install process anyways.\r\nchown -R root:root \/mnt\r\n\r\nexport HOME=\/mnt\/server\r\n.\/steamcmd.sh +login anonymous +force_install_dir \/mnt\/server +app_update 376030 +quit\r\n\r\nmkdir -p \/mnt\/server\/.steam\/sdk32\r\ncp -v linux32\/steamclient.so ..\/.steam\/sdk32\/steamclient.so\r\n\r\ncd \/mnt\/server\/Engine\/Binaries\/ThirdParty\/SteamCMD\/Linux\r\n\r\nln -sf ..\/..\/..\/..\/..\/Steam\/steamapps steamapps\r\n\r\ncd \/mnt\/server", "container": "ubuntu:16.04", "entrypoint": "bash" } }, "variables": [ + { + "name": "Server Name", + "description": "ARK server name", + "env_variable": "SESSION_NAME", + "default_value": "ARK SERVER", + "user_viewable": 1, + "user_editable": 1, + "rules": "required|string|max:128" + }, { "name": "Server Password", "description": "If specified, players must provide this password to join the server.", @@ -39,7 +48,43 @@ "default_value": "", "user_viewable": 1, "user_editable": 1, - "rules": "nullable|alpha_dash|between:1,100" + "rules": "nullable|alpha_dash|between:1,100" + }, + { + "name": "Server Port", + "description": "ARK server port used by client.", + "env_variable": "PORT", + "default_value": "7777", + "user_viewable": 1, + "user_editable": 1, + "rules": "required|numeric" + }, + { + "name": "Use Rcon", + "description": "Enable or disable rcon system. (true or false)", + "env_variable": "ENABLE_RCON", + "default_value": "false", + "user_viewable": 1, + "user_editable": 1, + "rules": "required|string|max:5" + }, + { + "name": "Rcon Port", + "description": "ARK rcon port used by rcon tools.", + "env_variable": "RCON_PORT", + "default_value": "27020", + "user_viewable": 1, + "user_editable": 1, + "rules": "required|numeric" + }, + { + "name": "Query Port", + "description": "ARK query port used by steam server browser and ark client server browser.", + "env_variable": "QUERY_PORT", + "default_value": "27015", + "user_viewable": 1, + "user_editable": 1, + "rules": "required|numeric" }, { "name": "Maximum Players", @@ -50,9 +95,18 @@ "user_editable": 1, "rules": "required|numeric|digits_between:1,4" }, + { + "name": "App ID", + "description": "ARK steam app id for auto updates. Leave blank to avoid auto update.", + "env_variable": "SRCDS_APPID", + "default_value": "376030", + "user_viewable": 1, + "user_editable": 0, + "rules": "nullable|numeric" + }, { "name": "Server Map", - "description": "Available Maps: TheIsland, TheCenter, Ragnarok, ScorchedEarth_P, Aberration_P", + "description": "Available Maps: TheIsland, TheCenter, Ragnarok, ScorchedEarth_P, Aberration_P, Extinction", "env_variable": "SERVER_MAP", "default_value": "TheIsland", "user_viewable": 1, @@ -60,4 +114,4 @@ "rules": "required|string|max:20" } ] -} \ No newline at end of file +} diff --git a/database/seeds/eggs/source-engine/egg-counter--strike--global-offensive.json b/database/seeds/eggs/source-engine/egg-counter--strike--global-offensive.json index ad921025..191aa99e 100644 --- a/database/seeds/eggs/source-engine/egg-counter--strike--global-offensive.json +++ b/database/seeds/eggs/source-engine/egg-counter--strike--global-offensive.json @@ -3,7 +3,7 @@ "meta": { "version": "PTDL_v1" }, - "exported_at": "2018-06-19T07:46:06-04:00", + "exported_at": "2019-12-08T10:52:19-05:00", "name": "Counter-Strike: Global Offensive", "author": "support@pterodactyl.io", "description": "Counter-Strike: Global Offensive is a multiplayer first-person shooter video game developed by Hidden Path Entertainment and Valve Corporation.", @@ -17,8 +17,8 @@ }, "scripts": { "installation": { - "script": "#!\/bin\/bash\n# CSGO Installation Script\n#\n# Server Files: \/mnt\/server\napt -y update\napt -y --no-install-recommends install curl lib32gcc1 ca-certificates\n\ncd \/tmp\ncurl -sSL -o steamcmd.tar.gz http:\/\/media.steampowered.com\/installer\/steamcmd_linux.tar.gz\n\nmkdir -p \/mnt\/server\/steamcmd\ntar -xzvf steamcmd.tar.gz -C \/mnt\/server\/steamcmd\ncd \/mnt\/server\/steamcmd\n\n# SteamCMD fails otherwise for some reason, even running as root.\n# This is changed at the end of the install process anyways.\nchown -R root:root \/mnt\n\nexport HOME=\/mnt\/server\n.\/steamcmd.sh +login anonymous +force_install_dir \/mnt\/server +app_update 740 +quit\n\nmkdir -p \/mnt\/server\/.steam\/sdk32\ncp -v linux32\/steamclient.so ..\/.steam\/sdk32\/steamclient.so", - "container": "ubuntu:16.04", + "script": "#!\/bin\/bash\r\n# steamcmd Base Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\n# Image to install with is 'ubuntu:18.04'\r\napt -y update\r\napt -y --no-install-recommends install curl lib32gcc1 ca-certificates\r\n\r\n## just in case someone removed the defaults.\r\nif [ \"${STEAM_USER}\" == \"\" ]; then\r\n STEAM_USER=anonymous\r\n STEAM_PASS=\"\"\r\n STEAM_AUTH=\"\"\r\nfi\r\n\r\n## download and install steamcmd\r\ncd \/tmp\r\nmkdir -p \/mnt\/server\/steamcmd\r\ncurl -sSL -o steamcmd.tar.gz https:\/\/steamcdn-a.akamaihd.net\/client\/installer\/steamcmd_linux.tar.gz\r\ntar -xzvf steamcmd.tar.gz -C \/mnt\/server\/steamcmd\r\ncd \/mnt\/server\/steamcmd\r\n\r\n# SteamCMD fails otherwise for some reason, even running as root.\r\n# This is changed at the end of the install process anyways.\r\nchown -R root:root \/mnt\r\nexport HOME=\/mnt\/server\r\n\r\n## install game using steamcmd\r\n.\/steamcmd.sh +login ${STEAM_USER} ${STEAM_PASS} ${STEAM_AUTH} +force_install_dir \/mnt\/server +app_update ${SRCDS_APPID} ${EXTRA_FLAGS} +quit ## other flags may be needed depending on install. looking at you cs 1.6\r\n\r\n## set up 32 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk32\r\ncp -v linux32\/steamclient.so ..\/.steam\/sdk32\/steamclient.so\r\n\r\n## set up 64 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk64\r\ncp -v linux64\/steamclient.so ..\/.steam\/sdk64\/steamclient.so", + "container": "ubuntu:18.04", "entrypoint": "bash" } }, @@ -51,4 +51,4 @@ "rules": "required|string|max:20" } ] -} +} \ No newline at end of file diff --git a/database/seeds/eggs/source-engine/egg-custom-source-engine-game.json b/database/seeds/eggs/source-engine/egg-custom-source-engine-game.json index 7a7b40bd..aae3b3f3 100644 --- a/database/seeds/eggs/source-engine/egg-custom-source-engine-game.json +++ b/database/seeds/eggs/source-engine/egg-custom-source-engine-game.json @@ -3,7 +3,7 @@ "meta": { "version": "PTDL_v1" }, - "exported_at": "2018-01-21T16:59:45-06:00", + "exported_at": "2019-12-08T10:54:26-05:00", "name": "Custom Source Engine Game", "author": "support@pterodactyl.io", "description": "This option allows modifying the startup arguments and other details to run a custom SRCDS based game on the panel.", @@ -17,8 +17,8 @@ }, "scripts": { "installation": { - "script": "#!\/bin\/bash\n# SRCDS Base Installation Script\n#\n# Server Files: \/mnt\/server\napt -y update\napt -y --no-install-recommends install curl lib32gcc1 ca-certificates\n\ncd \/tmp\ncurl -sSL -o steamcmd.tar.gz http:\/\/media.steampowered.com\/installer\/steamcmd_linux.tar.gz\n\nmkdir -p \/mnt\/server\/steamcmd\ntar -xzvf steamcmd.tar.gz -C \/mnt\/server\/steamcmd\ncd \/mnt\/server\/steamcmd\n\n# SteamCMD fails otherwise for some reason, even running as root.\n# This is changed at the end of the install process anyways.\nchown -R root:root \/mnt\n\nexport HOME=\/mnt\/server\n.\/steamcmd.sh +login anonymous +force_install_dir \/mnt\/server +app_update ${SRCDS_APPID} +quit\n\nmkdir -p \/mnt\/server\/.steam\/sdk32\ncp -v linux32\/steamclient.so ..\/.steam\/sdk32\/steamclient.so", - "container": "ubuntu:16.04", + "script": "#!\/bin\/bash\r\n# steamcmd Base Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\n# Image to install with is 'ubuntu:18.04'\r\napt -y update\r\napt -y --no-install-recommends install curl lib32gcc1 ca-certificates\r\n\r\n## just in case someone removed the defaults.\r\nif [ \"${STEAM_USER}\" == \"\" ]; then\r\n STEAM_USER=anonymous\r\n STEAM_PASS=\"\"\r\n STEAM_AUTH=\"\"\r\nfi\r\n\r\n## download and install steamcmd\r\ncd \/tmp\r\nmkdir -p \/mnt\/server\/steamcmd\r\ncurl -sSL -o steamcmd.tar.gz https:\/\/steamcdn-a.akamaihd.net\/client\/installer\/steamcmd_linux.tar.gz\r\ntar -xzvf steamcmd.tar.gz -C \/mnt\/server\/steamcmd\r\ncd \/mnt\/server\/steamcmd\r\n\r\n# SteamCMD fails otherwise for some reason, even running as root.\r\n# This is changed at the end of the install process anyways.\r\nchown -R root:root \/mnt\r\nexport HOME=\/mnt\/server\r\n\r\n## install game using steamcmd\r\n.\/steamcmd.sh +login ${STEAM_USER} ${STEAM_PASS} ${STEAM_AUTH} +force_install_dir \/mnt\/server +app_update ${SRCDS_APPID} ${EXTRA_FLAGS} +quit ## other flags may be needed depending on install. looking at you cs 1.6\r\n\r\n## set up 32 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk32\r\ncp -v linux32\/steamclient.so ..\/.steam\/sdk32\/steamclient.so\r\n\r\n## set up 64 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk64\r\ncp -v linux64\/steamclient.so ..\/.steam\/sdk64\/steamclient.so", + "container": "ubuntu:18.04", "entrypoint": "bash" } }, @@ -51,4 +51,4 @@ "rules": "required|string|alpha_dash" } ] -} +} \ No newline at end of file diff --git a/database/seeds/eggs/source-engine/egg-garrys-mod.json b/database/seeds/eggs/source-engine/egg-garrys-mod.json index 3b134b0d..17940e8f 100644 --- a/database/seeds/eggs/source-engine/egg-garrys-mod.json +++ b/database/seeds/eggs/source-engine/egg-garrys-mod.json @@ -3,7 +3,7 @@ "meta": { "version": "PTDL_v1" }, - "exported_at": "2019-02-16T14:20:52-05:00", + "exported_at": "2019-12-08T10:56:42-05:00", "name": "Garrys Mod", "author": "support@pterodactyl.io", "description": "Garrys Mod, is a sandbox physics game created by Garry Newman, and developed by his company, Facepunch Studios.", @@ -17,8 +17,8 @@ }, "scripts": { "installation": { - "script": "#!\/bin\/bash\r\n# Garry's Mod Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\napt -y update\r\napt -y --no-install-recommends install curl lib32gcc1 ca-certificates\r\n\r\ncd \/tmp\r\ncurl -sSL -o steamcmd.tar.gz http:\/\/media.steampowered.com\/installer\/steamcmd_linux.tar.gz\r\n\r\nmkdir -p \/mnt\/server\/steamcmd\r\ntar -xzvf steamcmd.tar.gz -C \/mnt\/server\/steamcmd\r\ncd \/mnt\/server\/steamcmd\r\n\r\n# SteamCMD fails otherwise for some reason, even running as root.\r\n# This is changed at the end of the install process anyways.\r\nchown -R root:root \/mnt\r\n\r\nexport HOME=\/mnt\/server\r\n.\/steamcmd.sh +login anonymous +force_install_dir \/mnt\/server +app_update 4020 +quit\r\n\r\nmkdir -p \/mnt\/server\/.steam\/sdk32\r\ncp -v linux32\/steamclient.so ..\/.steam\/sdk32\/steamclient.so\r\n\r\n# Creating needed default files for the game\r\ncd \/mnt\/server\/garrysmod\/lua\/autorun\/server\r\necho '\r\n-- Docs: https:\/\/wiki.garrysmod.com\/page\/resource\/AddWorkshop\r\n-- Place the ID of the workshop addon you want to be downloaded to people who join your server, not the collection ID\r\n-- Use https:\/\/beta.configcreator.com\/create\/gmod\/resources.lua to easily create a list based on your collection ID\r\n\r\nresource.AddWorkshop( \"\" )\r\n' > workshop.lua\r\n\r\ncd \/mnt\/server\/garrysmod\/cfg\r\necho '\r\n\/\/ Please do not set RCon in here, use the startup parameters.\r\n\r\nhostname\t\t\"New Gmod Server\"\r\nsv_password\t\t\"\"\r\nsv_loadingurl \"\"\r\n\r\n\/\/ Steam Server List Settings\r\nsv_region \"255\"\r\nsv_lan \"0\"\r\nsv_max_queries_sec_global \"30000\"\r\nsv_max_queries_window \"45\"\r\nsv_max_queries_sec \"5\"\r\n\r\n\/\/ Server Limits\r\nsbox_maxprops\t\t100\r\nsbox_maxragdolls\t5\r\nsbox_maxnpcs\t\t10\r\nsbox_maxballoons\t10\r\nsbox_maxeffects\t\t10\r\nsbox_maxdynamite\t10\r\nsbox_maxlamps\t\t10\r\nsbox_maxthrusters\t10\r\nsbox_maxwheels\t\t10\r\nsbox_maxhoverballs\t10\r\nsbox_maxvehicles\t20\r\nsbox_maxbuttons\t\t10\r\nsbox_maxsents\t\t20\r\nsbox_maxemitters\t5\r\nsbox_godmode\t\t0\r\nsbox_noclip\t\t 0\r\n\r\n\/\/ Network Settings - Please keep these set to default.\r\n\r\nsv_minrate\t\t75000\r\nsv_maxrate\t\t0\r\ngmod_physiterations\t2\r\nnet_splitpacket_maxrate\t45000\r\ndecalfrequency\t\t12 \r\n\r\n\/\/ Execute Ban Files - Please do not edit\r\nexec banned_ip.cfg \r\nexec banned_user.cfg \r\n\r\n\/\/ Add custom lines under here\r\n' > server.cfg", - "container": "ubuntu:16.04", + "script": "#!\/bin\/bash\r\n# steamcmd Base Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\n# Image to install with is 'ubuntu:18.04'\r\napt -y update\r\napt -y --no-install-recommends install curl lib32gcc1 ca-certificates\r\n\r\n## just in case someone removed the defaults.\r\nif [ \"${STEAM_USER}\" == \"\" ]; then\r\n STEAM_USER=anonymous\r\n STEAM_PASS=\"\"\r\n STEAM_AUTH=\"\"\r\nfi\r\n\r\n## download and install steamcmd\r\ncd \/tmp\r\nmkdir -p \/mnt\/server\/steamcmd\r\ncurl -sSL -o steamcmd.tar.gz https:\/\/steamcdn-a.akamaihd.net\/client\/installer\/steamcmd_linux.tar.gz\r\ntar -xzvf steamcmd.tar.gz -C \/mnt\/server\/steamcmd\r\ncd \/mnt\/server\/steamcmd\r\n\r\n# SteamCMD fails otherwise for some reason, even running as root.\r\n# This is changed at the end of the install process anyways.\r\nchown -R root:root \/mnt\r\nexport HOME=\/mnt\/server\r\n\r\n## install game using steamcmd\r\n.\/steamcmd.sh +login ${STEAM_USER} ${STEAM_PASS} ${STEAM_AUTH} +force_install_dir \/mnt\/server +app_update ${SRCDS_APPID} ${EXTRA_FLAGS} +quit ## other flags may be needed depending on install. looking at you cs 1.6\r\n\r\n## set up 32 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk32\r\ncp -v linux32\/steamclient.so ..\/.steam\/sdk32\/steamclient.so\r\n\r\n## set up 64 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk64\r\ncp -v linux64\/steamclient.so ..\/.steam\/sdk64\/steamclient.so\r\n\r\n# Creating needed default files for the game\r\ncd \/mnt\/server\/garrysmod\/lua\/autorun\/server\r\necho '\r\n-- Docs: https:\/\/wiki.garrysmod.com\/page\/resource\/AddWorkshop\r\n-- Place the ID of the workshop addon you want to be downloaded to people who join your server, not the collection ID\r\n-- Use https:\/\/beta.configcreator.com\/create\/gmod\/resources.lua to easily create a list based on your collection ID\r\n\r\nresource.AddWorkshop( \"\" )\r\n' > workshop.lua\r\n\r\ncd \/mnt\/server\/garrysmod\/cfg\r\necho '\r\n\/\/ Please do not set RCon in here, use the startup parameters.\r\n\r\nhostname\t\t\"New Gmod Server\"\r\nsv_password\t\t\"\"\r\nsv_loadingurl \"\"\r\n\r\n\/\/ Steam Server List Settings\r\nsv_region \"255\"\r\nsv_lan \"0\"\r\nsv_max_queries_sec_global \"30000\"\r\nsv_max_queries_window \"45\"\r\nsv_max_queries_sec \"5\"\r\n\r\n\/\/ Server Limits\r\nsbox_maxprops\t\t100\r\nsbox_maxragdolls\t5\r\nsbox_maxnpcs\t\t10\r\nsbox_maxballoons\t10\r\nsbox_maxeffects\t\t10\r\nsbox_maxdynamite\t10\r\nsbox_maxlamps\t\t10\r\nsbox_maxthrusters\t10\r\nsbox_maxwheels\t\t10\r\nsbox_maxhoverballs\t10\r\nsbox_maxvehicles\t20\r\nsbox_maxbuttons\t\t10\r\nsbox_maxsents\t\t20\r\nsbox_maxemitters\t5\r\nsbox_godmode\t\t0\r\nsbox_noclip\t\t 0\r\n\r\n\/\/ Network Settings - Please keep these set to default.\r\n\r\nsv_minrate\t\t75000\r\nsv_maxrate\t\t0\r\ngmod_physiterations\t2\r\nnet_splitpacket_maxrate\t45000\r\ndecalfrequency\t\t12 \r\n\r\n\/\/ Execute Ban Files - Please do not edit\r\nexec banned_ip.cfg \r\nexec banned_user.cfg \r\n\r\n\/\/ Add custom lines under here\r\n' > server.cfg", + "container": "ubuntu:18.04", "entrypoint": "bash" } }, @@ -87,4 +87,4 @@ "rules": "required|integer|max:100" } ] -} +} \ No newline at end of file diff --git a/database/seeds/eggs/source-engine/egg-insurgency.json b/database/seeds/eggs/source-engine/egg-insurgency.json index 950743fd..7f0c76be 100644 --- a/database/seeds/eggs/source-engine/egg-insurgency.json +++ b/database/seeds/eggs/source-engine/egg-insurgency.json @@ -3,7 +3,7 @@ "meta": { "version": "PTDL_v1" }, - "exported_at": "2018-01-21T16:59:48-06:00", + "exported_at": "2019-12-08T10:57:32-05:00", "name": "Insurgency", "author": "support@pterodactyl.io", "description": "Take to the streets for intense close quarters combat, where a team's survival depends upon securing crucial strongholds and destroying enemy supply in this multiplayer and cooperative Source Engine based experience.", @@ -17,8 +17,8 @@ }, "scripts": { "installation": { - "script": "#!\/bin\/bash\n# SRCDS Base Installation Script\n#\n# Server Files: \/mnt\/server\napt -y update\napt -y --no-install-recommends install curl lib32gcc1 ca-certificates\n\ncd \/tmp\ncurl -sSL -o steamcmd.tar.gz http:\/\/media.steampowered.com\/installer\/steamcmd_linux.tar.gz\n\nmkdir -p \/mnt\/server\/steamcmd\ntar -xzvf steamcmd.tar.gz -C \/mnt\/server\/steamcmd\ncd \/mnt\/server\/steamcmd\n\n# SteamCMD fails otherwise for some reason, even running as root.\n# This is changed at the end of the install process anyways.\nchown -R root:root \/mnt\n\nexport HOME=\/mnt\/server\n.\/steamcmd.sh +login anonymous +force_install_dir \/mnt\/server +app_update ${SRCDS_APPID} +quit\n\nmkdir -p \/mnt\/server\/.steam\/sdk32\ncp -v linux32\/steamclient.so ..\/.steam\/sdk32\/steamclient.so", - "container": "ubuntu:16.04", + "script": "#!\/bin\/bash\r\n# steamcmd Base Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\n# Image to install with is 'ubuntu:18.04'\r\napt -y update\r\napt -y --no-install-recommends install curl lib32gcc1 ca-certificates\r\n\r\n## just in case someone removed the defaults.\r\nif [ \"${STEAM_USER}\" == \"\" ]; then\r\n STEAM_USER=anonymous\r\n STEAM_PASS=\"\"\r\n STEAM_AUTH=\"\"\r\nfi\r\n\r\n## download and install steamcmd\r\ncd \/tmp\r\nmkdir -p \/mnt\/server\/steamcmd\r\ncurl -sSL -o steamcmd.tar.gz https:\/\/steamcdn-a.akamaihd.net\/client\/installer\/steamcmd_linux.tar.gz\r\ntar -xzvf steamcmd.tar.gz -C \/mnt\/server\/steamcmd\r\ncd \/mnt\/server\/steamcmd\r\n\r\n# SteamCMD fails otherwise for some reason, even running as root.\r\n# This is changed at the end of the install process anyways.\r\nchown -R root:root \/mnt\r\nexport HOME=\/mnt\/server\r\n\r\n## install game using steamcmd\r\n.\/steamcmd.sh +login ${STEAM_USER} ${STEAM_PASS} ${STEAM_AUTH} +force_install_dir \/mnt\/server +app_update ${SRCDS_APPID} ${EXTRA_FLAGS} +quit ## other flags may be needed depending on install. looking at you cs 1.6\r\n\r\n## set up 32 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk32\r\ncp -v linux32\/steamclient.so ..\/.steam\/sdk32\/steamclient.so\r\n\r\n## set up 64 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk64\r\ncp -v linux64\/steamclient.so ..\/.steam\/sdk64\/steamclient.so", + "container": "ubuntu:18.04", "entrypoint": "bash" } }, @@ -51,4 +51,4 @@ "rules": "required|regex:\/^(\\w{1,20})$\/" } ] -} +} \ No newline at end of file diff --git a/database/seeds/eggs/source-engine/egg-team-fortress2.json b/database/seeds/eggs/source-engine/egg-team-fortress2.json index ae443370..159e7bf9 100644 --- a/database/seeds/eggs/source-engine/egg-team-fortress2.json +++ b/database/seeds/eggs/source-engine/egg-team-fortress2.json @@ -3,7 +3,7 @@ "meta": { "version": "PTDL_v1" }, - "exported_at": "2018-01-21T16:59:45-06:00", + "exported_at": "2019-12-08T10:58:48-05:00", "name": "Team Fortress 2", "author": "support@pterodactyl.io", "description": "Team Fortress 2 is a team-based first-person shooter multiplayer video game developed and published by Valve Corporation. It is the sequel to the 1996 mod Team Fortress for Quake and its 1999 remake.", @@ -17,8 +17,8 @@ }, "scripts": { "installation": { - "script": "#!\/bin\/bash\n# SRCDS Base Installation Script\n#\n# Server Files: \/mnt\/server\napt -y update\napt -y --no-install-recommends install curl lib32gcc1 ca-certificates\n\ncd \/tmp\ncurl -sSL -o steamcmd.tar.gz http:\/\/media.steampowered.com\/installer\/steamcmd_linux.tar.gz\n\nmkdir -p \/mnt\/server\/steamcmd\ntar -xzvf steamcmd.tar.gz -C \/mnt\/server\/steamcmd\ncd \/mnt\/server\/steamcmd\n\n# SteamCMD fails otherwise for some reason, even running as root.\n# This is changed at the end of the install process anyways.\nchown -R root:root \/mnt\n\nexport HOME=\/mnt\/server\n.\/steamcmd.sh +login anonymous +force_install_dir \/mnt\/server +app_update ${SRCDS_APPID} +quit\n\nmkdir -p \/mnt\/server\/.steam\/sdk32\ncp -v linux32\/steamclient.so ..\/.steam\/sdk32\/steamclient.so", - "container": "ubuntu:16.04", + "script": "#!\/bin\/bash\r\n# steamcmd Base Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\n# Image to install with is 'ubuntu:18.04'\r\napt -y update\r\napt -y --no-install-recommends install curl lib32gcc1 ca-certificates\r\n\r\n## just in case someone removed the defaults.\r\nif [ \"${STEAM_USER}\" == \"\" ]; then\r\n STEAM_USER=anonymous\r\n STEAM_PASS=\"\"\r\n STEAM_AUTH=\"\"\r\nfi\r\n\r\n## download and install steamcmd\r\ncd \/tmp\r\nmkdir -p \/mnt\/server\/steamcmd\r\ncurl -sSL -o steamcmd.tar.gz https:\/\/steamcdn-a.akamaihd.net\/client\/installer\/steamcmd_linux.tar.gz\r\ntar -xzvf steamcmd.tar.gz -C \/mnt\/server\/steamcmd\r\ncd \/mnt\/server\/steamcmd\r\n\r\n# SteamCMD fails otherwise for some reason, even running as root.\r\n# This is changed at the end of the install process anyways.\r\nchown -R root:root \/mnt\r\nexport HOME=\/mnt\/server\r\n\r\n## install game using steamcmd\r\n.\/steamcmd.sh +login ${STEAM_USER} ${STEAM_PASS} ${STEAM_AUTH} +force_install_dir \/mnt\/server +app_update ${SRCDS_APPID} ${EXTRA_FLAGS} +quit ## other flags may be needed depending on install. looking at you cs 1.6\r\n\r\n## set up 32 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk32\r\ncp -v linux32\/steamclient.so ..\/.steam\/sdk32\/steamclient.so\r\n\r\n## set up 64 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk64\r\ncp -v linux64\/steamclient.so ..\/.steam\/sdk64\/steamclient.so", + "container": "ubuntu:18.04", "entrypoint": "bash" } }, diff --git a/database/seeds/eggs/voice-servers/egg-mumble-server.json b/database/seeds/eggs/voice-servers/egg-mumble-server.json index e6256259..00c87d21 100644 --- a/database/seeds/eggs/voice-servers/egg-mumble-server.json +++ b/database/seeds/eggs/voice-servers/egg-mumble-server.json @@ -36,7 +36,7 @@ "name": "Server Version", "description": "Version of Mumble Server to download and use.", "env_variable": "MUMBLE_VERSION", - "default_value": "1.2.19", + "default_value": "1.3.1", "user_viewable": 1, "user_editable": 1, "rules": "required|regex:\/^([0-9_\\.-]{5,8})$\/" diff --git a/docker-compose.example.yml b/docker-compose.example.yml index eb1bfadd..c69cce56 100644 --- a/docker-compose.example.yml +++ b/docker-compose.example.yml @@ -1,7 +1,8 @@ version: '2' services: database: - image: mariadb + image: mariadb:10.4 + restart: always volumes: - "/srv/pterodactyl/database:/var/lib/mysql" environment: @@ -14,9 +15,11 @@ services: cache: image: redis:alpine + restart: always panel: image: quay.io/pterodactyl/panel:latest + restart: always ports: - "80:80" - "443:443" @@ -68,7 +71,7 @@ services: - "MAIL_PASSWORD=''" - "MAIL_ENCRYPTION=true" ## certbot settings - Used to automatically generate ssl certs and - - "LE_EMAIL=''" ## leave blank unless you aree generating certs. + # - "LE_EMAIL=" ## uncomment if you are using ssl networks: default: diff --git a/.dev/docker/README.md b/docker/README.md similarity index 100% rename from .dev/docker/README.md rename to docker/README.md diff --git a/.dev/docker/default.conf b/docker/default.conf similarity index 96% rename from .dev/docker/default.conf rename to docker/default.conf index 0944bf79..b6105e5f 100644 --- a/.dev/docker/default.conf +++ b/docker/default.conf @@ -31,7 +31,7 @@ server { location ~ \.php$ { fastcgi_split_path_info ^(.+\.php)(/.+)$; # the fastcgi_pass path needs to be changed accordingly when using CentOS - fastcgi_pass unix:/var/run/php/php-fpm7.2.sock; + 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"; diff --git a/.dev/docker/default_ssl.conf b/docker/default_ssl.conf similarity index 96% rename from .dev/docker/default_ssl.conf rename to docker/default_ssl.conf index c2c2b6df..9ec5c10d 100644 --- a/.dev/docker/default_ssl.conf +++ b/docker/default_ssl.conf @@ -1,4 +1,4 @@ -# If using Ubuntu this file should be placed in: +# If using Ubuntu this file should be placed in: # /etc/nginx/sites-available/ # server { @@ -49,7 +49,7 @@ server { location ~ \.php$ { fastcgi_split_path_info ^(.+\.php)(/.+)$; - fastcgi_pass unix:/var/run/php/php-fpm7.2.sock; + 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"; diff --git a/.dev/docker/entrypoint.sh b/docker/entrypoint.sh similarity index 75% rename from .dev/docker/entrypoint.sh rename to docker/entrypoint.sh index c2b58375..84cf1957 100644 --- a/.dev/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -4,19 +4,19 @@ cd /app mkdir -p /var/log/panel/logs/ /var/log/supervisord/ /var/log/nginx/ /var/log/php7/ \ -&& rmdir /app/storage/logs/ \ +&& rm -rf /app/storage/logs/ \ && chmod 777 /var/log/panel/logs/ \ && ln -s /var/log/panel/logs/ /app/storage/ ## check for .env file and generate app keys if missing if [ -f /app/var/.env ]; then echo "external vars exist." - rm /app/.env + rm -rf /app/.env ln -s /app/var/.env /app/ else echo "external vars don't exist." - rm /app/.env + rm -rf /app/.env touch /app/var/.env ## manually generate a key because key generate --force fails @@ -31,15 +31,20 @@ fi echo "Checking if https is required." if [ -f /etc/nginx/conf.d/default.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 Failing to http." - cp .dev/docker/default.conf /etc/nginx/conf.d/default.conf - + echo "No letsencrypt email is set using http config." + cp docker/default.conf /etc/nginx/conf.d/default.conf else echo "writing ssl config" - cp .dev/docker/default_ssl.conf /etc/nginx/conf.d/default.conf + cp docker/default_ssl.conf /etc/nginx/conf.d/default.conf echo "updating ssl config for domain" sed -i "s||$(echo $APP_URL | sed 's~http[s]*://~~g')|g" /etc/nginx/conf.d/default.conf echo "generating certs" @@ -66,5 +71,10 @@ php artisan db:seed --force echo -e "Starting cron jobs." crond -L /var/log/crond -l 5 +## install yarn stuff +yarn install --production +yarn add cross-env +yarn run build:production + echo -e "Starting supervisord." exec "$@" \ No newline at end of file diff --git a/.dev/docker/supervisord.conf b/docker/supervisord.conf similarity index 91% rename from .dev/docker/supervisord.conf rename to docker/supervisord.conf index f2fd3a1b..da6823ae 100644 --- a/.dev/docker/supervisord.conf +++ b/docker/supervisord.conf @@ -20,12 +20,12 @@ supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface serverurl=unix:///tmp/supervisor.sock ; use a unix:// URL for a unix socket [program:php-fpm] -command=/usr/sbin/php-fpm7 -F +command=/usr/local/sbin/php-fpm -F autostart=true autorestart=true [program:queue-worker] -command=/usr/bin/php /app/artisan queue:work --queue=high,standard,low --sleep=3 --tries=3 +command=/usr/local/bin/php /app/artisan queue:work --queue=high,standard,low --sleep=3 --tries=3 user=nginx autostart=true autorestart=true diff --git a/.dev/docker/www.conf b/docker/www.conf similarity index 78% rename from .dev/docker/www.conf rename to docker/www.conf index 88142564..c0c17903 100644 --- a/.dev/docker/www.conf +++ b/docker/www.conf @@ -1,9 +1,9 @@ -[pterodactyl] +[www] user = nginx group = nginx -listen = /var/run/php/php-fpm7.2.sock +listen = 127.0.0.1:9000 listen.owner = nginx listen.group = nginx listen.mode = 0750 diff --git a/package.json b/package.json index 2a969740..15e96c4b 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,120 @@ { - "name": "pterodactyl-panel", - "devDependencies": { - "babel-cli": "6.18.0", - "babel-plugin-transform-strict-mode": "^6.18.0", - "babel-preset-es2015": "6.18.0" - }, - "scripts": { - "build": "./node_modules/babel-cli/bin/babel.js public/themes/pterodactyl/js/frontend/files/src --source-maps --out-file public/themes/pterodactyl/js/frontend/files/filemanager.min.js" - } + "name": "pterodactyl-panel", + "dependencies": { + "@fortawesome/fontawesome-svg-core": "1.2.19", + "@fortawesome/free-solid-svg-icons": "^5.9.0", + "@fortawesome/react-fontawesome": "0.1.4", + "axios": "^0.19.2", + "chart.js": "^2.8.0", + "codemirror": "^5.57.0", + "date-fns": "^2.14.0", + "debounce": "^1.2.0", + "deepmerge": "^4.2.2", + "easy-peasy": "^3.3.1", + "events": "^3.0.0", + "formik": "^2.1.4", + "i18next": "^19.0.0", + "i18next-chained-backend": "^2.0.0", + "i18next-localstorage-backend": "^3.0.0", + "i18next-xhr-backend": "^3.2.2", + "jquery": "^3.3.1", + "path": "^0.12.7", + "query-string": "^6.7.0", + "react": "^16.13.1", + "react-dom": "npm:@hot-loader/react-dom", + "react-fast-compare": "^3.2.0", + "react-google-recaptcha": "^2.0.1", + "react-helmet": "^6.1.0", + "react-ga": "^3.1.2", + "react-hot-loader": "^4.12.21", + "react-i18next": "^11.2.1", + "react-redux": "^7.1.0", + "react-router-dom": "^5.1.2", + "react-transition-group": "^4.4.1", + "reaptcha": "^1.7.2", + "sockette": "^2.0.6", + "styled-components": "^5.1.1", + "styled-components-breakpoint": "^3.0.0-preview.20", + "swr": "^0.2.3", + "uuid": "^3.3.2", + "xterm": "^3.14.4", + "xterm-addon-attach": "^0.1.0", + "xterm-addon-fit": "^0.1.0", + "yup": "^0.29.1" + }, + "devDependencies": { + "@babel/core": "^7.7.5", + "@babel/plugin-proposal-class-properties": "^7.7.4", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-proposal-object-rest-spread": "^7.7.4", + "@babel/plugin-proposal-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-dynamic-import": "^7.7.4", + "@babel/plugin-transform-react-jsx": "^7.10.4", + "@babel/plugin-transform-runtime": "^7.7.5", + "@babel/preset-env": "^7.7.5", + "@babel/preset-react": "^7.7.4", + "@babel/preset-typescript": "^7.7.4", + "@babel/runtime": "^7.7.5", + "@types/chart.js": "^2.8.5", + "@types/codemirror": "^0.0.98", + "@types/debounce": "^1.2.0", + "@types/events": "^3.0.0", + "@types/node": "^12.6.9", + "@types/query-string": "^6.3.0", + "@types/react": "^16.9.41", + "@types/react-dom": "^16.9.8", + "@types/react-helmet": "^6.0.0", + "@types/react-redux": "^7.1.1", + "@types/react-router": "^5.1.3", + "@types/react-router-dom": "^5.1.3", + "@types/react-transition-group": "^4.4.0", + "@types/styled-components": "^5.1.0", + "@types/uuid": "^3.4.5", + "@types/webpack-env": "^1.15.2", + "@types/yup": "^0.29.3", + "@typescript-eslint/eslint-plugin": "^3.5.0", + "@typescript-eslint/parser": "^3.5.0", + "babel-loader": "^8.0.6", + "babel-plugin-styled-components": "^1.10.7", + "cross-env": "^7.0.2", + "css-loader": "^3.2.1", + "eslint": "^7.4.0", + "eslint-config-standard": "^14.1.1", + "eslint-plugin-import": "^2.22.0", + "eslint-plugin-node": "^9.1.0", + "eslint-plugin-promise": "^4.2.1", + "eslint-plugin-react": "^7.20.3", + "eslint-plugin-react-hooks": "^4.0.5", + "eslint-plugin-standard": "^4.0.1", + "fork-ts-checker-webpack-plugin": "^5.0.6", + "redux-devtools-extension": "^2.13.8", + "source-map-loader": "^1.0.1", + "style-loader": "^1.2.1", + "svg-url-loader": "^6.0.0", + "tailwindcss": "^1.4.6", + "terser-webpack-plugin": "^3.0.6", + "twin.macro": "^1.4.1", + "typescript": "^3.9.6", + "typescript-plugin-tw-template": "^2.0.1", + "webpack": "^4.43.0", + "webpack-assets-manifest": "^3.1.1", + "webpack-bundle-analyzer": "^3.8.0", + "webpack-cli": "^3.3.12", + "webpack-dev-server": "^3.11.0", + "yarn-deduplicate": "^1.1.1" + }, + "scripts": { + "clean": "cd public/assets && find . \\( -name \"*.js\" -o -name \"*.map\" \\) -type f -delete", + "lint": "eslint ./resources/scripts/**/*.{ts,tsx} --ext .ts,.tsx", + "watch": "cross-env NODE_ENV=development ./node_modules/.bin/webpack --watch --progress", + "build": "cross-env NODE_ENV=development ./node_modules/.bin/webpack --progress", + "build:production": "yarn run clean && cross-env NODE_ENV=production ./node_modules/.bin/webpack --mode production", + "serve": "yarn run clean && cross-env PUBLIC_PATH=https://pterodactyl.test:8080 NODE_ENV=development TSC_WATCHFILE=UseFsEventsWithFallbackDynamicPolling webpack-dev-server --host 0.0.0.0 --hot --https --key /etc/ssl/private/pterodactyl.test-key.pem --cert /etc/ssl/private/pterodactyl.test.pem" + }, + "browserslist": [ + "> 0.5%", + "last 2 versions", + "firefox esr", + "not dead" + ] } diff --git a/phpunit.dusk.xml b/phpunit.dusk.xml new file mode 100644 index 00000000..60392c93 --- /dev/null +++ b/phpunit.dusk.xml @@ -0,0 +1,21 @@ + + + + + ./tests/Browser + + + + + ./app + + + diff --git a/phpunit.xml b/phpunit.xml index c0a7cf83..e4e18e41 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -10,6 +10,9 @@ processIsolation="false" stopOnFailure="false"> + + ./tests/Browser/Processes + ./tests/Integration diff --git a/public/.gitignore b/public/.gitignore new file mode 100644 index 00000000..31c19f1c --- /dev/null +++ b/public/.gitignore @@ -0,0 +1,3 @@ +assets/* +!assets/svgs +!assets/svgs/*.svg diff --git a/public/assets/svgs/not_found.svg b/public/assets/svgs/not_found.svg new file mode 100644 index 00000000..222a4152 --- /dev/null +++ b/public/assets/svgs/not_found.svg @@ -0,0 +1 @@ +not found \ No newline at end of file diff --git a/public/assets/svgs/pterodactyl.svg b/public/assets/svgs/pterodactyl.svg new file mode 100755 index 00000000..f3582adf --- /dev/null +++ b/public/assets/svgs/pterodactyl.svg @@ -0,0 +1 @@ +Artboard 1 \ No newline at end of file diff --git a/public/assets/svgs/server_error.svg b/public/assets/svgs/server_error.svg new file mode 100644 index 00000000..726fa106 --- /dev/null +++ b/public/assets/svgs/server_error.svg @@ -0,0 +1 @@ +server down \ No newline at end of file diff --git a/public/assets/svgs/server_installing.svg b/public/assets/svgs/server_installing.svg new file mode 100644 index 00000000..d2a0ae48 --- /dev/null +++ b/public/assets/svgs/server_installing.svg @@ -0,0 +1 @@ +uploading \ No newline at end of file diff --git a/public/favicons/android-icon-144x144.png b/public/favicons/android-icon-144x144.png new file mode 100644 index 00000000..7e98c1c3 Binary files /dev/null and b/public/favicons/android-icon-144x144.png differ diff --git a/public/favicons/android-icon-192x192.png b/public/favicons/android-icon-192x192.png new file mode 100644 index 00000000..80ce4d33 Binary files /dev/null and b/public/favicons/android-icon-192x192.png differ diff --git a/public/favicons/android-icon-36x36.png b/public/favicons/android-icon-36x36.png new file mode 100644 index 00000000..729e2126 Binary files /dev/null and b/public/favicons/android-icon-36x36.png differ diff --git a/public/favicons/android-icon-48x48.png b/public/favicons/android-icon-48x48.png new file mode 100644 index 00000000..9e5fe265 Binary files /dev/null and b/public/favicons/android-icon-48x48.png differ diff --git a/public/favicons/android-icon-72x72.png b/public/favicons/android-icon-72x72.png new file mode 100644 index 00000000..e6bb769c Binary files /dev/null and b/public/favicons/android-icon-72x72.png differ diff --git a/public/favicons/android-icon-96x96.png b/public/favicons/android-icon-96x96.png new file mode 100644 index 00000000..f9b8fbef Binary files /dev/null and b/public/favicons/android-icon-96x96.png differ diff --git a/public/favicons/apple-icon-114x114.png b/public/favicons/apple-icon-114x114.png new file mode 100644 index 00000000..3920011b Binary files /dev/null and b/public/favicons/apple-icon-114x114.png differ diff --git a/public/favicons/apple-icon-120x120.png b/public/favicons/apple-icon-120x120.png new file mode 100644 index 00000000..81b72ba8 Binary files /dev/null and b/public/favicons/apple-icon-120x120.png differ diff --git a/public/favicons/apple-icon-144x144.png b/public/favicons/apple-icon-144x144.png new file mode 100644 index 00000000..7e98c1c3 Binary files /dev/null and b/public/favicons/apple-icon-144x144.png differ diff --git a/public/favicons/apple-icon-152x152.png b/public/favicons/apple-icon-152x152.png new file mode 100644 index 00000000..6623bd58 Binary files /dev/null and b/public/favicons/apple-icon-152x152.png differ diff --git a/public/favicons/apple-icon-180x180.png b/public/favicons/apple-icon-180x180.png new file mode 100644 index 00000000..0503d522 Binary files /dev/null and b/public/favicons/apple-icon-180x180.png differ diff --git a/public/favicons/apple-icon-57x57.png b/public/favicons/apple-icon-57x57.png new file mode 100644 index 00000000..466878ee Binary files /dev/null and b/public/favicons/apple-icon-57x57.png differ diff --git a/public/favicons/apple-icon-60x60.png b/public/favicons/apple-icon-60x60.png new file mode 100644 index 00000000..09a79d99 Binary files /dev/null and b/public/favicons/apple-icon-60x60.png differ diff --git a/public/favicons/apple-icon-72x72.png b/public/favicons/apple-icon-72x72.png new file mode 100644 index 00000000..e6bb769c Binary files /dev/null and b/public/favicons/apple-icon-72x72.png differ diff --git a/public/favicons/apple-icon-76x76.png b/public/favicons/apple-icon-76x76.png new file mode 100644 index 00000000..f656479b Binary files /dev/null and b/public/favicons/apple-icon-76x76.png differ diff --git a/public/favicons/apple-icon-precomposed.png b/public/favicons/apple-icon-precomposed.png new file mode 100644 index 00000000..277e51bf Binary files /dev/null and b/public/favicons/apple-icon-precomposed.png differ diff --git a/public/favicons/apple-icon.png b/public/favicons/apple-icon.png new file mode 100644 index 00000000..277e51bf Binary files /dev/null and b/public/favicons/apple-icon.png differ diff --git a/public/favicons/browserconfig.xml b/public/favicons/browserconfig.xml index e3cb776e..c5541482 100644 --- a/public/favicons/browserconfig.xml +++ b/public/favicons/browserconfig.xml @@ -1,9 +1,2 @@ - - - - - #165ed4 - - - +#ffffff \ No newline at end of file diff --git a/public/favicons/df4b367461890fa5fd0d9339d3c3f9c6.ico.zip b/public/favicons/df4b367461890fa5fd0d9339d3c3f9c6.ico.zip new file mode 100644 index 00000000..8d2efae0 Binary files /dev/null and b/public/favicons/df4b367461890fa5fd0d9339d3c3f9c6.ico.zip differ diff --git a/public/favicons/favicon-16x16.png b/public/favicons/favicon-16x16.png index d568bd20..9597c6f3 100644 Binary files a/public/favicons/favicon-16x16.png and b/public/favicons/favicon-16x16.png differ diff --git a/public/favicons/favicon-32x32.png b/public/favicons/favicon-32x32.png index edfd1340..737917e1 100644 Binary files a/public/favicons/favicon-32x32.png and b/public/favicons/favicon-32x32.png differ diff --git a/public/favicons/favicon-96x96.png b/public/favicons/favicon-96x96.png new file mode 100644 index 00000000..f9b8fbef Binary files /dev/null and b/public/favicons/favicon-96x96.png differ diff --git a/public/favicons/favicon.ico b/public/favicons/favicon.ico index 99e5bffe..bc222883 100644 Binary files a/public/favicons/favicon.ico and b/public/favicons/favicon.ico differ diff --git a/public/favicons/manifest.json b/public/favicons/manifest.json index 796d1ff0..013d4a6a 100644 --- a/public/favicons/manifest.json +++ b/public/favicons/manifest.json @@ -1,18 +1,41 @@ { - "name": "", - "icons": [ - { - "src": "favicons/android-chrome-192x192.png", - "sizes": "192x192", - "type": "image/png" - }, - { - "src": "favicons/android-chrome-512x512.png", - "sizes": "512x512", - "type": "image/png" - } - ], - "theme_color": "#ffffff", - "background_color": "#ffffff", - "display": "standalone" + "name": "App", + "icons": [ + { + "src": "\/android-icon-36x36.png", + "sizes": "36x36", + "type": "image\/png", + "density": "0.75" + }, + { + "src": "\/android-icon-48x48.png", + "sizes": "48x48", + "type": "image\/png", + "density": "1.0" + }, + { + "src": "\/android-icon-72x72.png", + "sizes": "72x72", + "type": "image\/png", + "density": "1.5" + }, + { + "src": "\/android-icon-96x96.png", + "sizes": "96x96", + "type": "image\/png", + "density": "2.0" + }, + { + "src": "\/android-icon-144x144.png", + "sizes": "144x144", + "type": "image\/png", + "density": "3.0" + }, + { + "src": "\/android-icon-192x192.png", + "sizes": "192x192", + "type": "image\/png", + "density": "4.0" + } + ] } \ No newline at end of file diff --git a/public/favicons/ms-icon-144x144.png b/public/favicons/ms-icon-144x144.png new file mode 100644 index 00000000..7e98c1c3 Binary files /dev/null and b/public/favicons/ms-icon-144x144.png differ diff --git a/public/favicons/ms-icon-150x150.png b/public/favicons/ms-icon-150x150.png new file mode 100644 index 00000000..cdd392c8 Binary files /dev/null and b/public/favicons/ms-icon-150x150.png differ diff --git a/public/favicons/ms-icon-310x310.png b/public/favicons/ms-icon-310x310.png new file mode 100644 index 00000000..7b84fc12 Binary files /dev/null and b/public/favicons/ms-icon-310x310.png differ diff --git a/public/favicons/ms-icon-70x70.png b/public/favicons/ms-icon-70x70.png new file mode 100644 index 00000000..383bb767 Binary files /dev/null and b/public/favicons/ms-icon-70x70.png differ diff --git a/public/themes/pterodactyl/css/pterodactyl.css b/public/themes/pterodactyl/css/pterodactyl.css index a4e4c4cf..8e3580f2 100644 --- a/public/themes/pterodactyl/css/pterodactyl.css +++ b/public/themes/pterodactyl/css/pterodactyl.css @@ -331,6 +331,12 @@ span[aria-labelledby="select2-pUserId-container"] { background: transparent !important; } +.callout code { + background-color: #515f6cbb; + color: #c3c3c3; + border: 1px solid rgba(0, 0, 0, .25); +} + .tab-pane .box-footer { margin: 0 -10px -10px; } @@ -482,3 +488,348 @@ label.control-label > span.field-optional:before { padding: 15px 10px 0; } + +/* ******* + + > Version v1.0 + +******* */ + +body { + color: #cad1d8; +} + +.fixed .main-header { + box-shadow: 0 4px 8px 0 rgba(0,0,0,.12), 0 2px 4px 0 rgba(0,0,0,.08); +} + +.skin-blue .wrapper, .skin-blue .main-sidebar, .skin-blue .left-side { + background-color: #181f27; + box-shadow: 0 4px 8px 0 rgba(0,0,0,.12), 0 2px 4px 0 rgba(0,0,0,.08); +} + +.skin-blue .main-header .logo { + background-color: #1f2933; + color: #9aa5b1; +} + +.skin-blue .main-header .navbar .sidebar-toggle { + color: #9aa5b1; +} + +.skin-blue .main-header .navbar .nav>li>a { + color: #9aa5b1; +} + +.skin-blue .sidebar-menu>li.header { + color: #797979; + background: #0e111582; +} + +.skin-blue .main-header .navbar { + background-color: #1f2933; +} + +.skin-blue .main-header .navbar .sidebar-toggle:hover { + background-color: #1c252e; +} + +.skin-blue .main-header .logo:hover { + background-color: #1c252e; +} + +.main-footer { + background: #1f2933; + color: #9aa5b1; + border-top: 1px solid #1f2933; +} + +.skin-blue .sidebar-menu>li.active>a { + border-left-color: #099aa5; +} + +.text-gray { + color: #9aa5b1 !important; +} + +.text-green { + color: #00a65a !important; +} + +.text-muted { + color: #9aa5b1 !important; +} + +.text-danger { + color: #ff1c00; +} + +.content-wrapper { + background-color: #33404d; +} + +.btn-success { + background-color: #189a1c; + border-color: #0f8513; +} + +.btn.btn-green:hover { + background-color: #0f8513; + border-color: #0e7717; +} + +.btn-primary { + background-color: #0967d3; + border-color: #0550b3; +} + +.btn.btn-primary:hover { + background-color: #0550b3; + border-color: #0345a0; +} + +.box { + box-shadow: 0 4px 8px 0 rgba(0,0,0,.12), 0 2px 4px 0 rgba(0,0,0,.08) !important; + background: #3f4d5a; + border-top: 3px solid #1f2933; +} + +.box-header { + color: #cad1d8; + background: #1f2933; +} + +.box-header.with-border { + border-bottom: 1px solid #1f2933; +} + +.box.box-default { + border-top-color: #1f2933; +} + +.box-footer { + border-top: 1px solid #4d5b69; + background-color: #3f4d5a; +} +.content-header>.breadcrumb>li>a { + color: #cad1d8; +} + +.breadcrumb>.active { + color: #cad1d8; +} + +.h1 .small, .h1 small, .h2 .small, .h2 small, .h3 .small, .h3 small, .h4 .small, .h4 small, .h5 .small, .h5 small, .h6 .small, .h6 small, h1 .small, h1 small, h2 .small, h2 small, h3 .small, h3 small, h4 .small, h4 small, h5 .small, h5 small, h6 .small, h6 small { + color: #cad1d8; +} + +.table>thead>tr>th, .table>tbody>tr>th, .table>tfoot>tr>th, .table>thead>tr>td, .table>tbody>tr>td, .table>tfoot>tr>td { + border-top: 1px solid #4d5b69; +} + +.table>thead>tr>th { + border-bottom: 2px solid #4d5b69; +} + +.table-hover>tbody>tr:hover { + background-color: #33404d; +} + +a { + color: #007eff; +} + +.nav-tabs-custom { + background: #1f2933; +} + +.nav-tabs-custom>.nav-tabs>li.active { + border-top-color: #099aa5; +} + +.nav-tabs-custom>.nav-tabs>li.active>a { + border-top-color: transparent; + border-left-color: #15161c; + border-right-color: #15161c; + background: #13181e; +} + +.nav-tabs-custom>.nav-tabs>li>a { + color: #9aa5b1; +} + +.nav-tabs-custom>.nav-tabs>li.active>a, .nav-tabs-custom>.nav-tabs>li.active:hover>a { + color: #9aa5b1; +} + +input.form-control { + padding: .75rem; + background-color: #515f6cbb; + border-width: 1px; + border-color: #606d7b; + border-radius: .25rem; + color: #cad1d8; + box-shadow: none; + -webkit-transition: border .15s linear,box-shaodw .15s ease-in; + transition: border .15s linear,box-shaodw .15s ease-in; +} + +textarea.form-control { + padding: .75rem; + background-color: #515f6cbb; + border-width: 1px; + border-color: #606d7b; + border-radius: .25rem; + color: #cad1d8; + box-shadow: none; + -webkit-transition: border .15s linear,box-shaodw .15s ease-in; + transition: border .15s linear,box-shaodw .15s ease-in; +} + +.input-group .input-group-addon { + border-color: #606d7b; + background-color: #515f6cbb; + color: #cad1d8; +} + +.select2-container--default .select2-selection--single, .select2-selection .select2-selection--single { + border: 1px solid #606d7b; +} + +.select2-container--default .select2-selection--single { + background-color: #515f6cbb; +} + +.select2-container--default .select2-selection--single .select2-selection__rendered { + color: #cad1d8; +} + +.select2-container--default .select2-selection--multiple { + background-color: #515f6cbb; +} + +.select2-container--default .select2-selection--multiple { + border: 1px solid #606d7b; + border-radius: 0; +} + +code { + background-color: #515f6cbb; + color: #c3c3c3; + border: 1px solid rgba(0, 0, 0, .25); +} + +.btn-default { + background-color: #33404d; + color: #cad1d8; + border-color: #606d7b; +} + +.select2-results__option { + background-color: #b5bcc1; + color: #444; +} + +.select2-container--default .select2-results__option--highlighted[aria-selected] { + background-color: #3c8dbc; +} + +.modal-body { + background: #3f4d5a; +} + +.modal-header { + background: #3f4d5a; + border-bottom-color: #4d5b69; +} + +.modal-footer { + background: #3f4d5a; + border-top-color: #4d5b69; +} + +@media (max-width: 991px) { + .content-header>.breadcrumb { + background: #1f2933 !important; + } +} + +.nav-tabs-custom>.nav-tabs>li.active>a, .nav-tabs-custom>.nav-tabs>li.active:hover>a { + background-color: #101216; +} + +.select2-container--default .select2-results__option[aria-selected=true], .select2-container--default .select2-results__option[aria-selected=true]:hover { + color: #fff; +} + +.select2-dropdown { + background-color: #515f6cbb; + border: 1px solid #606d7b; +} +.select2-container--default.select2-container--focus .select2-selection--multiple, .select2-container--default .select2-search--dropdown .select2-search__field { + border-color: #d2d6de !important; + background-color: #515f6cbb; +} + +.select2-container--default .select2-results__option--highlighted[aria-selected] { + background-color: #099aa5; +} + +a { + color: #288afb; +} + +a:hover { + color: #206ec7; +} + +.form-control { + border-color: #606d7b; + background-color: #515f6cbb; + color: #cad1d8; +} + +.form-control[disabled], .form-control[readonly], +fieldset[disabled] .form-control { + background-color: #1f2933; + color: #cad1d8; + cursor: not-allowed; +} + +.well { + min-height: 20px; + padding: 19px; + margin-bottom: 20px; + background-color: #515f6cbb; + border: 1px solid #606d7b; +} + +.well-lg { + padding: 24px; +} + +.well-sm { + padding: 9px; +} + +.small-box h3, .small-box p { + color: #c3c3c3; +} + +.small-box-footer { + color: #288afb; +} + +.small-box .icon { + color: #cad1d8; +} + +.bg-gray { + background-color: #33404d !important; +} + +pre { + color: #cad1d8; + background-color: #515f6cbb; + border-color: #1f2933; +} diff --git a/public/themes/pterodactyl/css/terminal.css b/public/themes/pterodactyl/css/terminal.css index a9bd3db1..d2dc4d00 100644 --- a/public/themes/pterodactyl/css/terminal.css +++ b/public/themes/pterodactyl/css/terminal.css @@ -27,6 +27,7 @@ #terminal > .cmd { padding: 1px 0; word-wrap: break-word; + white-space: pre-wrap; } #terminal_input { diff --git a/public/themes/pterodactyl/js/admin/new-server.js b/public/themes/pterodactyl/js/admin/new-server.js index 97f05487..cda0d5cf 100644 --- a/public/themes/pterodactyl/js/admin/new-server.js +++ b/public/themes/pterodactyl/js/admin/new-server.js @@ -21,65 +21,29 @@ $(document).ready(function() { $('#pNestId').select2({ placeholder: 'Select a Nest', }).change(); + $('#pEggId').select2({ placeholder: 'Select a Nest Egg', }); + $('#pPackId').select2({ placeholder: 'Select a Service Pack', }); + $('#pNodeId').select2({ placeholder: 'Select a Node', }).change(); + $('#pAllocation').select2({ placeholder: 'Select a Default Allocation', }); + $('#pAllocationAdditional').select2({ placeholder: 'Select Additional Allocations', }); - - $('#pUserId').select2({ - ajax: { - url: Router.route('admin.users.json'), - dataType: 'json', - delay: 250, - data: function (params) { - return { - q: params.term, // search term - page: params.page, - }; - }, - processResults: function (data, params) { - return { results: data }; - }, - cache: true, - }, - escapeMarkup: function (markup) { return markup; }, - minimumInputLength: 2, - templateResult: function (data) { - if (data.loading) return data.text; - - return '
\ - User Image \ - \ - ' + data.name_first + ' ' + data.name_last +' \ - \ - ' + data.email + ' - ' + data.username + ' \ -
'; - }, - templateSelection: function (data) { - return '
\ - \ - User Image \ - \ - \ - ' + data.name_first + ' ' + data.name_last + ' (' + data.email + ') \ - \ -
'; - } - }); }); -var lastActiveBox = null; +let lastActiveBox = null; $(document).on('click', function (event) { if (lastActiveBox !== null) { lastActiveBox.removeClass('box-primary'); @@ -97,10 +61,8 @@ $('#pNodeId').on('change', function () { data: v.allocations, placeholder: 'Select a Default Allocation', }); - $('#pAllocationAdditional').html('').select2({ - data: v.allocations, - placeholder: 'Select Additional Allocations', - }) + + updateAdditionalAllocations(); } }); }); @@ -117,8 +79,8 @@ $('#pNestId').on('change', function (event) { }); $('#pEggId').on('change', function (event) { - var parentChain = _.get(Pterodactyl.nests, $('#pNestId').val(), null); - var objectChain = _.get(parentChain, 'eggs.' + $(this).val(), null); + let parentChain = _.get(Pterodactyl.nests, $('#pNestId').val(), null); + let objectChain = _.get(parentChain, 'eggs.' + $(this).val(), null); $('#pDefaultContainer').val(_.get(objectChain, 'docker_image', 'not defined!')); @@ -139,10 +101,13 @@ $('#pEggId').on('change', function (event) { ), }); + const variableIds = {}; $('#appendVariablesTo').html(''); $.each(_.get(objectChain, 'variables', []), function (i, item) { - var isRequired = (item.required === 1) ? 'Required ' : ''; - var dataAppend = ' \ + variableIds[item.env_variable] = 'var_ref_' + item.id; + + let isRequired = (item.required === 1) ? 'Required ' : ''; + let dataAppend = ' \
\ \ \ @@ -153,4 +118,91 @@ $('#pEggId').on('change', function (event) { '; $('#appendVariablesTo').append(dataAppend); }); + + // If you receive a warning on this line, it should be fine to ignore. this function is + // defined in "resources/views/admin/servers/new.blade.php" near the bottom of the file. + serviceVariablesUpdated($('#pEggId').val(), variableIds); }); + +$('#pAllocation').on('change', function () { + updateAdditionalAllocations(); +}); + +function updateAdditionalAllocations() { + let currentAllocation = $('#pAllocation').val(); + let currentNode = $('#pNodeId').val(); + + $.each(Pterodactyl.nodeData, function (i, v) { + if (v.id == currentNode) { + let allocations = []; + + for (let i = 0; i < v.allocations.length; i++) { + const allocation = v.allocations[i]; + + if (allocation.id != currentAllocation) { + allocations.push(allocation); + } + } + + $('#pAllocationAdditional').html('').select2({ + data: allocations, + placeholder: 'Select Additional Allocations', + }); + } + }); +} + +function initUserIdSelect(data) { + function escapeHtml(str) { + var div = document.createElement('div'); + div.appendChild(document.createTextNode(str)); + return div.innerHTML; + } + + $('#pUserId').select2({ + ajax: { + url: '/admin/users/accounts.json', + dataType: 'json', + delay: 250, + + data: function (params) { + return { + filter: { email: params.term }, + page: params.page, + }; + }, + + processResults: function (data, params) { + return { results: data }; + }, + + cache: true, + }, + + data: data, + escapeMarkup: function (markup) { return markup; }, + minimumInputLength: 2, + templateResult: function (data) { + if (data.loading) return escapeHtml(data.text); + + return '
\ + User Image \ + \ + ' + escapeHtml(data.name_first) + ' ' + escapeHtml(data.name_last) +' \ + \ + ' + escapeHtml(data.email) + ' - ' + escapeHtml(data.username) + ' \ +
'; + }, + templateSelection: function (data) { + return '
\ + \ + User Image \ + \ + \ + ' + escapeHtml(data.name_first) + ' ' + escapeHtml(data.name_last) + ' (' + escapeHtml(data.email) + ') \ + \ +
'; + } + + }); +} diff --git a/public/themes/pterodactyl/js/admin/node/view-servers.js b/public/themes/pterodactyl/js/admin/node/view-servers.js deleted file mode 100644 index 96950cd1..00000000 --- a/public/themes/pterodactyl/js/admin/node/view-servers.js +++ /dev/null @@ -1,113 +0,0 @@ -// Copyright (c) 2015 - 2017 Dane Everitt -// -// 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. -(function initSocket() { - if (typeof $.notifyDefaults !== 'function') { - console.error('Notify does not appear to be loaded.'); - return; - } - - if (typeof io !== 'function') { - console.error('Socket.io is reqired to use this panel.'); - return; - } - - $.notifyDefaults({ - placement: { - from: 'bottom', - align: 'right' - }, - newest_on_top: true, - delay: 2000, - animate: { - enter: 'animated zoomInDown', - exit: 'animated zoomOutDown' - } - }); - - var notifySocketError = false; - // Main Socket Object - window.Socket = io(Pterodactyl.node.scheme + '://' + Pterodactyl.node.fqdn + ':' + Pterodactyl.node.daemonListen + '/v1/stats/', { - 'query': 'token=' + Pterodactyl.node.daemonSecret, - }); - - // Socket Failed to Connect - Socket.io.on('connect_error', function (err) { - if(typeof notifySocketError !== 'object') { - notifySocketError = $.notify({ - message: 'There was an error attempting to establish a WebSocket connection to the Daemon. This panel will not work as expected.

' + err, - }, { - type: 'danger', - delay: 0 - }); - } - }); - - // Connected to Socket Successfully - Socket.on('connect', function () { - if (notifySocketError !== false) { - notifySocketError.close(); - notifySocketError = false; - } - }); - - Socket.on('error', function (err) { - console.error('There was an error while attemping to connect to the websocket: ' + err + '\n\nPlease try loading this page again.'); - }); - - Socket.on('live-stats', function (data) { - $.each(data.servers, function (uuid, info) { - var element = $('tr[data-server="' + uuid + '"]'); - switch (info.status) { - case 0: - element.find('[data-action="status"]').html('Offline'); - break; - case 1: - element.find('[data-action="status"]').html('Online'); - break; - case 2: - element.find('[data-action="status"]').html('Starting'); - break; - case 3: - element.find('[data-action="status"]').html('Stopping'); - break; - case 20: - element.find('[data-action="status"]').html('Installing'); - break; - case 30: - element.find('[data-action="status"]').html('Suspended'); - break; - } - if (info.status !== 0) { - var cpuMax = element.find('[data-action="cpu"]').data('cpumax'); - var currentCpu = info.proc.cpu.total; - if (cpuMax !== 0) { - currentCpu = parseFloat(((info.proc.cpu.total / cpuMax) * 100).toFixed(2).toString()); - } - element.find('[data-action="memory"]').html(parseInt(info.proc.memory.total / (1024 * 1024))); - element.find('[data-action="disk"]').html(parseInt(info.proc.disk.used)); - element.find('[data-action="cpu"]').html(currentCpu); - } else { - element.find('[data-action="memory"]').html('--'); - element.find('[data-action="disk"]').html('--'); - element.find('[data-action="cpu"]').html('--'); - } - }); - }); -})(); \ No newline at end of file diff --git a/public/themes/pterodactyl/js/admin/server/transfer.js b/public/themes/pterodactyl/js/admin/server/transfer.js new file mode 100644 index 00000000..5c2664a8 --- /dev/null +++ b/public/themes/pterodactyl/js/admin/server/transfer.js @@ -0,0 +1,56 @@ +$(document).ready(function () { + $('#pNodeId').select2({ + placeholder: 'Select a Node', + }).change(); + + $('#pAllocation').select2({ + placeholder: 'Select a Default Allocation', + }); + + $('#pAllocationAdditional').select2({ + placeholder: 'Select Additional Allocations', + }); +}); + +$('#pNodeId').on('change', function () { + let currentNode = $(this).val(); + + $.each(Pterodactyl.nodeData, function (i, v) { + if (v.id == currentNode) { + $('#pAllocation').html('').select2({ + data: v.allocations, + placeholder: 'Select a Default Allocation', + }); + + updateAdditionalAllocations(); + } + }); +}); + +$('#pAllocation').on('change', function () { + updateAdditionalAllocations(); +}); + +function updateAdditionalAllocations() { + let currentAllocation = $('#pAllocation').val(); + let currentNode = $('#pNodeId').val(); + + $.each(Pterodactyl.nodeData, function (i, v) { + if (v.id == currentNode) { + let allocations = []; + + for (let i = 0; i < v.allocations.length; i++) { + const allocation = v.allocations[i]; + + if (allocation.id != currentAllocation) { + allocations.push(allocation); + } + } + + $('#pAllocationAdditional').html('').select2({ + data: allocations, + placeholder: 'Select Additional Allocations', + }); + } + }); +} diff --git a/public/themes/pterodactyl/js/frontend/2fa-modal.js b/public/themes/pterodactyl/js/frontend/2fa-modal.js deleted file mode 100644 index 8de4ee53..00000000 --- a/public/themes/pterodactyl/js/frontend/2fa-modal.js +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright (c) 2015 - 2017 Dane Everitt -// -// 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. - -var TwoFactorModal = (function () { - - function bindListeners() { - $(document).ready(function () { - $('#close_reload').click(function () { - location.reload(); - }); - $('#do_2fa').submit(function (event) { - event.preventDefault(); - - $.ajax({ - type: 'PUT', - url: Router.route('account.security.totp'), - headers: { - 'X-CSRF-TOKEN': $('meta[name="_token"]').attr('content'), - } - }).done(function (data) { - var image = new Image(); - image.src = data.qrImage; - $(image).on('load', function () { - $('#hide_img_load').slideUp(function () { - $('#qr_image_insert').attr('src', image.src).slideDown(); - }); - }); - $('#open2fa').modal('show'); - }).fail(function (jqXHR) { - alert('An error occurred while attempting to load the 2FA setup modal. Please try again.'); - console.error(jqXHR); - }); - - }); - $('#2fa_token_verify').submit(function (event) { - event.preventDefault(); - $('#submit_action').html(' Submit').addClass('disabled'); - - $.ajax({ - type: 'POST', - url: Router.route('account.security.totp'), - headers: { - 'X-CSRF-TOKEN': $('meta[name="_token"]').attr('content'), - }, - data: { - token: $('#2fa_token').val() - } - }).done(function (data) { - $('#notice_box_2fa').hide(); - if (data === 'true') { - $('#notice_box_2fa').html('
2-Factor Authentication has been enabled on your account. Press \'Close\' below to reload the page.
').slideDown(); - } else { - $('#notice_box_2fa').html('
The token provided was invalid.
').slideDown(); - } - }).fail(function (jqXHR) { - $('#notice_box_2fa').html('
There was an error while attempting to enable 2-Factor Authentication on this account.
').slideDown(); - console.error(jqXHR); - }).always(function () { - $('#submit_action').html('Submit').removeClass('disabled'); - }); - }); - }); - } - - return { - init: function () { - bindListeners(); - } - } -})(); - -TwoFactorModal.init(); diff --git a/public/themes/pterodactyl/js/frontend/console.js b/public/themes/pterodactyl/js/frontend/console.js deleted file mode 100644 index 19a11e20..00000000 --- a/public/themes/pterodactyl/js/frontend/console.js +++ /dev/null @@ -1,392 +0,0 @@ -// Copyright (c) 2015 - 2017 Dane Everitt -// -// 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. -var CONSOLE_PUSH_COUNT = Pterodactyl.config.console_count || 50; -var CONSOLE_PUSH_FREQ = Pterodactyl.config.console_freq || 200; -var CONSOLE_OUTPUT_LIMIT = Pterodactyl.config.console_limit || 2000; - -var KEYCODE_UP_ARROW = 38; -var KEYCODE_DOWN_ARROW = 40; - -var AnsiUp = new AnsiUp; -AnsiUp.use_classes = true; - -var $terminal = $('#terminal'); -var $terminalInput = $('.terminal_input--input'); -var $scrollNotify = $('#terminalNotify'); - -$(document).ready(function () { - var storage = window.localStorage; - var activeHx = []; - var currentHxIndex = 0; - var currentKeyCount = 0; - - var storedConsoleHistory = storage.getItem('console_hx_' + Pterodactyl.server.uuid); - try { - activeHx = JSON.parse(storedConsoleHistory) || []; - currentKeyCount = activeHx.length - 1; - } catch (ex) { - // - } - - $terminalInput.focus(); - $('.terminal_input--prompt, #terminal_input, #terminalNotify').on('click', function () { - $terminalInput.focus(); - }); - - $terminalInput.on('keyup', function (e) { - if (e.which === KEYCODE_DOWN_ARROW || e.which === KEYCODE_UP_ARROW) { - var value = consoleHistory(e.which); - - if (value !== false) { - $terminalInput.val(value); - } - } - - if (e.which === 27) { - $(this).val(''); - } - - if (e.which === 13) { - saveToHistory($(this).val()); - Socket.emit((ConsoleServerStatus !== 0) ? 'send command' : 'set status', $(this).val()); - - $(this).val(''); - } - }); - - function consoleHistory(key) { - // Get previous - if (key === KEYCODE_UP_ARROW) { - // currentHxIndex++; - var index = activeHx.length - (currentHxIndex + 1); - - if (typeof activeHx[index - 1] === 'undefined') { - return activeHx[index]; - } - - currentHxIndex++; - return activeHx[index]; - } - - // Get more recent - if (key === KEYCODE_DOWN_ARROW) { - var index = activeHx.length - currentHxIndex; - - if (typeof activeHx[index + 1] === 'undefined') { - return activeHx[index]; - } - - currentHxIndex--; - return activeHx[index]; - } - } - - function saveToHistory(command) { - if (command.length === 0) { - return; - } - - if (activeHx.length >= 50) { - activeHx.pop(); - } - - currentHxIndex = 0; - currentKeyCount++; - activeHx[currentKeyCount] = command; - - storage.setItem('console_hx_' + Pterodactyl.server.uuid, JSON.stringify(activeHx)); - } -}); - -$terminal.on('scroll', function () { - if (isTerminalScrolledDown()) { - $scrollNotify.addClass('hidden'); - } -}); - -function isTerminalScrolledDown() { - return $terminal.scrollTop() + $terminal.innerHeight() + 50 > $terminal[0].scrollHeight; -} - -window.scrollToBottom = function () { - $terminal.scrollTop($terminal[0].scrollHeight); -}; - -function pushToTerminal(string) { - $terminal.append('
' + AnsiUp.ansi_to_html(string + '\u001b[0m') + '
'); -} - -(function initConsole() { - window.TerminalQueue = []; - window.ConsoleServerStatus = 0; - window.ConsoleElements = 0; - - $scrollNotify.on('click', function () { - window.scrollToBottom(); - $scrollNotify.addClass('hidden'); - }); -})(); - -(function pushOutputQueue() { - if (TerminalQueue.length > CONSOLE_PUSH_COUNT) { - // console throttled warning show - } - - if (TerminalQueue.length > 0) { - var scrolledDown = isTerminalScrolledDown(); - - for (var i = 0; i < CONSOLE_PUSH_COUNT && TerminalQueue.length > 0; i++) { - pushToTerminal(TerminalQueue[0]); - - window.ConsoleElements++; - TerminalQueue.shift(); - } - - if (scrolledDown) { - window.scrollToBottom(); - } else if ($scrollNotify.hasClass('hidden')) { - $scrollNotify.removeClass('hidden'); - } - - var removeElements = window.ConsoleElements - CONSOLE_OUTPUT_LIMIT; - if (removeElements > 0) { - $('#terminal').find('.cmd').slice(0, removeElements).remove(); - window.ConsoleElements = window.ConsoleElements - removeElements; - } - } - - window.setTimeout(pushOutputQueue, CONSOLE_PUSH_FREQ); -})(); - -(function setupSocketListeners() { - // Update Listings on Initial Status - Socket.on('initial status', function (data) { - ConsoleServerStatus = data.status; - updateServerPowerControls(data.status); - - if (data.status === 1 || data.status === 2) { - Socket.emit('send server log'); - } - }); - - // Update Listings on Status - Socket.on('status', function (data) { - ConsoleServerStatus = data.status; - updateServerPowerControls(data.status); - }); - - // Skips the queue so we don't wait - // 10 minutes to load the log... - Socket.on('server log', function (data) { - $('#terminal').html(''); - data.split(/\n/g).forEach(function (item) { - pushToTerminal(item); - }); - window.scrollToBottom(); - }); - - Socket.on('console', function (data) { - if(data.line) { - data.line.split(/\n/g).forEach(function (item) { - TerminalQueue.push(item); - }); - } - }); -})(); - -function updateServerPowerControls (data) { - // Server is On or Starting - if(data == 1 || data == 2) { - $('[data-attr="power"][data-action="start"]').addClass('disabled'); - $('[data-attr="power"][data-action="stop"], [data-attr="power"][data-action="restart"]').removeClass('disabled'); - } else { - if (data == 0) { - $('[data-attr="power"][data-action="start"]').removeClass('disabled'); - } - $('[data-attr="power"][data-action="stop"], [data-attr="power"][data-action="restart"]').addClass('disabled'); - } - - if(data !== 0) { - $('[data-attr="power"][data-action="kill"]').removeClass('disabled'); - } else { - $('[data-attr="power"][data-action="kill"]').addClass('disabled'); - } -} - -$(document).ready(function () { - $('[data-attr="power"]').click(function (event) { - if (! $(this).hasClass('disabled')) { - Socket.emit('set status', $(this).data('action')); - } - }); - - (function setupChartElements() { - if (typeof SkipConsoleCharts !== 'undefined') { - return; - } - - Socket.on('proc', function (proc) { - if (CPUData.length > 10) { - CPUData.shift(); - MemoryData.shift(); - TimeLabels.shift(); - } - - var cpuUse = (Pterodactyl.server.cpu > 0) ? parseFloat(((proc.data.cpu.total / Pterodactyl.server.cpu) * 100).toFixed(3).toString()) : proc.data.cpu.total; - CPUData.push(cpuUse); - MemoryData.push(parseInt(proc.data.memory.total / (1024 * 1024))); - - TimeLabels.push($.format.date(new Date(), 'HH:mm:ss')); - - - // memory.cmax is the maximum given by the container - // memory.amax is given by the json config - // use the maximum of both - // with no limit memory.cmax will always be higher - // but with limit memory.amax is sometimes still smaller than memory.total - MemoryChart.config.options.scales.yAxes[0].ticks.max = Math.max(proc.data.memory.cmax, proc.data.memory.amax) / (1000 * 1000); - - if (Pterodactyl.server.cpu > 0) { - // if there is a cpu limit defined use 100% as maximum - CPUChart.config.options.scales.yAxes[0].ticks.max = 100; - } else { - // if there is no cpu limit defined use linux percentage - // and find maximum in all values - var maxCpu = 1; - for(var i = 0; i < CPUData.length; i++) { - maxCpu = Math.max(maxCpu, parseFloat(CPUData[i])) - } - - maxCpu = Math.ceil(maxCpu / 100) * 100; - CPUChart.config.options.scales.yAxes[0].ticks.max = maxCpu; - } - - - - CPUChart.update(); - MemoryChart.update(); - }); - - var ctc = $('#chart_cpu'); - var TimeLabels = []; - var CPUData = []; - var CPUChart = new Chart(ctc, { - type: 'line', - data: { - labels: TimeLabels, - datasets: [ - { - label: "Percent Use", - fill: false, - lineTension: 0.03, - backgroundColor: "#3c8dbc", - borderColor: "#3c8dbc", - borderCapStyle: 'butt', - borderDash: [], - borderDashOffset: 0.0, - borderJoinStyle: 'miter', - pointBorderColor: "#3c8dbc", - pointBackgroundColor: "#fff", - pointBorderWidth: 1, - pointHoverRadius: 5, - pointHoverBackgroundColor: "#3c8dbc", - pointHoverBorderColor: "rgba(220,220,220,1)", - pointHoverBorderWidth: 2, - pointRadius: 1, - pointHitRadius: 10, - data: CPUData, - spanGaps: false, - } - ] - }, - options: { - title: { - display: true, - text: 'CPU Usage (as Percent Total)' - }, - legend: { - display: false, - }, - animation: { - duration: 1, - }, - scales: { - yAxes: [{ - ticks: { - beginAtZero: true - } - }] - } - } - }); - - var ctm = $('#chart_memory'); - MemoryData = []; - MemoryChart = new Chart(ctm, { - type: 'line', - data: { - labels: TimeLabels, - datasets: [ - { - label: "Memory Use", - fill: false, - lineTension: 0.03, - backgroundColor: "#3c8dbc", - borderColor: "#3c8dbc", - borderCapStyle: 'butt', - borderDash: [], - borderDashOffset: 0.0, - borderJoinStyle: 'miter', - pointBorderColor: "#3c8dbc", - pointBackgroundColor: "#fff", - pointBorderWidth: 1, - pointHoverRadius: 5, - pointHoverBackgroundColor: "#3c8dbc", - pointHoverBorderColor: "rgba(220,220,220,1)", - pointHoverBorderWidth: 2, - pointRadius: 1, - pointHitRadius: 10, - data: MemoryData, - spanGaps: false, - } - ] - }, - options: { - title: { - display: true, - text: 'Memory Usage (in Megabytes)' - }, - legend: { - display: false, - }, - animation: { - duration: 1, - }, - scales: { - yAxes: [{ - ticks: { - beginAtZero: true - } - }] - } - } - }); - })(); -}); diff --git a/public/themes/pterodactyl/js/frontend/files/editor.js b/public/themes/pterodactyl/js/frontend/files/editor.js deleted file mode 100644 index 0ab7f9f7..00000000 --- a/public/themes/pterodactyl/js/frontend/files/editor.js +++ /dev/null @@ -1,131 +0,0 @@ -// Copyright (c) 2015 - 2017 Dane Everitt -// -// 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. -(function () { - window.Editor = ace.edit('editor'); - var Whitespace = ace.require('ace/ext/whitespace'); - var Modelist = ace.require('ace/ext/modelist'); - - Editor.setTheme('ace/theme/chrome'); - Editor.getSession().setUseWrapMode(true); - Editor.setShowPrintMargin(false); - - if (typeof Pterodactyl !== 'undefined') { - if(typeof Pterodactyl.stat !== 'undefined') { - Editor.getSession().setMode(Modelist.getModeForPath(Pterodactyl.stat.name).mode); - } - } - - Editor.commands.addCommand({ - name: 'save', - bindKey: {win: 'Ctrl-S', mac: 'Command-S'}, - exec: function(editor) { - if ($('#save_file').length) { - save(); - } else if ($('#create_file').length) { - create(); - } - }, - readOnly: false - }); - - Editor.commands.addCommands(Whitespace.commands); - - Whitespace.detectIndentation(Editor.session); - - $('#save_file').on('click', function (e) { - e.preventDefault(); - save(); - }); - - $('#create_file').on('click', function (e) { - e.preventDefault(); - create(); - }); - - $('#aceMode').on('change', event => { - Editor.getSession().setMode('ace/mode/' + $('#aceMode').val()); - }); - - function create() { - if (_.isEmpty($('#file_name').val())) { - $.notify({ - message: 'No filename was passed.' - }, { - type: 'danger' - }); - return; - } - $('#create_file').html(' Creating File').addClass('disabled'); - $.ajax({ - type: 'POST', - url: Router.route('server.files.save', { server: Pterodactyl.server.uuidShort }), - headers: { - 'X-CSRF-TOKEN': $('meta[name="_token"]').attr('content'), - }, - data: { - file: $('#file_name').val(), - contents: Editor.getValue() - } - }).done(function (data) { - window.location.replace(Router.route('server.files.edit', { - server: Pterodactyl.server.uuidShort, - file: $('#file_name').val(), - })); - }).fail(function (jqXHR) { - $.notify({ - message: jqXHR.responseText - }, { - type: 'danger' - }); - }).always(function () { - $('#create_file').html('Create File').removeClass('disabled'); - }); - } - - function save() { - var fileName = $('input[name="file"]').val(); - $('#save_file').html(' Saving File').addClass('disabled'); - $.ajax({ - type: 'POST', - url: Router.route('server.files.save', { server: Pterodactyl.server.uuidShort }), - headers: { - 'X-CSRF-TOKEN': $('meta[name="_token"]').attr('content'), - }, - data: { - file: fileName, - contents: Editor.getValue() - } - }).done(function (data) { - $.notify({ - message: 'File was successfully saved.' - }, { - type: 'success' - }); - }).fail(function (jqXHR) { - $.notify({ - message: jqXHR.responseText - }, { - type: 'danger' - }); - }).always(function () { - $('#save_file').html('  Save File').removeClass('disabled'); - }); - } -})(); diff --git a/public/themes/pterodactyl/js/frontend/files/filemanager.min.js b/public/themes/pterodactyl/js/frontend/files/filemanager.min.js deleted file mode 100644 index 5fe4e9f3..00000000 --- a/public/themes/pterodactyl/js/frontend/files/filemanager.min.js +++ /dev/null @@ -1,5 +0,0 @@ -'use strict';var _createClass=function(){function defineProperties(target,props){for(var i=0;i').text(value).html()}},{key:'folder',value:function folder(path){var inputValue=void 0;if(path){inputValue=path}else{var nameBlock=$(this.element).find('td[data-identifier="name"]');var currentName=decodeURIComponent(nameBlock.data('name'));var currentPath=decodeURIComponent(nameBlock.data('path'));if($(this.element).data('type')==='file'){inputValue=currentPath}else{inputValue=''+currentPath+currentName+'/'}}swal({type:'input',title:'Create Folder',text:'Please enter the path and folder name below.',showCancelButton:true,showConfirmButton:true,closeOnConfirm:false,showLoaderOnConfirm:true,inputValue:inputValue},function(val){if(val===false){return false}$.ajax({type:'POST',headers:{'X-Access-Token':Pterodactyl.server.daemonSecret,'X-Access-Server':Pterodactyl.server.uuid},contentType:'application/json; charset=utf-8',url:Pterodactyl.node.scheme+'://'+Pterodactyl.node.fqdn+':'+Pterodactyl.node.daemonListen+'/v1/server/file/folder',timeout:10000,data:JSON.stringify({path:val})}).done(function(data){swal.close();Files.list()}).fail(function(jqXHR){console.error(jqXHR);var error='An error occurred while trying to process this request.';if(typeof jqXHR.responseJSON!=='undefined'&&typeof jqXHR.responseJSON.error!=='undefined'){error=jqXHR.responseJSON.error}swal({type:'error',title:'',text:error})})})}},{key:'move',value:function move(){var nameBlock=$(this.element).find('td[data-identifier="name"]');var currentName=decodeURIComponent(nameBlock.attr('data-name'));var currentPath=decodeURIComponent(nameBlock.data('path'));swal({type:'input',title:'Move File',text:'Please enter the new path for the file below.',showCancelButton:true,showConfirmButton:true,closeOnConfirm:false,showLoaderOnConfirm:true,inputValue:''+currentPath+currentName},function(val){if(val===false){return false}$.ajax({type:'POST',headers:{'X-Access-Token':Pterodactyl.server.daemonSecret,'X-Access-Server':Pterodactyl.server.uuid},contentType:'application/json; charset=utf-8',url:Pterodactyl.node.scheme+'://'+Pterodactyl.node.fqdn+':'+Pterodactyl.node.daemonListen+'/v1/server/file/move',timeout:10000,data:JSON.stringify({from:''+currentPath+currentName,to:''+val})}).done(function(data){nameBlock.parent().addClass('warning').delay(200).fadeOut();swal.close()}).fail(function(jqXHR){console.error(jqXHR);var error='An error occurred while trying to process this request.';if(typeof jqXHR.responseJSON!=='undefined'&&typeof jqXHR.responseJSON.error!=='undefined'){error=jqXHR.responseJSON.error}swal({type:'error',title:'',text:error})})})}},{key:'rename',value:function rename(){var nameBlock=$(this.element).find('td[data-identifier="name"]');var currentLink=nameBlock.find('a');var currentName=decodeURIComponent(nameBlock.attr('data-name'));var attachEditor='\n \n \n ';nameBlock.html(attachEditor);var inputField=nameBlock.find('input');var inputLoader=nameBlock.find('.input-loader');inputField.focus();inputField.on('blur keydown',function(e){if(e.type==='keydown'&&e.which===27||e.type==='blur'||e.type==='keydown'&&e.which===13&¤tName===inputField.val()){if(!_.isEmpty(currentLink)){nameBlock.html(currentLink)}else{nameBlock.html(currentName)}inputField.remove();ContextMenu.unbind().run();return}if(e.type==='keydown'&&e.which!==13)return;inputLoader.show();var currentPath=decodeURIComponent(nameBlock.data('path'));$.ajax({type:'POST',headers:{'X-Access-Token':Pterodactyl.server.daemonSecret,'X-Access-Server':Pterodactyl.server.uuid},contentType:'application/json; charset=utf-8',url:Pterodactyl.node.scheme+'://'+Pterodactyl.node.fqdn+':'+Pterodactyl.node.daemonListen+'/v1/server/file/rename',timeout:10000,data:JSON.stringify({from:''+currentPath+currentName,to:''+currentPath+inputField.val()})}).done(function(data){nameBlock.attr('data-name',inputField.val());if(!_.isEmpty(currentLink)){var newLink=currentLink.attr('href');if(nameBlock.parent().data('type')!=='folder'){newLink=newLink.substr(0,newLink.lastIndexOf('/'))+'/'+inputField.val()}currentLink.attr('href',newLink);nameBlock.html(currentLink.html(inputField.val()))}else{nameBlock.html(inputField.val())}inputField.remove()}).fail(function(jqXHR){console.error(jqXHR);var error='An error occurred while trying to process this request.';if(typeof jqXHR.responseJSON!=='undefined'&&typeof jqXHR.responseJSON.error!=='undefined'){error=jqXHR.responseJSON.error}nameBlock.addClass('has-error').delay(2000).queue(function(){nameBlock.removeClass('has-error').dequeue()});inputField.popover({animation:true,placement:'top',content:error,title:'Save Error'}).popover('show')}).always(function(){inputLoader.remove();ContextMenu.unbind().run()})})}},{key:'copy',value:function copy(){var nameBlock=$(this.element).find('td[data-identifier="name"]');var currentName=decodeURIComponent(nameBlock.attr('data-name'));var currentPath=decodeURIComponent(nameBlock.data('path'));swal({type:'input',title:'Copy File',text:'Please enter the new path for the copied file below.',showCancelButton:true,showConfirmButton:true,closeOnConfirm:false,showLoaderOnConfirm:true,inputValue:''+currentPath+currentName},function(val){if(val===false){return false}$.ajax({type:'POST',headers:{'X-Access-Token':Pterodactyl.server.daemonSecret,'X-Access-Server':Pterodactyl.server.uuid},contentType:'application/json; charset=utf-8',url:Pterodactyl.node.scheme+'://'+Pterodactyl.node.fqdn+':'+Pterodactyl.node.daemonListen+'/v1/server/file/copy',timeout:10000,data:JSON.stringify({from:''+currentPath+currentName,to:''+val})}).done(function(data){swal({type:'success',title:'',text:'File successfully copied.'});Files.list()}).fail(function(jqXHR){console.error(jqXHR);var error='An error occurred while trying to process this request.';if(typeof jqXHR.responseJSON!=='undefined'&&typeof jqXHR.responseJSON.error!=='undefined'){error=jqXHR.responseJSON.error}swal({type:'error',title:'',text:error})})})}},{key:'download',value:function download(){var nameBlock=$(this.element).find('td[data-identifier="name"]');var fileName=decodeURIComponent(nameBlock.attr('data-name'));var filePath=decodeURIComponent(nameBlock.data('path'));window.location='/server/'+Pterodactyl.server.uuidShort+'/files/download/'+filePath+fileName}},{key:'delete',value:function _delete(){var nameBlock=$(this.element).find('td[data-identifier="name"]');var delPath=decodeURIComponent(nameBlock.data('path'));var delName=decodeURIComponent(nameBlock.data('name'));swal({type:'warning',title:'',text:'Are you sure you want to delete '+this.sanitizedString(delName)+'?',html:true,showCancelButton:true,showConfirmButton:true,closeOnConfirm:false,showLoaderOnConfirm:true},function(){$.ajax({type:'POST',headers:{'X-Access-Token':Pterodactyl.server.daemonSecret,'X-Access-Server':Pterodactyl.server.uuid},contentType:'application/json; charset=utf-8',url:Pterodactyl.node.scheme+'://'+Pterodactyl.node.fqdn+':'+Pterodactyl.node.daemonListen+'/v1/server/file/delete',timeout:10000,data:JSON.stringify({items:[''+delPath+delName]})}).done(function(data){nameBlock.parent().addClass('warning').delay(200).fadeOut();swal({type:'success',title:'File Deleted'})}).fail(function(jqXHR){console.error(jqXHR);swal({type:'error',title:'Whoops!',html:true,text:'An error occurred while attempting to delete this file. Please try again.'})})})}},{key:'toggleMassActions',value:function toggleMassActions(){if($('#file_listing input[type="checkbox"]:checked').length){$('#mass_actions').removeClass('disabled')}else{$('#mass_actions').addClass('disabled')}}},{key:'toggleHighlight',value:function toggleHighlight(event){var parent=$(event.currentTarget);var item=$(event.currentTarget).find('input');if($(item).is(':checked')){$(item).prop('checked',false);parent.removeClass('warning').delay(200)}else{$(item).prop('checked',true);parent.addClass('warning').delay(200)}}},{key:'highlightAll',value:function highlightAll(event){var parent=void 0;var item=$(event.currentTarget).find('input');if($(item).is(':checked')){$('#file_listing input[type=checkbox]').prop('checked',false);$('#file_listing input[data-action="addSelection"]').each(function(){parent=$(this).closest('tr');parent.removeClass('warning').delay(200)})}else{$('#file_listing input[type=checkbox]').prop('checked',true);$('#file_listing input[data-action="addSelection"]').each(function(){parent=$(this).closest('tr');parent.addClass('warning').delay(200)})}}},{key:'deleteSelected',value:function deleteSelected(){var selectedItems=[];var selectedItemsElements=[];var parent=void 0;var nameBlock=void 0;var delLocation=void 0;$('#file_listing input[data-action="addSelection"]:checked').each(function(){parent=$(this).closest('tr');nameBlock=$(parent).find('td[data-identifier="name"]');delLocation=decodeURIComponent(nameBlock.data('path'))+decodeURIComponent(nameBlock.data('name'));selectedItems.push(delLocation);selectedItemsElements.push(parent)});if(selectedItems.length!=0){var formattedItems='';var i=0;var self=this;$.each(selectedItems,function(key,value){formattedItems+=''+self.sanitizedString(value)+', ';i++;return i<5});formattedItems=formattedItems.slice(0,-2);if(selectedItems.length>5){formattedItems+=', and '+(selectedItems.length-5)+' other(s)'}swal({type:'warning',title:'',text:'Are you sure you want to delete the following files: '+formattedItems+'?',html:true,showCancelButton:true,showConfirmButton:true,closeOnConfirm:false,showLoaderOnConfirm:true},function(){$.ajax({type:'POST',headers:{'X-Access-Token':Pterodactyl.server.daemonSecret,'X-Access-Server':Pterodactyl.server.uuid},contentType:'application/json; charset=utf-8',url:Pterodactyl.node.scheme+'://'+Pterodactyl.node.fqdn+':'+Pterodactyl.node.daemonListen+'/v1/server/file/delete',timeout:10000,data:JSON.stringify({items:selectedItems})}).done(function(data){$('#file_listing input:checked').each(function(){$(this).prop('checked',false)});$.each(selectedItemsElements,function(){$(this).addClass('warning').delay(200).fadeOut()});swal({type:'success',title:'Files Deleted'})}).fail(function(jqXHR){console.error(jqXHR);swal({type:'error',title:'Whoops!',html:true,text:'An error occurred while attempting to delete these files. Please try again.'})})})}else{swal({type:'warning',title:'',text:'Please select files/folders to delete.'})}}},{key:'decompress',value:function decompress(){var nameBlock=$(this.element).find('td[data-identifier="name"]');var compPath=decodeURIComponent(nameBlock.data('path'));var compName=decodeURIComponent(nameBlock.data('name'));swal({title:' Decompressing...',text:'This might take a few seconds to complete.',html:true,allowOutsideClick:false,allowEscapeKey:false,showConfirmButton:false});$.ajax({type:'POST',url:Pterodactyl.node.scheme+'://'+Pterodactyl.node.fqdn+':'+Pterodactyl.node.daemonListen+'/v1/server/file/decompress',headers:{'X-Access-Token':Pterodactyl.server.daemonSecret,'X-Access-Server':Pterodactyl.server.uuid},contentType:'application/json; charset=utf-8',data:JSON.stringify({files:''+compPath+compName})}).done(function(data){swal.close();Files.list(compPath)}).fail(function(jqXHR){console.error(jqXHR);var error='An error occurred while trying to process this request.';if(typeof jqXHR.responseJSON!=='undefined'&&typeof jqXHR.responseJSON.error!=='undefined'){error=jqXHR.responseJSON.error}swal({type:'error',title:'Whoops!',html:true,text:error})})}},{key:'compress',value:function compress(){var _this=this;var nameBlock=$(this.element).find('td[data-identifier="name"]');var compPath=decodeURIComponent(nameBlock.data('path'));var compName=decodeURIComponent(nameBlock.data('name'));$.ajax({type:'POST',url:Pterodactyl.node.scheme+'://'+Pterodactyl.node.fqdn+':'+Pterodactyl.node.daemonListen+'/v1/server/file/compress',headers:{'X-Access-Token':Pterodactyl.server.daemonSecret,'X-Access-Server':Pterodactyl.server.uuid},contentType:'application/json; charset=utf-8',data:JSON.stringify({files:''+compPath+compName,to:compPath.toString()})}).done(function(data){Files.list(compPath,function(err){if(err)return;var fileListing=$('#file_listing').find('[data-name="'+data.saved_as+'"]').parent();fileListing.addClass('success pulsate').delay(3000).queue(function(){fileListing.removeClass('success pulsate').dequeue()})})}).fail(function(jqXHR){console.error(jqXHR);var error='An error occurred while trying to process this request.';if(typeof jqXHR.responseJSON!=='undefined'&&typeof jqXHR.responseJSON.error!=='undefined'){error=jqXHR.responseJSON.error}swal({type:'error',title:'Whoops!',html:true,text:_this.sanitizedString(error)})})}}]);return ActionsClass}(); -'use strict';var _createClass=function(){function defineProperties(target,props){for(var i=0;i').text(newFilePath).html()+'" class="text-muted"> New File
  • New Folder
  • '}if(Pterodactyl.permissions.downloadFiles||Pterodactyl.permissions.deleteFiles){buildMenu+='
  • '}if(Pterodactyl.permissions.downloadFiles){buildMenu+=''}if(Pterodactyl.permissions.deleteFiles){buildMenu+='
  • Delete
  • '}buildMenu+='';return buildMenu}},{key:'rightClick',value:function rightClick(){var _this=this;$('[data-action="toggleMenu"]').on('mousedown',function(event){event.preventDefault();if($(document).find('#fileOptionMenu').is(':visible')){$('body').trigger('click');return}_this.showMenu(event)});$('#file_listing > tbody td').on('contextmenu',function(event){_this.showMenu(event)})}},{key:'showMenu',value:function showMenu(event){var _this2=this;var parent=$(event.target).closest('tr');var menu=$(this.makeMenu(parent));if(parent.data('type')==='disabled')return;event.preventDefault();$(menu).appendTo('body');$(menu).data('invokedOn',$(event.target)).show().css({position:'absolute',left:event.pageX-150,top:event.pageY});this.activeLine=parent;this.activeLine.addClass('active');var Actions=new ActionsClass(parent,menu);if(Pterodactyl.permissions.moveFiles){$(menu).find('li[data-action="move"]').unbind().on('click',function(e){e.preventDefault();Actions.move()});$(menu).find('li[data-action="rename"]').unbind().on('click',function(e){e.preventDefault();Actions.rename()})}if(Pterodactyl.permissions.copyFiles){$(menu).find('li[data-action="copy"]').unbind().on('click',function(e){e.preventDefault();Actions.copy()})}if(Pterodactyl.permissions.compressFiles){if(parent.data('type')==='folder'){$(menu).find('li[data-action="compress"]').removeClass('hidden')}$(menu).find('li[data-action="compress"]').unbind().on('click',function(e){e.preventDefault();Actions.compress()})}if(Pterodactyl.permissions.decompressFiles){if(_.without(['application/zip','application/gzip','application/x-gzip'],parent.data('mime')).length<3){$(menu).find('li[data-action="decompress"]').removeClass('hidden')}$(menu).find('li[data-action="decompress"]').unbind().on('click',function(e){e.preventDefault();Actions.decompress()})}if(Pterodactyl.permissions.createFiles){$(menu).find('li[data-action="folder"]').unbind().on('click',function(e){e.preventDefault();Actions.folder()})}if(Pterodactyl.permissions.downloadFiles){if(parent.data('type')==='file'){$(menu).find('li[data-action="download"]').removeClass('hidden')}$(menu).find('li[data-action="download"]').unbind().on('click',function(e){e.preventDefault();Actions.download()})}if(Pterodactyl.permissions.deleteFiles){$(menu).find('li[data-action="delete"]').unbind().on('click',function(e){e.preventDefault();Actions.delete()})}$(window).unbind().on('click',function(event){if($(event.target).is('.disable-menu-hide')){event.preventDefault();return}$(menu).unbind().remove();if(!_.isNull(_this2.activeLine))_this2.activeLine.removeClass('active')})}},{key:'directoryClick',value:function directoryClick(){$('a[data-action="directory-view"]').on('click',function(event){event.preventDefault();var path=$(this).parent().data('path')||'';var name=$(this).parent().data('name')||'';window.location.hash=encodeURIComponent(path+name);Files.list()})}}]);return ContextMenuClass}();window.ContextMenu=new ContextMenuClass; -'use strict';var _typeof=typeof Symbol==='function'&&typeof Symbol.iterator==='symbol'?function(obj){return typeof obj}:function(obj){return obj&&typeof Symbol==='function'&&obj.constructor===Symbol&&obj!==Symbol.prototype?'symbol':typeof obj};var _createClass=function(){function defineProperties(target,props){for(var i=0;i\r\n//\r\n// Permission is hereby granted, free of charge, to any person obtaining a copy\r\n// of this software and associated documentation files (the \"Software\"), to deal\r\n// in the Software without restriction, including without limitation the rights\r\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n// copies of the Software, and to permit persons to whom the Software is\r\n// furnished to do so, subject to the following conditions:\r\n//\r\n// The above copyright notice and this permission notice shall be included in all\r\n// copies or substantial portions of the Software.\r\n//\r\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\r\n// SOFTWARE.\r\nclass ActionsClass {\r\n constructor(element, menu) {\r\n this.element = element;\r\n this.menu = menu;\r\n }\r\n\r\n destroy() {\r\n this.element = undefined;\r\n }\r\n\r\n sanitizedString(value) {\r\n return $('
    ').text(value).html();\r\n }\r\n\r\n folder(path) {\r\n let inputValue\r\n if (path) {\r\n inputValue = path\r\n } else {\r\n const nameBlock = $(this.element).find('td[data-identifier=\"name\"]');\r\n const currentName = decodeURIComponent(nameBlock.data('name'));\r\n const currentPath = decodeURIComponent(nameBlock.data('path'));\r\n\r\n if ($(this.element).data('type') === 'file') {\r\n inputValue = currentPath;\r\n } else {\r\n inputValue = `${currentPath}${currentName}/`;\r\n }\r\n }\r\n\r\n swal({\r\n type: 'input',\r\n title: 'Create Folder',\r\n text: 'Please enter the path and folder name below.',\r\n showCancelButton: true,\r\n showConfirmButton: true,\r\n closeOnConfirm: false,\r\n showLoaderOnConfirm: true,\r\n inputValue: inputValue\r\n }, (val) => {\r\n if (val === false) {\r\n return false;\r\n }\r\n\r\n $.ajax({\r\n type: 'POST',\r\n headers: {\r\n 'X-Access-Token': Pterodactyl.server.daemonSecret,\r\n 'X-Access-Server': Pterodactyl.server.uuid,\r\n },\r\n contentType: 'application/json; charset=utf-8',\r\n url: `${Pterodactyl.node.scheme}://${Pterodactyl.node.fqdn}:${Pterodactyl.node.daemonListen}/v1/server/file/folder`,\r\n timeout: 10000,\r\n data: JSON.stringify({\r\n path: val,\r\n }),\r\n }).done(data => {\r\n swal.close();\r\n Files.list();\r\n }).fail(jqXHR => {\r\n console.error(jqXHR);\r\n var error = 'An error occurred while trying to process this request.';\r\n if (typeof jqXHR.responseJSON !== 'undefined' && typeof jqXHR.responseJSON.error !== 'undefined') {\r\n error = jqXHR.responseJSON.error;\r\n }\r\n swal({\r\n type: 'error',\r\n title: '',\r\n text: error,\r\n });\r\n });\r\n });\r\n }\r\n\r\n move() {\r\n const nameBlock = $(this.element).find('td[data-identifier=\"name\"]');\r\n const currentName = decodeURIComponent(nameBlock.attr('data-name'));\r\n const currentPath = decodeURIComponent(nameBlock.data('path'));\r\n\r\n swal({\r\n type: 'input',\r\n title: 'Move File',\r\n text: 'Please enter the new path for the file below.',\r\n showCancelButton: true,\r\n showConfirmButton: true,\r\n closeOnConfirm: false,\r\n showLoaderOnConfirm: true,\r\n inputValue: `${currentPath}${currentName}`,\r\n }, (val) => {\r\n if (val === false) {\r\n return false;\r\n }\r\n\r\n $.ajax({\r\n type: 'POST',\r\n headers: {\r\n 'X-Access-Token': Pterodactyl.server.daemonSecret,\r\n 'X-Access-Server': Pterodactyl.server.uuid,\r\n },\r\n contentType: 'application/json; charset=utf-8',\r\n url: `${Pterodactyl.node.scheme}://${Pterodactyl.node.fqdn}:${Pterodactyl.node.daemonListen}/v1/server/file/move`,\r\n timeout: 10000,\r\n data: JSON.stringify({\r\n from: `${currentPath}${currentName}`,\r\n to: `${val}`,\r\n }),\r\n }).done(data => {\r\n nameBlock.parent().addClass('warning').delay(200).fadeOut();\r\n swal.close();\r\n }).fail(jqXHR => {\r\n console.error(jqXHR);\r\n var error = 'An error occurred while trying to process this request.';\r\n if (typeof jqXHR.responseJSON !== 'undefined' && typeof jqXHR.responseJSON.error !== 'undefined') {\r\n error = jqXHR.responseJSON.error;\r\n }\r\n swal({\r\n type: 'error',\r\n title: '',\r\n text: error,\r\n });\r\n });\r\n });\r\n\r\n }\r\n\r\n rename() {\r\n const nameBlock = $(this.element).find('td[data-identifier=\"name\"]');\r\n const currentLink = nameBlock.find('a');\r\n const currentName = decodeURIComponent(nameBlock.attr('data-name'));\r\n const attachEditor = `\r\n \r\n \r\n `;\r\n\r\n nameBlock.html(attachEditor);\r\n const inputField = nameBlock.find('input');\r\n const inputLoader = nameBlock.find('.input-loader');\r\n\r\n inputField.focus();\r\n inputField.on('blur keydown', e => {\r\n // Save Field\r\n if (\r\n (e.type === 'keydown' && e.which === 27)\r\n || e.type === 'blur'\r\n || (e.type === 'keydown' && e.which === 13 && currentName === inputField.val())\r\n ) {\r\n if (!_.isEmpty(currentLink)) {\r\n nameBlock.html(currentLink);\r\n } else {\r\n nameBlock.html(currentName);\r\n }\r\n inputField.remove();\r\n ContextMenu.unbind().run();\r\n return;\r\n }\r\n\r\n if (e.type === 'keydown' && e.which !== 13) return;\r\n\r\n inputLoader.show();\r\n const currentPath = decodeURIComponent(nameBlock.data('path'));\r\n\r\n $.ajax({\r\n type: 'POST',\r\n headers: {\r\n 'X-Access-Token': Pterodactyl.server.daemonSecret,\r\n 'X-Access-Server': Pterodactyl.server.uuid,\r\n },\r\n contentType: 'application/json; charset=utf-8',\r\n url: `${Pterodactyl.node.scheme}://${Pterodactyl.node.fqdn}:${Pterodactyl.node.daemonListen}/v1/server/file/rename`,\r\n timeout: 10000,\r\n data: JSON.stringify({\r\n from: `${currentPath}${currentName}`,\r\n to: `${currentPath}${inputField.val()}`,\r\n }),\r\n }).done(data => {\r\n nameBlock.attr('data-name', inputField.val());\r\n if (!_.isEmpty(currentLink)) {\r\n let newLink = currentLink.attr('href');\r\n if (nameBlock.parent().data('type') !== 'folder') {\r\n newLink = newLink.substr(0, newLink.lastIndexOf('/')) + '/' + inputField.val();\r\n }\r\n currentLink.attr('href', newLink);\r\n nameBlock.html(\r\n currentLink.html(inputField.val())\r\n );\r\n } else {\r\n nameBlock.html(inputField.val());\r\n }\r\n inputField.remove();\r\n }).fail(jqXHR => {\r\n console.error(jqXHR);\r\n var error = 'An error occurred while trying to process this request.';\r\n if (typeof jqXHR.responseJSON !== 'undefined' && typeof jqXHR.responseJSON.error !== 'undefined') {\r\n error = jqXHR.responseJSON.error;\r\n }\r\n nameBlock.addClass('has-error').delay(2000).queue(() => {\r\n nameBlock.removeClass('has-error').dequeue();\r\n });\r\n inputField.popover({\r\n animation: true,\r\n placement: 'top',\r\n content: error,\r\n title: 'Save Error'\r\n }).popover('show');\r\n }).always(() => {\r\n inputLoader.remove();\r\n ContextMenu.unbind().run();\r\n });\r\n });\r\n }\r\n\r\n copy() {\r\n const nameBlock = $(this.element).find('td[data-identifier=\"name\"]');\r\n const currentName = decodeURIComponent(nameBlock.attr('data-name'));\r\n const currentPath = decodeURIComponent(nameBlock.data('path'));\r\n\r\n swal({\r\n type: 'input',\r\n title: 'Copy File',\r\n text: 'Please enter the new path for the copied file below.',\r\n showCancelButton: true,\r\n showConfirmButton: true,\r\n closeOnConfirm: false,\r\n showLoaderOnConfirm: true,\r\n inputValue: `${currentPath}${currentName}`,\r\n }, (val) => {\r\n if (val === false) {\r\n return false;\r\n }\r\n\r\n $.ajax({\r\n type: 'POST',\r\n headers: {\r\n 'X-Access-Token': Pterodactyl.server.daemonSecret,\r\n 'X-Access-Server': Pterodactyl.server.uuid,\r\n },\r\n contentType: 'application/json; charset=utf-8',\r\n url: `${Pterodactyl.node.scheme}://${Pterodactyl.node.fqdn}:${Pterodactyl.node.daemonListen}/v1/server/file/copy`,\r\n timeout: 10000,\r\n data: JSON.stringify({\r\n from: `${currentPath}${currentName}`,\r\n to: `${val}`,\r\n }),\r\n }).done(data => {\r\n swal({\r\n type: 'success',\r\n title: '',\r\n text: 'File successfully copied.'\r\n });\r\n Files.list();\r\n }).fail(jqXHR => {\r\n console.error(jqXHR);\r\n var error = 'An error occurred while trying to process this request.';\r\n if (typeof jqXHR.responseJSON !== 'undefined' && typeof jqXHR.responseJSON.error !== 'undefined') {\r\n error = jqXHR.responseJSON.error;\r\n }\r\n swal({\r\n type: 'error',\r\n title: '',\r\n text: error,\r\n });\r\n });\r\n });\r\n }\r\n\r\n download() {\r\n const nameBlock = $(this.element).find('td[data-identifier=\"name\"]');\r\n const fileName = decodeURIComponent(nameBlock.attr('data-name'));\r\n const filePath = decodeURIComponent(nameBlock.data('path'));\r\n\r\n window.location = `/server/${Pterodactyl.server.uuidShort}/files/download/${filePath}${fileName}`;\r\n }\r\n\r\n delete() {\r\n const nameBlock = $(this.element).find('td[data-identifier=\"name\"]');\r\n const delPath = decodeURIComponent(nameBlock.data('path'));\r\n const delName = decodeURIComponent(nameBlock.data('name'));\r\n\r\n swal({\r\n type: 'warning',\r\n title: '',\r\n text: 'Are you sure you want to delete ' + this.sanitizedString(delName) + '?',\r\n html: true,\r\n showCancelButton: true,\r\n showConfirmButton: true,\r\n closeOnConfirm: false,\r\n showLoaderOnConfirm: true\r\n }, () => {\r\n $.ajax({\r\n type: 'POST',\r\n headers: {\r\n 'X-Access-Token': Pterodactyl.server.daemonSecret,\r\n 'X-Access-Server': Pterodactyl.server.uuid,\r\n },\r\n contentType: 'application/json; charset=utf-8',\r\n url: `${Pterodactyl.node.scheme}://${Pterodactyl.node.fqdn}:${Pterodactyl.node.daemonListen}/v1/server/file/delete`,\r\n timeout: 10000,\r\n data: JSON.stringify({\r\n items: [`${delPath}${delName}`]\r\n }),\r\n }).done(data => {\r\n nameBlock.parent().addClass('warning').delay(200).fadeOut();\r\n swal({\r\n type: 'success',\r\n title: 'File Deleted'\r\n });\r\n }).fail(jqXHR => {\r\n console.error(jqXHR);\r\n swal({\r\n type: 'error',\r\n title: 'Whoops!',\r\n html: true,\r\n text: 'An error occurred while attempting to delete this file. Please try again.',\r\n });\r\n });\r\n });\r\n }\r\n\r\n toggleMassActions() {\r\n if ($('#file_listing input[type=\"checkbox\"]:checked').length) {\r\n $('#mass_actions').removeClass('disabled');\r\n } else {\r\n $('#mass_actions').addClass('disabled');\r\n }\r\n }\r\n\r\n toggleHighlight(event) {\r\n const parent = $(event.currentTarget);\r\n const item = $(event.currentTarget).find('input');\r\n\r\n if($(item).is(':checked')) {\r\n $(item).prop('checked', false);\r\n parent.removeClass('warning').delay(200);\r\n } else {\r\n $(item).prop('checked', true);\r\n parent.addClass('warning').delay(200);\r\n }\r\n }\r\n\r\n highlightAll(event) {\r\n let parent;\r\n const item = $(event.currentTarget).find('input');\r\n\r\n if($(item).is(':checked')) {\r\n $('#file_listing input[type=checkbox]').prop('checked', false);\r\n $('#file_listing input[data-action=\"addSelection\"]').each(function() {\r\n parent = $(this).closest('tr');\r\n parent.removeClass('warning').delay(200);\r\n });\r\n } else {\r\n $('#file_listing input[type=checkbox]').prop('checked', true);\r\n $('#file_listing input[data-action=\"addSelection\"]').each(function() {\r\n parent = $(this).closest('tr');\r\n parent.addClass('warning').delay(200);\r\n });\r\n }\r\n }\r\n\r\n deleteSelected() {\r\n let selectedItems = [];\r\n let selectedItemsElements = [];\r\n let parent;\r\n let nameBlock;\r\n let delLocation;\r\n\r\n $('#file_listing input[data-action=\"addSelection\"]:checked').each(function() {\r\n parent = $(this).closest('tr');\r\n nameBlock = $(parent).find('td[data-identifier=\"name\"]');\r\n delLocation = decodeURIComponent(nameBlock.data('path')) + decodeURIComponent(nameBlock.data('name'));\r\n\r\n selectedItems.push(delLocation);\r\n selectedItemsElements.push(parent);\r\n });\r\n\r\n if (selectedItems.length != 0)\r\n {\r\n let formattedItems = \"\";\r\n let i = 0;\r\n let self = this;\r\n\r\n $.each(selectedItems, function(key, value) {\r\n formattedItems += (\"\" + self.sanitizedString(value) + \", \");\r\n i++;\r\n return i < 5;\r\n });\r\n\r\n formattedItems = formattedItems.slice(0, -2);\r\n if (selectedItems.length > 5) {\r\n formattedItems += ', and ' + (selectedItems.length - 5) + ' other(s)';\r\n }\r\n\r\n swal({\r\n type: 'warning',\r\n title: '',\r\n text: 'Are you sure you want to delete the following files: ' + formattedItems + '?',\r\n html: true,\r\n showCancelButton: true,\r\n showConfirmButton: true,\r\n closeOnConfirm: false,\r\n showLoaderOnConfirm: true\r\n }, () => {\r\n $.ajax({\r\n type: 'POST',\r\n headers: {\r\n 'X-Access-Token': Pterodactyl.server.daemonSecret,\r\n 'X-Access-Server': Pterodactyl.server.uuid,\r\n },\r\n contentType: 'application/json; charset=utf-8',\r\n url: `${Pterodactyl.node.scheme}://${Pterodactyl.node.fqdn}:${Pterodactyl.node.daemonListen}/v1/server/file/delete`,\r\n timeout: 10000,\r\n data: JSON.stringify({\r\n items: selectedItems\r\n }),\r\n }).done(data => {\r\n $('#file_listing input:checked').each(function() {\r\n $(this).prop('checked', false);\r\n });\r\n\r\n $.each(selectedItemsElements, function() {\r\n $(this).addClass('warning').delay(200).fadeOut();\r\n })\r\n\r\n swal({\r\n type: 'success',\r\n title: 'Files Deleted'\r\n });\r\n }).fail(jqXHR => {\r\n console.error(jqXHR);\r\n swal({\r\n type: 'error',\r\n title: 'Whoops!',\r\n html: true,\r\n text: 'An error occurred while attempting to delete these files. Please try again.',\r\n });\r\n });\r\n });\r\n } else {\r\n swal({\r\n type: 'warning',\r\n title: '',\r\n text: 'Please select files/folders to delete.',\r\n });\r\n }\r\n }\r\n\r\n decompress() {\r\n const nameBlock = $(this.element).find('td[data-identifier=\"name\"]');\r\n const compPath = decodeURIComponent(nameBlock.data('path'));\r\n const compName = decodeURIComponent(nameBlock.data('name'));\r\n\r\n swal({\r\n title: ' Decompressing...',\r\n text: 'This might take a few seconds to complete.',\r\n html: true,\r\n allowOutsideClick: false,\r\n allowEscapeKey: false,\r\n showConfirmButton: false,\r\n });\r\n\r\n $.ajax({\r\n type: 'POST',\r\n url: `${Pterodactyl.node.scheme}://${Pterodactyl.node.fqdn}:${Pterodactyl.node.daemonListen}/v1/server/file/decompress`,\r\n headers: {\r\n 'X-Access-Token': Pterodactyl.server.daemonSecret,\r\n 'X-Access-Server': Pterodactyl.server.uuid,\r\n },\r\n contentType: 'application/json; charset=utf-8',\r\n data: JSON.stringify({\r\n files: `${compPath}${compName}`\r\n })\r\n }).done(data => {\r\n swal.close();\r\n Files.list(compPath);\r\n }).fail(jqXHR => {\r\n console.error(jqXHR);\r\n var error = 'An error occurred while trying to process this request.';\r\n if (typeof jqXHR.responseJSON !== 'undefined' && typeof jqXHR.responseJSON.error !== 'undefined') {\r\n error = jqXHR.responseJSON.error;\r\n }\r\n swal({\r\n type: 'error',\r\n title: 'Whoops!',\r\n html: true,\r\n text: error\r\n });\r\n });\r\n }\r\n\r\n compress() {\r\n const nameBlock = $(this.element).find('td[data-identifier=\"name\"]');\r\n const compPath = decodeURIComponent(nameBlock.data('path'));\r\n const compName = decodeURIComponent(nameBlock.data('name'));\r\n\r\n $.ajax({\r\n type: 'POST',\r\n url: `${Pterodactyl.node.scheme}://${Pterodactyl.node.fqdn}:${Pterodactyl.node.daemonListen}/v1/server/file/compress`,\r\n headers: {\r\n 'X-Access-Token': Pterodactyl.server.daemonSecret,\r\n 'X-Access-Server': Pterodactyl.server.uuid,\r\n },\r\n contentType: 'application/json; charset=utf-8',\r\n data: JSON.stringify({\r\n files: `${compPath}${compName}`,\r\n to: compPath.toString()\r\n })\r\n }).done(data => {\r\n Files.list(compPath, err => {\r\n if (err) return;\r\n const fileListing = $('#file_listing').find(`[data-name=\"${data.saved_as}\"]`).parent();\r\n fileListing.addClass('success pulsate').delay(3000).queue(() => {\r\n fileListing.removeClass('success pulsate').dequeue();\r\n });\r\n });\r\n }).fail(jqXHR => {\r\n console.error(jqXHR);\r\n var error = 'An error occurred while trying to process this request.';\r\n if (typeof jqXHR.responseJSON !== 'undefined' && typeof jqXHR.responseJSON.error !== 'undefined') {\r\n error = jqXHR.responseJSON.error;\r\n }\r\n swal({\r\n type: 'error',\r\n title: 'Whoops!',\r\n html: true,\r\n text: this.sanitizedString(error)\r\n });\r\n });\r\n }\r\n}\r\n","\"use strict\";\r\n\r\n// Copyright (c) 2015 - 2017 Dane Everitt \r\n//\r\n// Permission is hereby granted, free of charge, to any person obtaining a copy\r\n// of this software and associated documentation files (the \"Software\"), to deal\r\n// in the Software without restriction, including without limitation the rights\r\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n// copies of the Software, and to permit persons to whom the Software is\r\n// furnished to do so, subject to the following conditions:\r\n//\r\n// The above copyright notice and this permission notice shall be included in all\r\n// copies or substantial portions of the Software.\r\n//\r\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\r\n// SOFTWARE.\r\nclass ContextMenuClass {\r\n constructor() {\r\n this.activeLine = null;\r\n }\r\n\r\n run() {\r\n this.directoryClick();\r\n this.rightClick();\r\n }\r\n\r\n makeMenu(parent) {\r\n $(document).find('#fileOptionMenu').remove();\r\n if (!_.isNull(this.activeLine)) this.activeLine.removeClass('active');\r\n\r\n let newFilePath = $('#file_listing').data('current-dir');\r\n if (parent.data('type') === 'folder') {\r\n const nameBlock = parent.find('td[data-identifier=\"name\"]');\r\n const currentName = decodeURIComponent(nameBlock.attr('data-name'));\r\n const currentPath = decodeURIComponent(nameBlock.data('path'));\r\n newFilePath = `${currentPath}${currentName}`;\r\n }\r\n\r\n let buildMenu = '
      ';\r\n\r\n if (Pterodactyl.permissions.moveFiles) {\r\n buildMenu += '
    • Rename
    • \\\r\n
    • Move
    • ';\r\n }\r\n\r\n if (Pterodactyl.permissions.copyFiles) {\r\n buildMenu += '
    • Copy
    • ';\r\n }\r\n\r\n if (Pterodactyl.permissions.compressFiles) {\r\n buildMenu += '
    • Compress
    • ';\r\n }\r\n\r\n if (Pterodactyl.permissions.decompressFiles) {\r\n buildMenu += '
    • Decompress
    • ';\r\n }\r\n\r\n if (Pterodactyl.permissions.createFiles) {\r\n buildMenu += '
    • \\\r\n
    • ').text(newFilePath).html() + '\" class=\"text-muted\"> New File
    • \\\r\n
    • New Folder
    • ';\r\n }\r\n\r\n if (Pterodactyl.permissions.downloadFiles || Pterodactyl.permissions.deleteFiles) {\r\n buildMenu += '
    • ';\r\n }\r\n\r\n if (Pterodactyl.permissions.downloadFiles) {\r\n buildMenu += '
    • Download
    • ';\r\n }\r\n\r\n if (Pterodactyl.permissions.deleteFiles) {\r\n buildMenu += '
    • Delete
    • ';\r\n }\r\n\r\n buildMenu += '
    ';\r\n return buildMenu;\r\n }\r\n\r\n rightClick() {\r\n $('[data-action=\"toggleMenu\"]').on('mousedown', event => {\r\n event.preventDefault();\r\n if ($(document).find('#fileOptionMenu').is(':visible')) {\r\n $('body').trigger('click');\r\n return;\r\n }\r\n this.showMenu(event);\r\n });\r\n $('#file_listing > tbody td').on('contextmenu', event => {\r\n this.showMenu(event);\r\n });\r\n }\r\n\r\n showMenu(event) {\r\n const parent = $(event.target).closest('tr');\r\n const menu = $(this.makeMenu(parent));\r\n\r\n if (parent.data('type') === 'disabled') return;\r\n event.preventDefault();\r\n\r\n $(menu).appendTo('body');\r\n $(menu).data('invokedOn', $(event.target)).show().css({\r\n position: 'absolute',\r\n left: event.pageX - 150,\r\n top: event.pageY,\r\n });\r\n\r\n this.activeLine = parent;\r\n this.activeLine.addClass('active');\r\n\r\n // Handle Events\r\n const Actions = new ActionsClass(parent, menu);\r\n if (Pterodactyl.permissions.moveFiles) {\r\n $(menu).find('li[data-action=\"move\"]').unbind().on('click', e => {\r\n e.preventDefault();\r\n Actions.move();\r\n });\r\n $(menu).find('li[data-action=\"rename\"]').unbind().on('click', e => {\r\n e.preventDefault();\r\n Actions.rename();\r\n });\r\n }\r\n\r\n if (Pterodactyl.permissions.copyFiles) {\r\n $(menu).find('li[data-action=\"copy\"]').unbind().on('click', e => {\r\n e.preventDefault();\r\n Actions.copy();\r\n });\r\n }\r\n\r\n if (Pterodactyl.permissions.compressFiles) {\r\n if (parent.data('type') === 'folder') {\r\n $(menu).find('li[data-action=\"compress\"]').removeClass('hidden');\r\n }\r\n $(menu).find('li[data-action=\"compress\"]').unbind().on('click', e => {\r\n e.preventDefault();\r\n Actions.compress();\r\n });\r\n }\r\n\r\n if (Pterodactyl.permissions.decompressFiles) {\r\n if (_.without(['application/zip', 'application/gzip', 'application/x-gzip'], parent.data('mime')).length < 3) {\r\n $(menu).find('li[data-action=\"decompress\"]').removeClass('hidden');\r\n }\r\n $(menu).find('li[data-action=\"decompress\"]').unbind().on('click', e => {\r\n e.preventDefault();\r\n Actions.decompress();\r\n });\r\n }\r\n\r\n if (Pterodactyl.permissions.createFiles) {\r\n $(menu).find('li[data-action=\"folder\"]').unbind().on('click', e => {\r\n e.preventDefault();\r\n Actions.folder();\r\n });\r\n }\r\n\r\n if (Pterodactyl.permissions.downloadFiles) {\r\n if (parent.data('type') === 'file') {\r\n $(menu).find('li[data-action=\"download\"]').removeClass('hidden');\r\n }\r\n $(menu).find('li[data-action=\"download\"]').unbind().on('click', e => {\r\n e.preventDefault();\r\n Actions.download();\r\n });\r\n }\r\n\r\n if (Pterodactyl.permissions.deleteFiles) {\r\n $(menu).find('li[data-action=\"delete\"]').unbind().on('click', e => {\r\n e.preventDefault();\r\n Actions.delete();\r\n });\r\n }\r\n\r\n $(window).unbind().on('click', event => {\r\n if($(event.target).is('.disable-menu-hide')) {\r\n event.preventDefault();\r\n return;\r\n }\r\n $(menu).unbind().remove();\r\n if(!_.isNull(this.activeLine)) this.activeLine.removeClass('active');\r\n });\r\n }\r\n\r\n directoryClick() {\r\n $('a[data-action=\"directory-view\"]').on('click', function (event) {\r\n event.preventDefault();\r\n\r\n const path = $(this).parent().data('path') || '';\r\n const name = $(this).parent().data('name') || '';\r\n\r\n window.location.hash = encodeURIComponent(path + name);\r\n Files.list();\r\n });\r\n }\r\n}\r\n\r\nwindow.ContextMenu = new ContextMenuClass;\r\n","\"use strict\";\r\n\r\n// Copyright (c) 2015 - 2017 Dane Everitt \r\n//\r\n// Permission is hereby granted, free of charge, to any person obtaining a copy\r\n// of this software and associated documentation files (the \"Software\"), to deal\r\n// in the Software without restriction, including without limitation the rights\r\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n// copies of the Software, and to permit persons to whom the Software is\r\n// furnished to do so, subject to the following conditions:\r\n//\r\n// The above copyright notice and this permission notice shall be included in all\r\n// copies or substantial portions of the Software.\r\n//\r\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\r\n// SOFTWARE.\r\nclass FileManager {\r\n constructor() {\r\n this.list(this.decodeHash());\r\n }\r\n\r\n list(path, next) {\r\n if (_.isUndefined(path)) {\r\n path = this.decodeHash();\r\n }\r\n\r\n this.loader(true);\r\n $.ajax({\r\n type: 'POST',\r\n url: Pterodactyl.meta.directoryList,\r\n headers: {\r\n 'X-CSRF-Token': Pterodactyl.meta.csrftoken,\r\n },\r\n data: {\r\n directory: path,\r\n },\r\n }).done(data => {\r\n this.loader(false);\r\n $('#load_files').slideUp(10).html(data).slideDown(10, () => {\r\n ContextMenu.run();\r\n this.reloadFilesButton();\r\n this.addFolderButton();\r\n this.selectItem();\r\n this.selectAll();\r\n this.selectiveDeletion();\r\n this.selectRow();\r\n if (_.isFunction(next)) {\r\n return next();\r\n }\r\n });\r\n $('#internal_alert').slideUp();\r\n\r\n if (typeof Siofu === 'object') {\r\n Siofu.listenOnInput(document.getElementById(\"files_touch_target\"));\r\n }\r\n }).fail(jqXHR => {\r\n this.loader(false);\r\n if (_.isFunction(next)) {\r\n return next(new Error('Failed to load file listing.'));\r\n }\r\n\r\n if ((path !== '' && path !== '/') && jqXHR.status === 404) {\r\n return this.list('', next);\r\n }\r\n\r\n swal({\r\n type: 'error',\r\n title: 'File Error',\r\n text: jqXHR.responseJSON.errors[0].detail || 'An error occurred while attempting to process this request. Please try again.',\r\n });\r\n console.error(jqXHR);\r\n });\r\n }\r\n\r\n loader(show) {\r\n if (show){\r\n $('.file-overlay').fadeIn(100);\r\n } else {\r\n $('.file-overlay').fadeOut(100);\r\n }\r\n }\r\n\r\n reloadFilesButton() {\r\n $('i[data-action=\"reload-files\"]').unbind().on('click', () => {\r\n $('i[data-action=\"reload-files\"]').addClass('fa-spin');\r\n this.list();\r\n });\r\n }\r\n\r\n selectItem() {\r\n $('[data-action=\"addSelection\"]').on('click', event => {\r\n event.preventDefault();\r\n });\r\n }\r\n\r\n selectAll() {\r\n $('[data-action=\"selectAll\"]').on('click', event => {\r\n event.preventDefault();\r\n });\r\n }\r\n\r\n selectiveDeletion() {\r\n $('[data-action=\"selective-deletion\"]').on('mousedown', event => {\r\n new ActionsClass().deleteSelected();\r\n });\r\n }\r\n\r\n addFolderButton() {\r\n $('[data-action=\"add-folder\"]').unbind().on('click', () => {\r\n new ActionsClass().folder($('#file_listing').data('current-dir') || '/');\r\n });\r\n }\r\n\r\n selectRow() {\r\n $('#file_listing tr').on('mousedown', event => {\r\n if (event.which === 1) {\r\n if ($(event.target).is('th') || $(event.target).is('input[data-action=\"selectAll\"]')) {\r\n new ActionsClass().highlightAll(event);\r\n } else if ($(event.target).is('td') || $(event.target).is('input[data-action=\"addSelection\"]')) {\r\n new ActionsClass().toggleHighlight(event);\r\n }\r\n\r\n new ActionsClass().toggleMassActions();\r\n }\r\n });\r\n }\r\n\r\n decodeHash() {\r\n return decodeURIComponent(window.location.hash.substring(1));\r\n }\r\n\r\n}\r\n\r\nwindow.Files = new FileManager;\r\n"]} \ No newline at end of file diff --git a/public/themes/pterodactyl/js/frontend/files/src/actions.js b/public/themes/pterodactyl/js/frontend/files/src/actions.js deleted file mode 100644 index 244bcaab..00000000 --- a/public/themes/pterodactyl/js/frontend/files/src/actions.js +++ /dev/null @@ -1,549 +0,0 @@ -"use strict"; - -// Copyright (c) 2015 - 2017 Dane Everitt -// -// 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. -class ActionsClass { - constructor(element, menu) { - this.element = element; - this.menu = menu; - } - - destroy() { - this.element = undefined; - } - - sanitizedString(value) { - return $('
    ').text(value).html(); - } - - folder(path) { - let inputValue - if (path) { - inputValue = path - } else { - const nameBlock = $(this.element).find('td[data-identifier="name"]'); - const currentName = decodeURIComponent(nameBlock.data('name')); - const currentPath = decodeURIComponent(nameBlock.data('path')); - - if ($(this.element).data('type') === 'file') { - inputValue = currentPath; - } else { - inputValue = `${currentPath}${currentName}/`; - } - } - - swal({ - type: 'input', - title: 'Create Folder', - text: 'Please enter the path and folder name below.', - showCancelButton: true, - showConfirmButton: true, - closeOnConfirm: false, - showLoaderOnConfirm: true, - inputValue: inputValue - }, (val) => { - if (val === false) { - return false; - } - - $.ajax({ - type: 'POST', - headers: { - 'X-Access-Token': Pterodactyl.server.daemonSecret, - 'X-Access-Server': Pterodactyl.server.uuid, - }, - contentType: 'application/json; charset=utf-8', - url: `${Pterodactyl.node.scheme}://${Pterodactyl.node.fqdn}:${Pterodactyl.node.daemonListen}/v1/server/file/folder`, - timeout: 10000, - data: JSON.stringify({ - path: val, - }), - }).done(data => { - swal.close(); - Files.list(); - }).fail(jqXHR => { - console.error(jqXHR); - var error = 'An error occurred while trying to process this request.'; - if (typeof jqXHR.responseJSON !== 'undefined' && typeof jqXHR.responseJSON.error !== 'undefined') { - error = jqXHR.responseJSON.error; - } - swal({ - type: 'error', - title: '', - text: error, - }); - }); - }); - } - - move() { - const nameBlock = $(this.element).find('td[data-identifier="name"]'); - const currentName = decodeURIComponent(nameBlock.attr('data-name')); - const currentPath = decodeURIComponent(nameBlock.data('path')); - - swal({ - type: 'input', - title: 'Move File', - text: 'Please enter the new path for the file below.', - showCancelButton: true, - showConfirmButton: true, - closeOnConfirm: false, - showLoaderOnConfirm: true, - inputValue: `${currentPath}${currentName}`, - }, (val) => { - if (val === false) { - return false; - } - - $.ajax({ - type: 'POST', - headers: { - 'X-Access-Token': Pterodactyl.server.daemonSecret, - 'X-Access-Server': Pterodactyl.server.uuid, - }, - contentType: 'application/json; charset=utf-8', - url: `${Pterodactyl.node.scheme}://${Pterodactyl.node.fqdn}:${Pterodactyl.node.daemonListen}/v1/server/file/move`, - timeout: 10000, - data: JSON.stringify({ - from: `${currentPath}${currentName}`, - to: `${val}`, - }), - }).done(data => { - nameBlock.parent().addClass('warning').delay(200).fadeOut(); - swal.close(); - }).fail(jqXHR => { - console.error(jqXHR); - var error = 'An error occurred while trying to process this request.'; - if (typeof jqXHR.responseJSON !== 'undefined' && typeof jqXHR.responseJSON.error !== 'undefined') { - error = jqXHR.responseJSON.error; - } - swal({ - type: 'error', - title: '', - text: error, - }); - }); - }); - - } - - rename() { - const nameBlock = $(this.element).find('td[data-identifier="name"]'); - const currentLink = nameBlock.find('a'); - const currentName = decodeURIComponent(nameBlock.attr('data-name')); - const attachEditor = ` - - - `; - - nameBlock.html(attachEditor); - const inputField = nameBlock.find('input'); - const inputLoader = nameBlock.find('.input-loader'); - - inputField.focus(); - inputField.on('blur keydown', e => { - // Save Field - if ( - (e.type === 'keydown' && e.which === 27) - || e.type === 'blur' - || (e.type === 'keydown' && e.which === 13 && currentName === inputField.val()) - ) { - if (!_.isEmpty(currentLink)) { - nameBlock.html(currentLink); - } else { - nameBlock.html(currentName); - } - inputField.remove(); - ContextMenu.unbind().run(); - return; - } - - if (e.type === 'keydown' && e.which !== 13) return; - - inputLoader.show(); - const currentPath = decodeURIComponent(nameBlock.data('path')); - - $.ajax({ - type: 'POST', - headers: { - 'X-Access-Token': Pterodactyl.server.daemonSecret, - 'X-Access-Server': Pterodactyl.server.uuid, - }, - contentType: 'application/json; charset=utf-8', - url: `${Pterodactyl.node.scheme}://${Pterodactyl.node.fqdn}:${Pterodactyl.node.daemonListen}/v1/server/file/rename`, - timeout: 10000, - data: JSON.stringify({ - from: `${currentPath}${currentName}`, - to: `${currentPath}${inputField.val()}`, - }), - }).done(data => { - nameBlock.attr('data-name', inputField.val()); - if (!_.isEmpty(currentLink)) { - let newLink = currentLink.attr('href'); - if (nameBlock.parent().data('type') !== 'folder') { - newLink = newLink.substr(0, newLink.lastIndexOf('/')) + '/' + inputField.val(); - } - currentLink.attr('href', newLink); - nameBlock.html( - currentLink.html(inputField.val()) - ); - } else { - nameBlock.html(inputField.val()); - } - inputField.remove(); - }).fail(jqXHR => { - console.error(jqXHR); - var error = 'An error occurred while trying to process this request.'; - if (typeof jqXHR.responseJSON !== 'undefined' && typeof jqXHR.responseJSON.error !== 'undefined') { - error = jqXHR.responseJSON.error; - } - nameBlock.addClass('has-error').delay(2000).queue(() => { - nameBlock.removeClass('has-error').dequeue(); - }); - inputField.popover({ - animation: true, - placement: 'top', - content: error, - title: 'Save Error' - }).popover('show'); - }).always(() => { - inputLoader.remove(); - ContextMenu.unbind().run(); - }); - }); - } - - copy() { - const nameBlock = $(this.element).find('td[data-identifier="name"]'); - const currentName = decodeURIComponent(nameBlock.attr('data-name')); - const currentPath = decodeURIComponent(nameBlock.data('path')); - - swal({ - type: 'input', - title: 'Copy File', - text: 'Please enter the new path for the copied file below.', - showCancelButton: true, - showConfirmButton: true, - closeOnConfirm: false, - showLoaderOnConfirm: true, - inputValue: `${currentPath}${currentName}`, - }, (val) => { - if (val === false) { - return false; - } - - $.ajax({ - type: 'POST', - headers: { - 'X-Access-Token': Pterodactyl.server.daemonSecret, - 'X-Access-Server': Pterodactyl.server.uuid, - }, - contentType: 'application/json; charset=utf-8', - url: `${Pterodactyl.node.scheme}://${Pterodactyl.node.fqdn}:${Pterodactyl.node.daemonListen}/v1/server/file/copy`, - timeout: 10000, - data: JSON.stringify({ - from: `${currentPath}${currentName}`, - to: `${val}`, - }), - }).done(data => { - swal({ - type: 'success', - title: '', - text: 'File successfully copied.' - }); - Files.list(); - }).fail(jqXHR => { - console.error(jqXHR); - var error = 'An error occurred while trying to process this request.'; - if (typeof jqXHR.responseJSON !== 'undefined' && typeof jqXHR.responseJSON.error !== 'undefined') { - error = jqXHR.responseJSON.error; - } - swal({ - type: 'error', - title: '', - text: error, - }); - }); - }); - } - - download() { - const nameBlock = $(this.element).find('td[data-identifier="name"]'); - const fileName = decodeURIComponent(nameBlock.attr('data-name')); - const filePath = decodeURIComponent(nameBlock.data('path')); - - window.location = `/server/${Pterodactyl.server.uuidShort}/files/download/${filePath}${fileName}`; - } - - delete() { - const nameBlock = $(this.element).find('td[data-identifier="name"]'); - const delPath = decodeURIComponent(nameBlock.data('path')); - const delName = decodeURIComponent(nameBlock.data('name')); - - swal({ - type: 'warning', - title: '', - text: 'Are you sure you want to delete ' + this.sanitizedString(delName) + '?', - html: true, - showCancelButton: true, - showConfirmButton: true, - closeOnConfirm: false, - showLoaderOnConfirm: true - }, () => { - $.ajax({ - type: 'POST', - headers: { - 'X-Access-Token': Pterodactyl.server.daemonSecret, - 'X-Access-Server': Pterodactyl.server.uuid, - }, - contentType: 'application/json; charset=utf-8', - url: `${Pterodactyl.node.scheme}://${Pterodactyl.node.fqdn}:${Pterodactyl.node.daemonListen}/v1/server/file/delete`, - timeout: 10000, - data: JSON.stringify({ - items: [`${delPath}${delName}`] - }), - }).done(data => { - nameBlock.parent().addClass('warning').delay(200).fadeOut(); - swal({ - type: 'success', - title: 'File Deleted' - }); - }).fail(jqXHR => { - console.error(jqXHR); - swal({ - type: 'error', - title: 'Whoops!', - html: true, - text: 'An error occurred while attempting to delete this file. Please try again.', - }); - }); - }); - } - - toggleMassActions() { - if ($('#file_listing input[type="checkbox"]:checked').length) { - $('#mass_actions').removeClass('disabled'); - } else { - $('#mass_actions').addClass('disabled'); - } - } - - toggleHighlight(event) { - const parent = $(event.currentTarget); - const item = $(event.currentTarget).find('input'); - - if($(item).is(':checked')) { - $(item).prop('checked', false); - parent.removeClass('warning').delay(200); - } else { - $(item).prop('checked', true); - parent.addClass('warning').delay(200); - } - } - - highlightAll(event) { - let parent; - const item = $(event.currentTarget).find('input'); - - if($(item).is(':checked')) { - $('#file_listing input[type=checkbox]').prop('checked', false); - $('#file_listing input[data-action="addSelection"]').each(function() { - parent = $(this).closest('tr'); - parent.removeClass('warning').delay(200); - }); - } else { - $('#file_listing input[type=checkbox]').prop('checked', true); - $('#file_listing input[data-action="addSelection"]').each(function() { - parent = $(this).closest('tr'); - parent.addClass('warning').delay(200); - }); - } - } - - deleteSelected() { - let selectedItems = []; - let selectedItemsElements = []; - let parent; - let nameBlock; - let delLocation; - - $('#file_listing input[data-action="addSelection"]:checked').each(function() { - parent = $(this).closest('tr'); - nameBlock = $(parent).find('td[data-identifier="name"]'); - delLocation = decodeURIComponent(nameBlock.data('path')) + decodeURIComponent(nameBlock.data('name')); - - selectedItems.push(delLocation); - selectedItemsElements.push(parent); - }); - - if (selectedItems.length != 0) - { - let formattedItems = ""; - let i = 0; - let self = this; - - $.each(selectedItems, function(key, value) { - formattedItems += ("" + self.sanitizedString(value) + ", "); - i++; - return i < 5; - }); - - formattedItems = formattedItems.slice(0, -2); - if (selectedItems.length > 5) { - formattedItems += ', and ' + (selectedItems.length - 5) + ' other(s)'; - } - - swal({ - type: 'warning', - title: '', - text: 'Are you sure you want to delete the following files: ' + formattedItems + '?', - html: true, - showCancelButton: true, - showConfirmButton: true, - closeOnConfirm: false, - showLoaderOnConfirm: true - }, () => { - $.ajax({ - type: 'POST', - headers: { - 'X-Access-Token': Pterodactyl.server.daemonSecret, - 'X-Access-Server': Pterodactyl.server.uuid, - }, - contentType: 'application/json; charset=utf-8', - url: `${Pterodactyl.node.scheme}://${Pterodactyl.node.fqdn}:${Pterodactyl.node.daemonListen}/v1/server/file/delete`, - timeout: 10000, - data: JSON.stringify({ - items: selectedItems - }), - }).done(data => { - $('#file_listing input:checked').each(function() { - $(this).prop('checked', false); - }); - - $.each(selectedItemsElements, function() { - $(this).addClass('warning').delay(200).fadeOut(); - }) - - swal({ - type: 'success', - title: 'Files Deleted' - }); - }).fail(jqXHR => { - console.error(jqXHR); - swal({ - type: 'error', - title: 'Whoops!', - html: true, - text: 'An error occurred while attempting to delete these files. Please try again.', - }); - }); - }); - } else { - swal({ - type: 'warning', - title: '', - text: 'Please select files/folders to delete.', - }); - } - } - - decompress() { - const nameBlock = $(this.element).find('td[data-identifier="name"]'); - const compPath = decodeURIComponent(nameBlock.data('path')); - const compName = decodeURIComponent(nameBlock.data('name')); - - swal({ - title: ' Decompressing...', - text: 'This might take a few seconds to complete.', - html: true, - allowOutsideClick: false, - allowEscapeKey: false, - showConfirmButton: false, - }); - - $.ajax({ - type: 'POST', - url: `${Pterodactyl.node.scheme}://${Pterodactyl.node.fqdn}:${Pterodactyl.node.daemonListen}/v1/server/file/decompress`, - headers: { - 'X-Access-Token': Pterodactyl.server.daemonSecret, - 'X-Access-Server': Pterodactyl.server.uuid, - }, - contentType: 'application/json; charset=utf-8', - data: JSON.stringify({ - files: `${compPath}${compName}` - }) - }).done(data => { - swal.close(); - Files.list(compPath); - }).fail(jqXHR => { - console.error(jqXHR); - var error = 'An error occurred while trying to process this request.'; - if (typeof jqXHR.responseJSON !== 'undefined' && typeof jqXHR.responseJSON.error !== 'undefined') { - error = jqXHR.responseJSON.error; - } - swal({ - type: 'error', - title: 'Whoops!', - html: true, - text: error - }); - }); - } - - compress() { - const nameBlock = $(this.element).find('td[data-identifier="name"]'); - const compPath = decodeURIComponent(nameBlock.data('path')); - const compName = decodeURIComponent(nameBlock.data('name')); - - $.ajax({ - type: 'POST', - url: `${Pterodactyl.node.scheme}://${Pterodactyl.node.fqdn}:${Pterodactyl.node.daemonListen}/v1/server/file/compress`, - headers: { - 'X-Access-Token': Pterodactyl.server.daemonSecret, - 'X-Access-Server': Pterodactyl.server.uuid, - }, - contentType: 'application/json; charset=utf-8', - data: JSON.stringify({ - files: `${compPath}${compName}`, - to: compPath.toString() - }) - }).done(data => { - Files.list(compPath, err => { - if (err) return; - const fileListing = $('#file_listing').find(`[data-name="${data.saved_as}"]`).parent(); - fileListing.addClass('success pulsate').delay(3000).queue(() => { - fileListing.removeClass('success pulsate').dequeue(); - }); - }); - }).fail(jqXHR => { - console.error(jqXHR); - var error = 'An error occurred while trying to process this request.'; - if (typeof jqXHR.responseJSON !== 'undefined' && typeof jqXHR.responseJSON.error !== 'undefined') { - error = jqXHR.responseJSON.error; - } - swal({ - type: 'error', - title: 'Whoops!', - html: true, - text: this.sanitizedString(error) - }); - }); - } -} diff --git a/public/themes/pterodactyl/js/frontend/files/src/contextmenu.js b/public/themes/pterodactyl/js/frontend/files/src/contextmenu.js deleted file mode 100644 index 6796f8b0..00000000 --- a/public/themes/pterodactyl/js/frontend/files/src/contextmenu.js +++ /dev/null @@ -1,203 +0,0 @@ -"use strict"; - -// Copyright (c) 2015 - 2017 Dane Everitt -// -// 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. -class ContextMenuClass { - constructor() { - this.activeLine = null; - } - - run() { - this.directoryClick(); - this.rightClick(); - } - - makeMenu(parent) { - $(document).find('#fileOptionMenu').remove(); - if (!_.isNull(this.activeLine)) this.activeLine.removeClass('active'); - - let newFilePath = $('#file_listing').data('current-dir'); - if (parent.data('type') === 'folder') { - const nameBlock = parent.find('td[data-identifier="name"]'); - const currentName = decodeURIComponent(nameBlock.attr('data-name')); - const currentPath = decodeURIComponent(nameBlock.data('path')); - newFilePath = `${currentPath}${currentName}`; - } - - let buildMenu = ''; - return buildMenu; - } - - rightClick() { - $('[data-action="toggleMenu"]').on('mousedown', event => { - event.preventDefault(); - if ($(document).find('#fileOptionMenu').is(':visible')) { - $('body').trigger('click'); - return; - } - this.showMenu(event); - }); - $('#file_listing > tbody td').on('contextmenu', event => { - this.showMenu(event); - }); - } - - showMenu(event) { - const parent = $(event.target).closest('tr'); - const menu = $(this.makeMenu(parent)); - - if (parent.data('type') === 'disabled') return; - event.preventDefault(); - - $(menu).appendTo('body'); - $(menu).data('invokedOn', $(event.target)).show().css({ - position: 'absolute', - left: event.pageX - 150, - top: event.pageY, - }); - - this.activeLine = parent; - this.activeLine.addClass('active'); - - // Handle Events - const Actions = new ActionsClass(parent, menu); - if (Pterodactyl.permissions.moveFiles) { - $(menu).find('li[data-action="move"]').unbind().on('click', e => { - e.preventDefault(); - Actions.move(); - }); - $(menu).find('li[data-action="rename"]').unbind().on('click', e => { - e.preventDefault(); - Actions.rename(); - }); - } - - if (Pterodactyl.permissions.copyFiles) { - $(menu).find('li[data-action="copy"]').unbind().on('click', e => { - e.preventDefault(); - Actions.copy(); - }); - } - - if (Pterodactyl.permissions.compressFiles) { - if (parent.data('type') === 'folder') { - $(menu).find('li[data-action="compress"]').removeClass('hidden'); - } - $(menu).find('li[data-action="compress"]').unbind().on('click', e => { - e.preventDefault(); - Actions.compress(); - }); - } - - if (Pterodactyl.permissions.decompressFiles) { - if (_.without(['application/zip', 'application/gzip', 'application/x-gzip'], parent.data('mime')).length < 3) { - $(menu).find('li[data-action="decompress"]').removeClass('hidden'); - } - $(menu).find('li[data-action="decompress"]').unbind().on('click', e => { - e.preventDefault(); - Actions.decompress(); - }); - } - - if (Pterodactyl.permissions.createFiles) { - $(menu).find('li[data-action="folder"]').unbind().on('click', e => { - e.preventDefault(); - Actions.folder(); - }); - } - - if (Pterodactyl.permissions.downloadFiles) { - if (parent.data('type') === 'file') { - $(menu).find('li[data-action="download"]').removeClass('hidden'); - } - $(menu).find('li[data-action="download"]').unbind().on('click', e => { - e.preventDefault(); - Actions.download(); - }); - } - - if (Pterodactyl.permissions.deleteFiles) { - $(menu).find('li[data-action="delete"]').unbind().on('click', e => { - e.preventDefault(); - Actions.delete(); - }); - } - - $(window).unbind().on('click', event => { - if($(event.target).is('.disable-menu-hide')) { - event.preventDefault(); - return; - } - $(menu).unbind().remove(); - if(!_.isNull(this.activeLine)) this.activeLine.removeClass('active'); - }); - } - - directoryClick() { - $('a[data-action="directory-view"]').on('click', function (event) { - event.preventDefault(); - - const path = $(this).parent().data('path') || ''; - const name = $(this).parent().data('name') || ''; - - window.location.hash = encodeURIComponent(path + name); - Files.list(); - }); - } -} - -window.ContextMenu = new ContextMenuClass; diff --git a/public/themes/pterodactyl/js/frontend/files/src/index.js b/public/themes/pterodactyl/js/frontend/files/src/index.js deleted file mode 100644 index 83c1460b..00000000 --- a/public/themes/pterodactyl/js/frontend/files/src/index.js +++ /dev/null @@ -1,139 +0,0 @@ -"use strict"; - -// Copyright (c) 2015 - 2017 Dane Everitt -// -// 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. -class FileManager { - constructor() { - this.list(this.decodeHash()); - } - - list(path, next) { - if (_.isUndefined(path)) { - path = this.decodeHash(); - } - - this.loader(true); - $.ajax({ - type: 'POST', - url: Pterodactyl.meta.directoryList, - headers: { - 'X-CSRF-Token': Pterodactyl.meta.csrftoken, - }, - data: { - directory: path, - }, - }).done(data => { - this.loader(false); - $('#load_files').slideUp(10).html(data).slideDown(10, () => { - ContextMenu.run(); - this.reloadFilesButton(); - this.addFolderButton(); - this.selectItem(); - this.selectAll(); - this.selectiveDeletion(); - this.selectRow(); - if (_.isFunction(next)) { - return next(); - } - }); - $('#internal_alert').slideUp(); - - if (typeof Siofu === 'object') { - Siofu.listenOnInput(document.getElementById("files_touch_target")); - } - }).fail(jqXHR => { - this.loader(false); - if (_.isFunction(next)) { - return next(new Error('Failed to load file listing.')); - } - - if ((path !== '' && path !== '/') && jqXHR.status === 404) { - return this.list('', next); - } - - swal({ - type: 'error', - title: 'File Error', - text: jqXHR.responseJSON.errors[0].detail || 'An error occurred while attempting to process this request. Please try again.', - }); - console.error(jqXHR); - }); - } - - loader(show) { - if (show){ - $('.file-overlay').fadeIn(100); - } else { - $('.file-overlay').fadeOut(100); - } - } - - reloadFilesButton() { - $('i[data-action="reload-files"]').unbind().on('click', () => { - $('i[data-action="reload-files"]').addClass('fa-spin'); - this.list(); - }); - } - - selectItem() { - $('[data-action="addSelection"]').on('click', event => { - event.preventDefault(); - }); - } - - selectAll() { - $('[data-action="selectAll"]').on('click', event => { - event.preventDefault(); - }); - } - - selectiveDeletion() { - $('[data-action="selective-deletion"]').on('mousedown', event => { - new ActionsClass().deleteSelected(); - }); - } - - addFolderButton() { - $('[data-action="add-folder"]').unbind().on('click', () => { - new ActionsClass().folder($('#file_listing').data('current-dir') || '/'); - }); - } - - selectRow() { - $('#file_listing tr').on('mousedown', event => { - if (event.which === 1) { - if ($(event.target).is('th') || $(event.target).is('input[data-action="selectAll"]')) { - new ActionsClass().highlightAll(event); - } else if ($(event.target).is('td') || $(event.target).is('input[data-action="addSelection"]')) { - new ActionsClass().toggleHighlight(event); - } - - new ActionsClass().toggleMassActions(); - } - }); - } - - decodeHash() { - return decodeURIComponent(window.location.hash.substring(1)); - } - -} - -window.Files = new FileManager; diff --git a/public/themes/pterodactyl/js/frontend/files/upload.js b/public/themes/pterodactyl/js/frontend/files/upload.js deleted file mode 100644 index 873fbf63..00000000 --- a/public/themes/pterodactyl/js/frontend/files/upload.js +++ /dev/null @@ -1,162 +0,0 @@ -// Copyright (c) 2015 - 2017 Dane Everitt -// -// 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. -(function initUploader() { - var notifyUploadSocketError = false; - uploadSocket = io(Pterodactyl.node.scheme + '://' + Pterodactyl.node.fqdn + ':' + Pterodactyl.node.daemonListen + '/v1/upload/' + Pterodactyl.server.uuid, { - 'query': 'token=' + Pterodactyl.server.daemonSecret, - }); - - uploadSocket.io.on('connect_error', function (err) { - if(typeof notifyUploadSocketError !== 'object') { - notifyUploadSocketError = $.notify({ - message: 'There was an error attempting to establish a connection to the uploader endpoint.

    ' + err, - }, { - type: 'danger', - delay: 0 - }); - } - }); - - uploadSocket.on('error', err => { - Siofu.destroy(); - console.error(err); - }); - - uploadSocket.on('connect', function () { - if (notifyUploadSocketError !== false) { - notifyUploadSocketError.close(); - notifyUploadSocketError = false; - } - }); - - window.Siofu = new SocketIOFileUpload(uploadSocket); - Siofu.listenOnDrop(document.getElementById("load_files")); - - if (document.getElementById("files_touch_target")) { - Siofu.listenOnInput(document.getElementById("files_touch_target")); - } - - window.addEventListener('dragover', function (event) { - event.preventDefault(); - }, false); - - window.addEventListener('drop', function (event) { - event.preventDefault(); - }, false); - - window.foldersDetectedInDrag = function (event) { - var folderDetected = false; - var files = event.dataTransfer.files; - for (var i = 0, f; f = files[i]; i++) { - if (!f.type && f.size === 0) { - return true; - } - } - - return folderDetected; - }; - - var dropCounter = 0; - $('#load_files').bind({ - dragenter: function (event) { - event.preventDefault(); - dropCounter++; - $(this).addClass('hasFileHover'); - }, - dragleave: function (event) { - dropCounter--; - if (dropCounter === 0) { - $(this).removeClass('hasFileHover'); - } - }, - drop: function (event) { - if (window.foldersDetectedInDrag(event.originalEvent)) { - $.notify({ - message: 'Folder uploads are not supported. Please use SFTP to upload whole directories.', - }, { - type: 'warning', - delay: 0 - }); - } - - dropCounter = 0; - $(this).removeClass('hasFileHover'); - } - }); - - Siofu.addEventListener('start', function (event) { - window.onbeforeunload = function () { - return 'A file upload in in progress, are you sure you want to continue?'; - }; - event.file.meta.path = $('#file_listing').data('current-dir'); - event.file.meta.identifier = Math.random().toString(36).slice(2); - - $('#append_files_to').append(' \ - \ - ' + event.file.name + ' \ -   \ - \ - \ -
    \ -
    \ -
    \ - \ - \ - '); - }); - - Siofu.addEventListener('progress', function(event) { - window.onbeforeunload = function () { - return 'A file upload in in progress, are you sure you want to continue?'; - }; - var percent = event.bytesLoaded / event.file.size * 100; - if (percent >= 100) { - $('.prog-bar-' + event.file.meta.identifier).css('width', '100%').removeClass('progress-bar-info').addClass('progress-bar-success').parent().removeClass('active'); - } else { - $('.prog-bar-' + event.file.meta.identifier).css('width', percent + '%'); - } - }); - - // Do something when a file is uploaded: - Siofu.addEventListener('complete', function(event) { - window.onbeforeunload = function () {}; - if (!event.success) { - $('.prog-bar-' + event.file.meta.identifier).css('width', '100%').removeClass('progress-bar-info').addClass('progress-bar-danger'); - $.notify({ - message: 'An error was encountered while attempting to upload this file.' - }, { - type: 'danger', - delay: 5000 - }); - } - }); - - Siofu.addEventListener('error', function(event) { - window.onbeforeunload = function () {}; - console.error(event); - $('.prog-bar-' + event.file.meta.identifier).css('width', '100%').removeClass('progress-bar-info').addClass('progress-bar-danger'); - $.notify({ - message: 'An error was encountered while attempting to upload this file: ' + event.message + '.', - }, { - type: 'danger', - delay: 8000 - }); - }); -})(); diff --git a/public/themes/pterodactyl/js/frontend/server.socket.js b/public/themes/pterodactyl/js/frontend/server.socket.js deleted file mode 100644 index 09063351..00000000 --- a/public/themes/pterodactyl/js/frontend/server.socket.js +++ /dev/null @@ -1,131 +0,0 @@ -// Copyright (c) 2015 - 2017 Dane Everitt -// -// 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. -$('#console-popout').on('click', function (event) { - event.preventDefault(); - window.open($(this).attr('href'), 'Pterodactyl Console', 'width=800,height=400'); -}); -var Server = (function () { - - function initSocket() { - if (typeof $.notifyDefaults !== 'function') { - console.error('Notify does not appear to be loaded.'); - return; - } - - if (typeof io !== 'function') { - console.error('Socket.io is required to use this panel.'); - return; - } - - $.notifyDefaults({ - placement: { - from: 'bottom', - align: 'right' - }, - newest_on_top: true, - delay: 2000, - offset: { - x: 20, - y: 60, - }, - animate: { - enter: 'animated bounceInUp', - exit: 'animated bounceOutDown' - } - }); - - var notifySocketError = false; - - window.Socket = io(Pterodactyl.node.scheme + '://' + Pterodactyl.node.fqdn + ':' + Pterodactyl.node.daemonListen + '/v1/ws/' + Pterodactyl.server.uuid, { - 'query': 'token=' + Pterodactyl.server.daemonSecret, - }); - - Socket.on('error', function (err) { - if(typeof notifySocketError !== 'object') { - notifySocketError = $.notify({ - message: 'There was an error attempting to establish a WebSocket connection to the Daemon. This panel will not work as expected.

    ' + err, - }, { - type: 'danger', - delay: 0, - }); - } - setStatusIcon(999); - }); - - Socket.io.on('connect_error', function (err) { - if(typeof notifySocketError !== 'object') { - notifySocketError = $.notify({ - message: 'There was an error attempting to establish a WebSocket connection to the Daemon. This panel will not work as expected.

    ' + err, - }, { - type: 'danger', - delay: 0, - }); - } - setStatusIcon(999); - }); - - // Connected to Socket Successfully - Socket.on('connect', function () { - if (notifySocketError !== false) { - notifySocketError.close(); - notifySocketError = false; - } - }); - - Socket.on('initial status', function (data) { - setStatusIcon(data.status); - }); - - Socket.on('status', function (data) { - setStatusIcon(data.status); - }); - } - - function setStatusIcon(status) { - switch (status) { - case 0: - $('#server_status_icon').html(' Offline'); - break; - case 1: - $('#server_status_icon').html(' Online'); - break; - case 2: - $('#server_status_icon').html(' Starting'); - break; - case 3: - $('#server_status_icon').html(' Stopping'); - break; - default: - $('#server_status_icon').html(' Connection Error'); - break; - } - } - - return { - init: function () { - initSocket(); - }, - - setStatusIcon: setStatusIcon, - } - -})(); - -Server.init(); diff --git a/public/themes/pterodactyl/js/frontend/serverlist.js b/public/themes/pterodactyl/js/frontend/serverlist.js deleted file mode 100644 index 6e4d5c4e..00000000 --- a/public/themes/pterodactyl/js/frontend/serverlist.js +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright (c) 2015 - 2017 Dane Everitt -// -// 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. -(function updateServerStatus() { - var Status = { - 0: 'Offline', - 1: 'Online', - 2: 'Starting', - 3: 'Stopping' - }; - $('.dynamic-update').each(function (index, data) { - var element = $(this); - var serverShortUUID = $(this).data('server'); - - $.ajax({ - type: 'GET', - url: Router.route('index.status', { server: serverShortUUID }), - timeout: 5000, - headers: { - 'X-CSRF-TOKEN': $('meta[name="_token"]').attr('content'), - } - }).done(function (data) { - if (typeof data.status === 'undefined') { - element.find('[data-action="status"]').html('Error'); - return; - } - switch (data.status) { - case 0: - element.find('[data-action="status"]').html('Offline'); - break; - case 1: - element.find('[data-action="status"]').html('Online'); - break; - case 2: - element.find('[data-action="status"]').html('Starting'); - break; - case 3: - element.find('[data-action="status"]').html('Stopping'); - break; - case 20: - element.find('[data-action="status"]').html('Installing'); - break; - case 30: - element.find('[data-action="status"]').html('Suspended'); - break; - } - if (data.status > 0 && data.status < 4) { - var cpuMax = element.find('[data-action="cpu"]').data('cpumax'); - var currentCpu = data.proc.cpu.total; - if (cpuMax !== 0) { - currentCpu = parseFloat(((data.proc.cpu.total / cpuMax) * 100).toFixed(2).toString()); - } - if (data.status !== 0) { - var cpuMax = element.find('[data-action="cpu"]').data('cpumax'); - var currentCpu = data.proc.cpu.total; - if (cpuMax !== 0) { - currentCpu = parseFloat(((data.proc.cpu.total / cpuMax) * 100).toFixed(2).toString()); - } - element.find('[data-action="memory"]').html(parseInt(data.proc.memory.total / (1024 * 1024))); - element.find('[data-action="cpu"]').html(currentCpu); - element.find('[data-action="disk"]').html(parseInt(data.proc.disk.used)); - } else { - element.find('[data-action="memory"]').html('--'); - element.find('[data-action="cpu"]').html('--'); - element.find('[data-action="disk"]').html('--'); - } - } - }).fail(function (jqXHR) { - if (jqXHR.status === 504) { - element.find('[data-action="status"]').html('Gateway Timeout'); - } else { - element.find('[data-action="status"]').html('Error'); - } - }); - }).promise().done(function () { - setTimeout(updateServerStatus, 10000); - }); -})(); diff --git a/public/themes/pterodactyl/js/frontend/tasks/management-actions.js b/public/themes/pterodactyl/js/frontend/tasks/management-actions.js deleted file mode 100644 index 857c32cf..00000000 --- a/public/themes/pterodactyl/js/frontend/tasks/management-actions.js +++ /dev/null @@ -1,144 +0,0 @@ -// Copyright (c) 2015 - 2017 Dane Everitt -// -// 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. - -$(document).ready(function () { - $('[data-toggle="tooltip"]').tooltip(); - $('[data-action="delete-schedule"]').click(function () { - var self = $(this); - swal({ - type: 'error', - title: 'Delete Schedule?', - text: 'Are you sure you want to delete this schedule? There is no undo.', - showCancelButton: true, - allowOutsideClick: true, - closeOnConfirm: false, - confirmButtonText: 'Delete Schedule', - confirmButtonColor: '#d9534f', - showLoaderOnConfirm: true - }, function () { - $.ajax({ - method: 'DELETE', - url: Router.route('server.schedules.view', { - server: Pterodactyl.server.uuidShort, - schedule: self.data('schedule-id'), - }), - headers: { - 'X-CSRF-TOKEN': $('meta[name="_token"]').attr('content'), - } - }).done(function (data) { - swal({ - type: 'success', - title: '', - text: 'Schedule has been deleted.' - }); - self.parent().parent().slideUp(); - }).fail(function (jqXHR) { - console.error(jqXHR); - swal({ - type: 'error', - title: 'Whoops!', - text: 'An error occurred while attempting to delete this schedule.' - }); - }); - }); - }); - - $('[data-action="trigger-schedule"]').click(function (event) { - event.preventDefault(); - var self = $(this); - swal({ - type: 'info', - title: 'Trigger Schedule', - text: 'This will run the selected schedule now.', - showCancelButton: true, - allowOutsideClick: true, - closeOnConfirm: false, - confirmButtonText: 'Continue', - showLoaderOnConfirm: true - }, function () { - $.ajax({ - method: 'POST', - url: Router.route('server.schedules.trigger', { - server: Pterodactyl.server.uuidShort, - schedule: self.data('schedule-id'), - }), - headers: { - 'X-CSRF-TOKEN': $('meta[name="_token"]').attr('content'), - }, - }).done(function (data) { - swal({ - type: 'success', - title: '', - text: 'Schedule has been added to the next-run queue.' - }); - }).fail(function (jqXHR) { - console.error(jqXHR); - swal({ - type: 'error', - title: 'Whoops!', - text: 'An error occurred while attempting to trigger this schedule.' - }); - }); - }); - }); - - $('[data-action="toggle-schedule"]').click(function (event) { - var self = $(this); - swal({ - type: 'info', - title: 'Toggle Schedule', - text: 'This will toggle the selected schedule.', - showCancelButton: true, - allowOutsideClick: true, - closeOnConfirm: false, - confirmButtonText: 'Continue', - showLoaderOnConfirm: true - }, function () { - $.ajax({ - method: 'POST', - url: Router.route('server.schedules.toggle', { - server: Pterodactyl.server.uuidShort, - schedule: self.data('schedule-id'), - }), - headers: { - 'X-CSRF-TOKEN': $('meta[name="_token"]').attr('content'), - } - }).done(function (data) { - swal({ - type: 'success', - title: '', - text: 'Schedule has been toggled.' - }); - if (data.status !== 1) { - self.parent().parent().addClass('muted muted-hover'); - } else { - self.parent().parent().removeClass('muted muted-hover'); - } - }).fail(function (jqXHR) { - console.error(jqXHR); - swal({ - type: 'error', - title: 'Whoops!', - text: 'An error occurred while attempting to toggle this schedule.' - }); - }); - }); - }); -}); diff --git a/public/themes/pterodactyl/js/frontend/tasks/view-actions.js b/public/themes/pterodactyl/js/frontend/tasks/view-actions.js deleted file mode 100644 index 86b7f856..00000000 --- a/public/themes/pterodactyl/js/frontend/tasks/view-actions.js +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (c) 2015 - 2017 Dane Everitt -// -// 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. - -$(document).ready(function () { - function setupSelect2() { - $('select[name="tasks[time_value][]"]').select2(); - $('select[name="tasks[time_interval][]"]').select2(); - $('select[name="tasks[action][]"]').select2(); - } - - setupSelect2(); - - $('[data-action="update-field"]').on('change', function (event) { - event.preventDefault(); - var updateField = $(this).data('field'); - var selected = $(this).map(function (i, opt) { - return $(opt).val(); - }).toArray(); - if (selected.length === $(this).find('option').length) { - $('input[name=' + updateField + ']').val('*'); - } else { - $('input[name=' + updateField + ']').val(selected.join(',')); - } - }); - - $('button[data-action="add-new-task"]').on('click', function () { - if ($('#containsTaskList').find('.task-list-item').length >= 5) { - swal('Task Limit Reached', 'You may only assign a maximum of 5 tasks to one schedule.'); - return; - } - - var clone = $('div[data-target="task-clone"]').clone(); - clone.insertBefore('#taskAppendBefore').removeAttr('data-target'); - clone.find('select:first').attr('selected'); - clone.find('input').val(''); - clone.find('span.select2-container').remove(); - clone.find('div[data-attribute="remove-task-element"]').addClass('input-group').find('div.input-group-btn').removeClass('hidden'); - clone.find('button[data-action="remove-task"]').on('click', function () { - clone.remove(); - }); - setupSelect2(); - $(this).data('element', clone); - }); -}); diff --git a/resources/lang/de/admin/nests.php b/resources/lang/de/admin/nests.php deleted file mode 100644 index e4761eb0..00000000 --- a/resources/lang/de/admin/nests.php +++ /dev/null @@ -1,26 +0,0 @@ - [ - 'created' => 'Das Nest :name wurde erstellt', - 'deleted' => 'Nest erfolgreich gelöscht.', - 'updated' => 'Nest erfolgreich bearbeitet.', - ], - 'eggs' => [ - 'notices' => [ - 'imported' => 'Ei erfolgreich importiert.', - 'updated_via_import' => 'Dieses Ei wurde aktualisiert', - 'deleted' => 'Dieses Ei wurde gelöscht', - 'updated' => 'Ei wurde erfolgreich bearbeitet.', - 'script_updated' => 'Ei Installations-Skript wurde aktualisiert.', - 'egg_created' => 'Ein neues Ei wurde gelegt. Du musst alle laufenden Daemons neustarten, um dieses Ei anzuwenden.', - ], - ], - 'variables' => [ - 'notices' => [ - 'variable_deleted' => 'Die Variable ":variable" wurde gelöscht', - 'variable_updated' => 'Die Variable ":variable" wurde aktualisiert du musst die Rebuild Funktion jedes Servers benutzen.', - 'variable_created' => 'Eine neue Variable wurde erstellt und dem Egg zugewiesen.', - ], - ], -]; diff --git a/resources/lang/de/admin/node.php b/resources/lang/de/admin/node.php deleted file mode 100644 index 1304a8e6..00000000 --- a/resources/lang/de/admin/node.php +++ /dev/null @@ -1,16 +0,0 @@ - [ - 'fqdn_not_resolvable' => 'Diese Domain scheint nicht auf eine IP weiterzuleiten.', - 'fqdn_required_for_ssl' => 'Eine Domain wird für die SSL Funktion benötigt.', - ], - 'notices' => [ - 'allocations_added' => 'Zuweisungen wurden zu dieser Node erfolgreich hinzugefügt.', - 'node_deleted' => 'Node wurde erfolgreich gelöscht.', - 'location_required' => 'Du brauchst mindestens eine Location um eine Node zu konfigurieren.', - 'node_created' => 'Node erfolgreich erstellt, bitte füge die Konfigurations-Daten aus dem Konfigurations-Tab in die Datei /srv/daemon/config/core.json', - 'node_updated' => 'Node erfolgreich bearbeitet.', - 'unallocated_deleted' => 'Alle unbenutzen Ports für:ip gelöscht.', - ], -]; diff --git a/resources/lang/de/admin/pack.php b/resources/lang/de/admin/pack.php deleted file mode 100644 index c2cd11eb..00000000 --- a/resources/lang/de/admin/pack.php +++ /dev/null @@ -1,9 +0,0 @@ - [ - 'pack_updated' => 'Pack erfolgreich aktualisiert', - 'pack_deleted' => 'Pack ":name" erfolgreich gelöscht.', - 'pack_created' => 'Ein neues Pack wurde erfolgreich erstellt.', - ], -]; diff --git a/resources/lang/de/admin/user.php b/resources/lang/de/admin/user.php deleted file mode 100644 index 6d6a263f..00000000 --- a/resources/lang/de/admin/user.php +++ /dev/null @@ -1,11 +0,0 @@ - [ - 'user_has_servers' => 'Es kann kein Benutzer mit einem aktiven Server gelöscht werden.', - ], - 'notices' => [ - 'account_created' => 'Der Account wurde erfolgreich erstellt.', - 'account_updated' => 'Der Account wurde erfolgreich bearbeitet.', - ], -]; diff --git a/resources/lang/de/auth.php b/resources/lang/de/auth.php deleted file mode 100644 index 73673a00..00000000 --- a/resources/lang/de/auth.php +++ /dev/null @@ -1,27 +0,0 @@ - 'Der 2FA-Token war ungültig.', - '2fa_must_be_enabled' => 'Der Administrator hat festgelegt, dass dein Konto die 2-Faktor-Authentifizierung benutzen muss, um das Panel verwenden zu können.', - '2fa_required' => 'Zwei-Faktor Authentifizierung', - 'authentication_required' => 'Du musst angemeldet sein, um fortzufahren.', - 'auth_error' => 'Während dem anmelden ist ein Fehler aufgetreten.', - 'confirmpassword' => 'Passwort bestätigen', - 'emailsent' => 'Deine E-Mail zum zurücksetzen des Passworts ist unterwegs.', - 'email_sent' => 'Du erhälst eine E-Mail mit weiteren Anweisungen zum zurücksetzen deines Passworts.', - 'failed' => 'Die Anmeldeinformationen stimmen nicht überein.', - 'forgot_password' => 'Passwort vergessen', - 'not_authorized' => 'Du bist nicht autorisiert diese Aktion auszuführen.', - 'password_requirements' => 'Passwörter müssen Zahlen, Klein-, Großbuchstaben enthalten und mindestens 8 Zeichen lang sein.', - 'remeberme' => 'Angemeldet bleiben', - 'remember_me' => 'Angemeldet bleiben', - 'request_reset' => 'Konto suchen', - 'request_reset_text' => 'Du hast dein Passwort vergessen? Das ist kein Weltuntergang! Gib einfach deine E-Mail hier an.', - 'resetpassword' => 'Passwort zurücksetzen', - 'reset_password' => 'Passwort zurücksetzen', - 'reset_password_text' => 'Passwort zurücksetzen.', - 'sendlink' => 'Passwortrücksetzungslink senden.', - 'sign_in' => 'Anmelden', - 'throttle' => 'Du hast zu oft versucht dich anzumelden, bitte warte noch :seconds Sekunden.', - 'totp_failed' => 'Der TOTP Code war ungültig.', -]; diff --git a/resources/lang/de/base.php b/resources/lang/de/base.php deleted file mode 100644 index b720f7c6..00000000 --- a/resources/lang/de/base.php +++ /dev/null @@ -1,354 +0,0 @@ - [ - 'current_password' => 'Aktuelles Passwort', - 'delete_user' => 'Benutzer löschen', - 'details_updated' => 'Dein Account wurde erfolgreich bearbeitet.', - 'email_password' => 'Email Passwort', - 'exception' => 'Während dem aktualisieren deines Kontos ist ein Fehler aufgetreten.', - 'first_name' => 'Vorname', - 'header' => 'BENUTZERVERWALTUNG', - 'header_sub' => 'Verwalte deine Kontodetails.', - 'invalid_pass' => 'Das angegebene Passwort ist für dieses Konto falsch.', - 'invalid_password' => 'Das Passwort war leider ungültig.', - 'last_name' => 'Nachname', - 'new_email' => 'Neue E-Mail Adresse', - 'new_password' => 'Neues Passwort', - 'new_password_again' => 'Neues Passwort wiederholen', - 'totp_disable' => 'Deaktiviere die Zwei-Faktor-Authentifizierung', - 'totp_enable' => 'Zwei-Faktor-Authentifizierung aktivieren', - 'totp_enable_help' => 'Es sieht so aus als hättest du die Zwei-Faktor-Authentifizierung deaktiviert. Diese Authentifizierungsmethode schützt dein Konto zusätzlich vor unerlaubtem Zugriff. Wenn du sie aktivierst musst du zukünftig neben deinem Passwort auch einen Code, der von deinem Smartphone oder einem anderen TOTP fähigen Gerät generiert wird, eingeben um dich anzumelden.', - 'totp_header' => 'Zwei-Faktor Authentifizierung', - 'update_email' => 'E-Mail Adresse aktualisieren', - 'update_identitity' => 'Kotodetails aktualisieren', - 'update_identity' => 'Konto bearbeiten', - 'update_pass' => 'Passwort ändern', - 'update_user' => 'Benutzer aktualisieren', - 'username_help' => 'Dein Benutzername muss für dein Konto einzigartig sein und darf nur die folgenden Zeichen enthalten: :requirements.', - ], - 'api' => [ - 'index' => [ - 'create_new' => 'Neuen API Schlüssel erstellen', - 'header' => 'API Zugriff', - 'header_sub' => 'Verwalte deine API Zugangsschlüssel.', - 'keypair_created' => 'Ein API-Schlüsselpaar wurde generiert. Dein API Secret Token ist : token . Bitte notiere diesen Schlüssel, da er nicht mehr angezeigt wird. ', - 'list' => 'API Schlüssel', - ], - 'new' => [ - 'allowed_ips' => [ - 'description' => 'Gib zeilenweise alle IP Adressen an, die diesen Schlüssel verwenden können um auf die API zuzugreifen. CIDR-Notation ist erlaubt. Lass das Feld leer um beliebige IPs zu erlauben.', - 'title' => 'Erlaubte IP-Adressen', - ], - 'base' => [ - 'information' => [ - 'description' => 'Gibt eine Liste aller Server zurück auf die das Konto Zugriff hat.', - 'title' => 'Basisinformationen', - ], - 'title' => 'Basisinformationen', - ], - 'descriptive_memo' => [ - 'description' => 'Gebe dem API Schlüssel eine kurze Beschreibung.', - 'title' => 'Beschreibung', - ], - 'form_title' => 'Details', - 'header' => 'Neuer API Schlüssel', - 'header_sub' => 'Erzeuge einen neuen API Schlüssel', - 'location_management' => [ - 'list' => [ - 'title' => 'Liste Standorte', - ], - 'title' => 'Orte verwalten', - ], - 'node_management' => [ - 'allocations' => [ - 'description' => 'Erlaubt es alle Zuweisungen (IP und Port) für alle Nodes im Panel aufzulisten.', - 'title' => 'Zuweisungen auflisten', - ], - 'create' => [ - 'description' => 'Erlaubt es neue Nodes zu erstellen.', - 'title' => 'Node erstellen', - ], - 'delete' => [ - 'description' => 'Erlaubt es eine Node zu löschen.', - 'title' => 'Node löschen', - ], - 'list' => [ - 'description' => 'Erlaubt die Auflistung aller Nodes.', - 'title' => 'Nodes auflisten', - ], - 'title' => 'Nodeverwaltung', - 'view' => [ - 'description' => 'Erlaubt es Details zu einer Node abzurufen.', - 'title' => 'Einzelne Node anzeigen', - ], - ], - 'server_management' => [ - 'command' => [ - 'title' => 'Befehle senden', - ], - 'config' => [ - 'title' => 'Konfiguration aktualisieren', - ], - 'create' => [ - 'description' => 'Erlaubt es neue Server zu erstellen.', - 'title' => 'Server erstellen', - ], - 'delete' => [ - 'description' => 'Ermöglicht es, Server zu löschen.', - 'title' => 'Server löschen', - ], - 'list' => [ - 'title' => 'Server auflisten', - ], - 'server' => [ - 'title' => 'Server Informationen', - ], - 'suspend' => [ - 'description' => 'Ermöglicht das Suspendieren einer Serverinstanz.', - 'title' => 'Server suspendieren', - ], - 'title' => 'Serververwaltung', - 'unsuspend' => [ - 'description' => 'Ermöglicht die Suspendierung einer Serverinstanz aufzuheben.', - 'title' => 'Suspendierung des Servers aufheben', - ], - 'view' => [ - 'title' => 'Einzelnen Server anzeigen', - ], - ], - 'service_management' => [ - 'list' => [ - 'title' => 'Dienste auflisten', - ], - 'title' => 'Serviceverwaltung', - 'view' => [ - 'title' => 'Einzelnen Dienst auflisten', - ], - ], - 'user_management' => [ - 'create' => [ - 'description' => 'Erlaubt es neue Benutzer zu erstellen.', - 'title' => 'Benutzer erstellen', - ], - 'delete' => [ - 'description' => 'Erlaubt es einen Benutzer zu entfernen.', - 'title' => 'Benutzer entfernen', - ], - 'list' => [ - 'description' => 'Erlaubt die Auflistung aller Benutzerkonten.', - 'title' => 'Benutzerkonten auflisten', - ], - 'title' => 'Benutzerverwaltung', - 'update' => [ - 'description' => 'Erlaubt Benutzerdetails zu ändern (E-Mail, Passwort, TOPT Einstellungen).', - 'title' => 'Benutzer aktualisieren', - ], - 'view' => [ - 'description' => 'Erlaubt es Details zu einem Benutzer abzurufen. Inklusive aktiver Services.', - 'title' => 'Einzelnen Benutzer anzeigen', - ], - ], - ], - 'permissions' => [ - 'admin' => [ - 'location' => [ - 'list' => [ - 'desc' => 'Der Benutzer darf alle Standorte sehen.', - 'title' => 'Liste Standorte', - ], - ], - 'location_header' => 'Standort-Verwaltung', - 'node' => [ - 'create' => [ - 'desc' => 'Erlaubt es neue Nodes zu erstellen.', - 'title' => 'Node erstellen', - ], - 'delete' => [ - 'desc' => 'Erlaubt das löschen einer Node aus dem System.', - 'title' => 'Node löschen', - ], - 'list' => [ - 'desc' => 'Der Benutzer darf alle Nodes sehen.', - 'title' => 'Nodes auflisten', - ], - 'view-config' => [ - 'desc' => 'Der Benutzer kann die Konfiguration dieser Node sehen.', - 'title' => 'Node Konfiguration anzeigen', - ], - 'view' => [ - 'desc' => 'Erlaubt es Details zu einer Node abzurufen.', - 'title' => 'Node anzeigen', - ], - ], - 'node_header' => 'Nodeverwaltung', - 'option' => [ - 'list' => [ - 'title' => 'List Options', - ], - 'view' => [ - 'title' => 'View Option', - ], - ], - 'option_header' => 'Option Control', - 'pack' => [ - 'list' => [ - 'title' => 'List Packs', - ], - 'view' => [ - 'title' => 'View Pack', - ], - ], - 'pack_header' => 'Pack Control', - 'server' => [ - 'create' => [ - 'desc' => 'Der Benutzer darf Server erstellen.', - 'title' => 'Server erstellen', - ], - 'delete' => [ - 'desc' => 'Der Benutzer darf Server löschen.', - 'title' => 'Server löschen', - ], - 'edit-build' => [ - 'desc' => 'Der Benutzer darf Servereinstellungen bearbeiten.', - 'title' => 'Servereinstellungen ändern', - ], - 'edit-container' => [ - 'desc' => 'Der Benutzer darf die Container Einstellungen des Servers verändern.', - 'title' => 'Server Container Einstellungen ändern', - ], - 'edit-details' => [ - 'desc' => 'Der Benutzer darf die Servereinstellungen bearbeiten.', - 'title' => 'Server Details ändern', - ], - 'edit-startup' => [ - 'desc' => 'Der User darf die Startparameter ändern.', - 'title' => 'Server Startparameter ändern', - ], - 'install' => [ - 'desc' => 'Der Benutzer darf den Installationstatus bearbeiten', - 'title' => 'Installlations Status ändern', - ], - 'list' => [ - 'desc' => 'Der Benutzer darf alle Server dieser Instanz sehen.', - 'title' => 'Servers Liste anzeigen', - ], - 'rebuild' => [ - 'desc' => 'Der Benutzer darf den Server ner erstellen', - 'title' => 'Rebuild Server', - ], - 'suspend' => [ - 'desc' => 'Der User darf Server sperren.', - 'title' => 'Server sperren', - ], - 'view' => [ - 'desc' => 'Der Benutzer darf detaillierte Informationen zu allen Servern dieser Instanz sehen.', - 'title' => 'Server Informationen anzeigen', - ], - ], - 'server_header' => 'Server Control', - 'service' => [ - 'list' => [ - 'desc' => 'Der Benutzer kann alle Services sehen.', - 'title' => 'Services anzeigen', - ], - 'view' => [ - 'desc' => 'Der Benutzer kann detaillierte Informationen über einen Service sehen.', - 'title' => 'Service anzeigen', - ], - ], - 'service_header' => 'Service Control', - 'user' => [ - 'create' => [ - 'desc' => 'Der Benutzer kann einen User erstellen.', - 'title' => 'Benutzer erstellen', - ], - 'delete' => [ - 'desc' => 'Der User kann einen Server löschen.', - 'title' => 'Benutzer löschen', - ], - 'edit' => [ - 'desc' => 'Der User kann einen User bearbeiten.', - 'title' => 'Aktualisiere Benutzer', - ], - 'list' => [ - 'desc' => 'Ermöglicht die Auflistung aller derzeit im System befindlichen Benutzer.', - 'title' => 'Benutzerliste anzeigen', - ], - 'view' => [ - 'desc' => 'Der User kann detaillierte Informationen der User sehen.', - 'title' => 'Benutzerinformationen anzeigen', - ], - ], - 'user_header' => 'Benutzer Control', - ], - 'user' => [ - 'server' => [ - 'command' => [ - 'desc' => 'Der Benutzer hat Zugriff auf die Server Konsole.', - 'title' => 'Befehl senden', - ], - 'list' => [ - 'desc' => 'Der Benutzer darf seine Serverliste ansehen.', - 'title' => 'Serverliste', - ], - 'power' => [ - 'desc' => 'Der Benutzer darf den Server starten/stoppen/restartet.', - 'title' => 'Server start/stop/restart', - ], - 'view' => [ - 'desc' => 'Der Benutzer darf detaillierte Informationen über seine Server sehen.', - 'title' => 'Serverinformationen anzeigen', - ], - ], - 'server_header' => 'Benutzer Rechte', - ], - ], - ], - 'confirm' => 'Bist du sicher?', - 'errors' => [ - '403' => [ - 'desc' => 'Du bist nicht berechtigt, diese Seite zu öffnen.', - 'header' => 'Forbidden', - ], - '404' => [ - 'desc' => 'Die angefragte Ressource konnte nicht gefunden werden.', - 'header' => 'File Not Found', - ], - 'home' => 'Gehe zur Startseite', - 'installing' => [ - 'desc' => 'Dieser Server wird derzeit noch installiert. Bitte versuche es in ein paar Minuten erneut, du solltest eine E-Mail erhalten, sobald dieser Prozess abgeschlossen ist.', - 'header' => 'Server Installation', - ], - 'return' => 'Zur vorherigen Seite zurückkehren', - 'suspended' => [ - 'desc' => 'Dieser Server wurde von einem Administrator gesperrt.', - 'header' => 'Server Suspended', - ], - ], - 'form_error' => 'Die folgenden Fehler sind bei dem Versuch die Anfrage auszuführen aufgetreten.', - 'index' => [ - 'header' => 'Serverkonsole', - 'header_sub' => 'Kontrollieren Sie Ihren Server in Echtzeit.', - 'list' => 'Serverliste', - ], - 'no_servers' => 'Deinem Benutzerkonto sind aktuell keine Server zugeordnet.', - 'password_req' => 'Passwörter müssen den folgenden Anforderungen genügen: mindestens ein Großbuchstabe, ein Kleinbuchstabe, eine Ziffer und eine Länge von mindestens 8 Zeichen.', - 'security' => [ - '2fa_checkpoint_help' => 'Verwende die 2FA-Anwendung auf deinem Telefon, um den QR-Codes auf der linken Seite zu scannen, oder gebe den Code darunter manuell ein. Sobald du dies getan hast, generiere einen Token und gebe ihn unten ein.', - '2fa_disabled' => '2-Faktor-Authentifizierung ist deaktiviert! Du solltest die 2-Faktor-Authentifizierung aktivieren um dein Konto zusätzlich zu schützen.', - '2fa_disable_error' => 'Der bereitgestellte 2FA-Token war nicht gültig. Der Schutz wurde für dieses Konto nicht deaktiviert.', - '2fa_header' => '2-Faktor-Authentifizierung', - '2fa_qr' => '2FA konfigurieren', - '2fa_token_help' => 'Bitte gebe den 2FA Code von deiner 2FA APP ein (Google Authenticator, Authy, etc.).', - 'disable_2fa' => '2-Factor-Authentifizierung deaktivieren', - 'enable_2fa' => '2-Faktor-Authentifizierung aktivieren', - 'header' => 'Kontosicherheit', - 'header_sub' => 'Verwalte aktive Sitzungen und die 2-Faktor-Authentifizierung.', - 'sessions' => 'Aktive Sitzungen', - 'session_mgmt_disabled' => 'Der Administrator hat die Möglichkeit, aktive Sitzungen über dieses Panel zu verwalten, nicht aktiviert.', - ], - 'server_name' => 'Name des Servers', - 'validation_error' => 'Es gab ein Problem mit einer oder mehreren deiner Eingaben.', - 'view_as_admin' => 'Du siehst die Serverliste als Administrator. Deshalb siehst du alle im System vorhandenen Server. Die Server bei denen du als Besitzer eingetragen bist sind mit einem blauen Punkt markiert.', -]; diff --git a/resources/lang/de/command/messages.php b/resources/lang/de/command/messages.php deleted file mode 100644 index f4cdfa85..00000000 --- a/resources/lang/de/command/messages.php +++ /dev/null @@ -1,84 +0,0 @@ - [ - 'no_location_found' => 'Shortcode wurde nicht gefunden.', - 'ask_short' => 'Standort Short Code', - 'ask_long' => 'Standortbeschreibung', - 'created' => 'Neuer Standort (:name) mit der ID :id erstellt.', - 'deleted' => 'Standort gelöscht.', - ], - 'user' => [ - 'search_users' => 'Gib einen Benutzernamen, eine UUID oder eine E-Mail an', - 'select_search_user' => 'ID des Benutzers (Gib \'0\' ein, um erneut zu suchen)', - 'deleted' => 'Benutzer erfolgreich gelöscht.', - 'confirm_delete' => 'Bist du dir wirklich sicher?', - 'no_users_found' => 'Es wurden keine Benutzer gefunden.', - 'multiple_found' => 'Es wurden mehrere Benutzer gefunden.', - 'ask_admin' => 'Ist dieser Benutzer ein Administrator?', - 'ask_email' => 'Email Adresse', - 'ask_username' => 'Benutzername', - 'ask_name_first' => 'Vorname', - 'ask_name_last' => 'Nachname', - 'ask_password' => 'Passwort', - 'ask_password_tip' => 'Wenn du das wirklich tun willst drücke Strg+c und benutze das `--no-password` flag.', - 'ask_password_help' => 'Das Passwort muss Zahlen, Groß- und Kleinbuchstaben enthalten und mindestens 8 Zeichen lang sein.', - '2fa_help_text' => [ - 'Dieser Befehl deaktiviert die 2-Faktor-Authentifizierung für ein Benutzerkonto, falls es aktiviert ist. Dieser Befehl sollte nur zur Accountrettung verwendet werden, wenn sich ein Nutzer aus seinem Account ausgeschlossen hat.', - 'Falls es nicht das ist, was du erreichen wolltest, drücke Strg+C, um diesen Prozess zu beenden.', - ], - '2fa_disabled' => '2-Faktor-Authentifizierung wurde für :email deaktiviert.', - ], - 'schedule' => [ - 'output_line' => 'Erledigt den Job für die erste Aufgabe in `:schedule` (:hash).', - ], - 'maintenance' => [ - 'deleting_service_backup' => 'Lösche Service Backup-Datei :file.', - ], - 'server' => [ - 'rebuild_failed' => 'Rebuild-Nachfrage für ":name" (#:id) auf der Node ":node" scheiterte mit dem Fehler: :message', - ], - 'environment' => [ - 'mail' => [ - 'ask_smtp_host' => 'SMTP Host (e.g. smtp.gmail.com)', - 'ask_smtp_port' => 'SMTP Port', - 'ask_smtp_username' => 'SMTP Benutzername', - 'ask_smtp_password' => 'SMTP Passwort', - 'ask_mailgun_domain' => 'Mailgun Domain', - 'ask_mailgun_secret' => 'Mailgun Secret', - 'ask_mandrill_secret' => 'Mandrill Secret', - 'ask_postmark_username' => 'Postmark API Key', - 'ask_driver' => 'Which driver should be used for sending emails?', - 'ask_mail_from' => 'Email address emails should originate from', - 'ask_mail_name' => 'Name that emails should appear from', - 'ask_encryption' => 'Encryption method to use', - ], - 'database' => [ - 'host_warning' => 'Es wird dringend empfohlen, "localhost" nicht als Datenbank-Host zu verwenden, da es zu häufigen Socket-Verbindungsproblemen gekommen ist. Wenn du eine lokale Verbindung verwenden möchten, solltest du "127.0.0.1" verwenden.', - 'host' => 'Datenbank Host', - 'port' => 'Datenbank Port', - 'database' => 'Datenbank Name', - 'username_warning' => 'Die Verwendung des "root" -Kontos für MySQL-Verbindungen ist nicht erlaubt, Du musst einen extra MySQL-Benutzer erstellt haben.', - 'username' => 'Datenbank Benutzername', - 'password_defined' => 'Es scheint so, als ob du schon ein MySQL-kennwort definiert haast. Möchtest du es ändern?', - 'password' => 'Datenbank Passwort', - 'connection_error' => 'Es konnte keine Verbindung zum MySQL-Server mit den angegebenen Anmeldeinformationen hergestellt werden. Zurückgegebene Fehler ":error".', - 'creds_not_saved' => 'Die Verbindungsdaten wurden NICHT gespeichert. Du musst gültige Verbindungsinformationen angeben, bevor du fortfahren kannst.', - 'try_again' => 'Zurück und erneuert versuchen?', - ], - 'app' => [ - 'app_url_help' => 'The application URL MUST begin with https:// or http:// depending on if you are using SSL or not. If you do not include the scheme your emails and other content will link to the wrong location.', - 'app_url' => 'Application URL', - 'timezone_help' => 'The timezone should match one of PHP\'s supported timezones. If you are unsure, please reference http://php.net/manual/en/timezones.php.', - 'timezone' => 'Application Timezone', - 'cache_driver' => 'Cache Driver', - 'session_driver' => 'Session Driver', - 'using_redis' => 'You\'ve selected the Redis driver for one or more options, please provide valid connection information below. In most cases you can use the defaults provided unless you have modified your setup.', - 'redis_host' => 'Redis Host', - 'redis_password' => 'Redis Password', - 'redis_pass_help' => 'By default a Redis server instance has no password as it is running locally and inaccessable to the outside world. If this is the case, simply hit enter without entering a value.', - 'redis_port' => 'Redis Port', - 'redis_pass_defined' => 'It seems a password is already defined for Redis, would you like to change it?', - ], - ], -]; diff --git a/resources/lang/de/exceptions.php b/resources/lang/de/exceptions.php deleted file mode 100644 index eb416b5f..00000000 --- a/resources/lang/de/exceptions.php +++ /dev/null @@ -1,55 +0,0 @@ - 'Es gab einen Fehler bei der Verbindung mit dem Daemon. Ausgabe: HTTP/:code response code.', - 'node' => [ - 'servers_attached' => 'Eine Node darf keine Server enthalten um gelöscht zu werden', - 'daemon_off_config_updated' => 'Die Config wurde aktualisiert! Du musst allerdings die Config neu auf dem Server bearbeiten.', - ], - 'allocations' => [ - 'too_many_ports' => 'Du kannst leider nicht mehr als 1000 Ports gleichzeitig hinzufügen', - 'invalid_mapping' => 'Die Zuweisung für den Port :port war ungültig und konnte nicht verarbeitet werden.', - 'cidr_out_of_range' => 'CIDR Notation erlaubt Masken nur zwischen /25 und /32.', - ], - 'nest' => [ - 'delete_has_servers' => 'Ein Nest mit zugewiesenen aktiven Servern kann nicht gelöscht werden.', - 'egg' => [ - 'delete_has_servers' => 'Ein Ei mit zugewiesenen aktiven Servern kann nicht gelöscht werden.', - 'invalid_copy_id' => 'Das Ei, von dem ein Skript kopiert werden sollte existiert entweder nicht, oder kopiert selbst ein Skript.', - 'must_be_child' => 'Die "Kopiere Einstellungen von" Direktive für diese Ei muss eine Kind-Option für das ausgewählte Ei sein.', - 'has_children' => 'Dieses Ei hat ein oder mehrere Kinder. Bitte lösche die Eier bevor du dieses löschen kannst.', - ], - 'variables' => [ - 'env_not_unique' => 'Die Umgebungsvariable :name muss einzigartig sein.', - 'reserved_name' => 'Die Umgebungsvariable :name ist geschützt und kann nicht zugewiesen werden.', - ], - 'importer' => [ - 'json_error' => 'Beim Parsen der JSON-Datei kam es zu einem Fehler: :error.', - 'file_error' => 'Die angegebene JSON-Datei war ungültig.', - 'invalid_json_provided' => 'Die angegebene JSON-Datei ist in einem unbekannten Format.', - ], - ], - 'packs' => [ - 'delete_has_servers' => 'Ein Pack kann nicht gelöscht werden, wenn es von einem aktiven Server verwendet wird.', - 'update_has_servers' => 'Ein Pack kann nicht bearbeitet werden, wenn es von einem aktiven Server verwendet wird..', - 'invalid_upload' => 'Die Datei scheint ungültig zu sein.', - 'invalid_mime' => 'Die Datei hat nicht den angeforderten Dateityp: :type', - 'unreadable' => 'Das Archiv konnte nicht geöffnet werden.', - 'zip_extraction' => 'Es gab ein Problem beim Entpacken des Archivs.', - 'invalid_archive_exception' => 'Die Pack Datei scheint keine import.json zu enthalten.', - ], - 'subusers' => [ - 'editing_self' => 'Du darfst deinen eigenen Subuser nicht bearbeiten.', - 'user_is_owner' => 'Du kannst den Serverbesitzer nicht als Subuser hinzufügen.', - 'subuser_exists' => 'Diese Email ist bereits registriert.', - ], - 'databases' => [ - 'delete_has_databases' => 'Es kann keine Datenbank gelöscht werden, die von einem aktiven Server verwendet wird.', - ], - 'tasks' => [ - 'chain_interval_too_long' => 'Die maximale Intervalldauer für eine verkettete Aufgabe ist 15 Minuten.', - ], - 'locations' => [ - 'has_nodes' => 'Es kann keine Location gelöscht werden, die von einer Node verwendet wird', - ], -]; diff --git a/resources/lang/de/navigation.php b/resources/lang/de/navigation.php deleted file mode 100644 index 607aaf8c..00000000 --- a/resources/lang/de/navigation.php +++ /dev/null @@ -1,31 +0,0 @@ - 'Startseite', - 'account' => [ - 'header' => 'ACCOUNT VERWALTUNG', - 'my_account' => 'Mein Account', - 'security_controls' => 'Sicherheit', - 'api_access' => 'API Zugriff', - 'my_servers' => 'Meine Server', - ], - 'server' => [ - 'header' => 'SERVER VERWALTUNG', - 'console' => 'Konsole', - 'console-pop' => 'Vollbild Konsole', - 'file_management' => 'Dateiverwaltung', - 'file_browser' => 'File Browser', - 'create_file' => 'Datei erstellen', - 'upload_files' => 'Datei hochladen', - 'subusers' => 'Subuser', - 'schedules' => 'Geplante Aufgaben', - 'configuration' => 'Konfiguration', - 'port_allocations' => 'Port-Einstellungen', - 'sftp_settings' => 'SFTP Einstellungen', - 'startup_parameters' => 'Startup Parameter', - 'databases' => 'Datenbanken', - 'edit_file' => 'Datei bearbeiten', - 'admin_header' => 'ADMINISTRATIV', - 'admin' => 'Server Konfiguration', - ], -]; diff --git a/resources/lang/de/pagination.php b/resources/lang/de/pagination.php deleted file mode 100644 index 1680d25b..00000000 --- a/resources/lang/de/pagination.php +++ /dev/null @@ -1,16 +0,0 @@ - - 'Nächste »', - 'previous' => '« Vorherige', - 'sidebar' => [ - 'account_controls' => 'Kontosteuerung', - 'account_security' => 'Kontosicherheit', - 'account_settings' => 'Kontoeinstellungen', - 'files' => 'Dateimanager', - 'manage' => 'Server verwalten', - 'overview' => 'Server Übersicht', - 'servers' => 'Deine Server', - 'subusers' => 'Subuser verwalten', - ], -]; diff --git a/resources/lang/de/passwords.php b/resources/lang/de/passwords.php deleted file mode 100644 index b1502c70..00000000 --- a/resources/lang/de/passwords.php +++ /dev/null @@ -1,9 +0,0 @@ - 'Passwort', - 'reset' => 'Dein Passwort wurde zurückgesetzt!', - 'sent' => 'Ein Link zum zurücksetzen des Passworts wurde per E-Mail gesendet!', - 'token' => 'Der Token war ungültig', - 'user' => 'Es gibt keinen Benutzer mit dieser E-Mail.', -]; diff --git a/resources/lang/de/server.php b/resources/lang/de/server.php deleted file mode 100644 index 4ee2ad06..00000000 --- a/resources/lang/de/server.php +++ /dev/null @@ -1,397 +0,0 @@ - [ - 'allocation' => [ - 'available' => 'Verfügbare Allocations', - 'header' => 'Server Allocations', - 'header_sub' => 'Verfügbare IPs und Ports für diesen Server verwalten.', - 'help' => 'Allocation Help', - 'help_text' => 'Die Liste auf der linken Seite zeigt alle verfügbaren IPs und Ports, die für eingehende Verbindungen auf diesem Server geöffnet sind.', - ], - 'database' => [ - 'add_db' => 'Datenbank hinzufügen.', - 'header' => 'Datenbanken', - 'header_sub' => 'Alle für diesen Server verfügbaren Datenbanken.', - 'host' => 'MySQL Host', - 'no_dbs' => 'Du hast leider keine Datenbanken.', - 'reset_password' => 'Passwort zurücksetzen', - 'your_dbs' => 'Deine Datenbanken', - ], - 'sftp' => [ - 'change_pass' => 'Passwort ändern', - 'conn_addr' => 'Verbindungsadresse', - 'details' => 'SFTP Details', - 'header' => 'SFTP Information', - 'header_sub' => 'Details für eine SFTP Verbindung.', - 'warning' => 'Bitte benutze SFTP und nicht FTP!.', - ], - 'startup' => [ - 'command' => 'Startbefehl', - 'edited' => 'Die Einstellungen wurden gespeichert und werden beim nächsten Serverstart verwendet.', - 'edit_params' => 'Parameter bearbeiten', - 'header' => 'Start Konfiguration', - 'header_sub' => 'Bearbeite die Startparameter des Serves.', - 'startup_regex' => 'Input Regeln', - 'startup_var' => 'Startbefehl Variablen', - 'update' => 'Absenden', - ], - ], - 'files' => [ - 'add' => [ - 'create' => 'Datei erstellen', - 'header' => 'Neue Datei', - 'header_sub' => 'Erstelle eine neue Datei auf deinem Server.', - 'name' => 'Dateiname', - ], - 'add_folder' => 'Neuen Ordner erstellen', - 'add_new' => 'Neue Datei erstellen', - 'back' => 'Zurück zum Datei-Manager', - 'delete' => 'Löschen', - 'edit' => [ - 'header' => 'Datei bearbeiten', - 'header_sub' => 'Bearbeite Dateien direkt vom Browser aus.', - 'return' => 'Zurück zum Datei Manager', - 'save' => 'Datei speichern', - ], - 'exceptions' => [ - 'invalid_mime' => 'Diese Datei kann leider nicht bearbeitet werden', - 'max_size' => 'Diese Datei ist zu groß um bearbeitet zu werden.', - ], - 'file_name' => 'Dateiname', - 'header' => 'Datei Manager', - 'header_sub' => 'Verwalte deine Dateien.', - 'last_modified' => 'Zuletzt bearbeitet', - 'loading' => 'Dateistruktur wird geladen, dies kann einige Sekunden dauern.', - 'mass_actions' => 'Massenaktionen', - 'path' => 'Wenn du Ordner erstellst solltest du :path als Basis Ordner verwenden! Der maximale Upload beträgt: :size.', - 'saved' => 'Die Datei wurde erfolgreich gespeichert.', - 'seconds_ago' => 'Sekunden her', - 'size' => 'Größe', - 'yaml_notice' => 'Du bearbeitest gerade eine YAML Datei. Diese Dateien akzeptieren keine Tabs sondern nur Leerzeichen. Deshalb wird die Tab Taste automatisch :dropdown Leerzeichen einfügen.', - ], - 'index' => [ - 'add_new' => 'Füge einen Server hinzu', - 'allocation' => 'Zuweisung', - 'connection' => 'Standardverbindung', - 'control' => 'Server verwalten', - 'cpu_use' => 'CPU Auslastung', - 'disk_space' => 'Festplattenspeicher', - 'header' => 'Serverkonsole', - 'header_sub' => 'Kontrollieren Sie Ihren Server in Echtzeit.', - 'memory_use' => 'Arbeitsspeicherverbrauch', - 'mem_limit' => 'Arbeitsspeicherlimit.', - 'server_info' => 'Server-Informationen', - 'title' => 'Server :name', - 'usage' => 'Nutzung', - ], - 'schedule' => [ - 'actions' => [ - 'command' => 'Befehl ausführen', - 'power' => 'Power Aktion', - ], - 'current' => 'Derzeitige Aktionen', - 'day_of_month' => 'Tag eines Monats', - 'day_of_week' => 'Tag einer Woche', - 'header' => 'Zeitplan Manager', - 'header_sub' => 'Erstelle geplante Aktionen.', - 'hour' => 'Stunde des Tages', - 'manage' => [ - 'delete' => 'Aktion löschen', - 'header' => 'Aktion verwalten', - 'submit' => 'Aktion bearbeiten', - ], - 'minute' => 'Minute der Stunde', - 'new' => [ - 'header' => 'Neue Aktion erstellen', - 'header_sub' => 'Erstelle eine neue Gruppe an Aktionen.', - 'submit' => 'Aktion erstellen', - ], - 'run_now' => 'Zeitplan jetzt ausführen', - 'schedule_created' => 'Neuen Zeitplan für diesen Server erfolgreich erstellt.', - 'schedule_updated' => 'Der Zeitplan wurde aktualisiert.', - 'setup' => 'Zeitplan Erstellung', - 'task' => [ - 'action' => 'Aktion ausführen', - 'add_more' => 'Weitere Aktion', - 'payload' => 'Mit Payload', - 'time' => 'Nach', - ], - 'task_help' => 'Zeiten für Aufgaben sind relativ zu der zuvor definierten Aufgabe. Jedem Zeitplan dürfen nicht mehr als 5 Aufgaben zugewiesen sein und Aufgaben dürfen nicht mehr als 15 Minuten voneinander entfernt liegen.', - 'time_help' => 'Dieses System unterstützt dern Cronjob Syntax.', - 'toggle' => 'Status wechseln', - 'unnamed' => 'Unbenannter Zeitplan', - ], - 'tasks' => [ - 'actions' => [ - 'command' => 'Befehl ausführen', - 'power' => 'Power Aktion senden', - ], - 'current' => 'Aktuelle Aktionen', - 'edit' => [ - 'header' => 'Aktion bearbeiten', - 'submit' => 'Abschicken', - ], - 'header' => 'Geplante Aufgaben', - 'header_sub' => 'Automatisiere deinen Server.', - 'new' => [ - 'chain_arguments' => 'With Arguments', - 'chain_do' => 'Do', - 'chain_then' => 'Then, After', - 'custom' => 'Benutzerdefinierter Wert', - 'day_of_month' => 'Tag eines Monats', - 'day_of_week' => 'Tag einer Woche', - 'fri' => 'Freitag', - 'header' => 'Neue Aktion', - 'header_sub' => 'Neuen Aktion erstellen.', - 'hour' => 'Stunde', - 'minute' => 'Minute', - 'mon' => 'Montag', - 'payload' => 'Task Payload', - 'payload_help' => 'Wenn du die Befehl ausführen Methode ausgewählt hast, wird ein Befehl zur angegebenen Zeit ausgeführt.', - 'sat' => 'Samstag', - 'submit' => 'Absenden', - 'sun' => 'Sonntag', - 'task_name' => 'Name', - 'thurs' => 'Donnerstag', - 'tues' => 'Dienstag', - 'type' => 'Typ', - 'wed' => 'Mittwoch', - ], - 'new_task' => 'Neue Aufgabe hinzufügen', - 'task_created' => 'Aktion erfolgreich erstellt.', - 'task_updated' => 'Aktion bearbeitet.', - 'toggle' => 'Status ändern', - ], - 'users' => [ - 'add' => 'Neuen Benutzer erstellen', - 'configure' => 'Rechte einstellen', - 'edit' => [ - 'header' => 'Benutzer bearbeiten', - 'header_sub' => 'Verwalte den Zugriff eines Benutzers auf diesen Server.', - ], - 'header' => 'Benutzer verwalten', - 'header_sub' => 'Bestimme wer den Server verwalten kann.', - 'list' => 'Account Liste', - 'new' => [ - 'access_sftp' => [ - 'description' => 'Ermöglicht dem Benutzer, eine Verbindung mit dem vom Daemon bereitgestellten SFTP-Server herzustellen.', - 'title' => 'SFTP-Verbindung erlauben', - ], - 'command' => [ - 'title' => 'Konsolenbefehl senden', - ], - 'compress_files' => [ - 'description' => 'Der User darf die Server-Dateien komprimieren(zip).', - 'title' => 'Dateien komprimieren', - ], - 'copy_files' => [ - 'description' => 'Der User darf die Server-Dateien kopieren.', - 'title' => 'Dateien kopieren', - ], - 'create_database' => [ - 'description' => 'Ermöglicht es dem Benutzer, für diesen Server zusätzliche Datenbanken zu erstellen.', - 'title' => 'Datenbank erstellen', - ], - 'create_files' => [ - 'description' => 'Der User darf Server-Dateien erstellen.', - 'title' => 'Dateien erstellen', - ], - 'create_schedule' => [ - 'description' => 'Der User darf geplante Aktionen für den Server erstellen.', - 'title' => 'Aktionen erstellen', - ], - 'create_subuser' => [ - 'description' => 'Der User darf Subuser erstellen.', - 'title' => 'Subuser erstellen', - ], - 'create_task' => [ - 'description' => 'Ermöglicht es einem Benutzer, neue Aufgaben zu erstellen.', - 'title' => 'Aufgabe erstellen', - ], - 'database_header' => 'Datenbank Verwaltung', - 'db_header' => 'Datenbankverwaltung', - 'decompress_files' => [ - 'description' => 'Der Benutzer darf ZIP Archive entpacken.', - 'title' => 'Dateien entpacken', - ], - 'delete_database' => [ - 'description' => 'Ermöglicht es dem Benutzer, Datenbanken für diesen Server über das Panel zu löschen.', - 'title' => 'Datenbank löschen', - ], - 'delete_files' => [ - 'description' => 'Der Benutzer darf Server-Dateien löschen.', - 'title' => 'Dateien löschen', - ], - 'delete_schedule' => [ - 'description' => 'Der Benutzer darf geplante Aktionen für den Server löschen.', - 'title' => 'Aktionen löschen', - ], - 'delete_subuser' => [ - 'description' => 'Der Benutzer darf Subuser löschen.', - 'title' => 'Subuser löschen', - ], - 'delete_task' => [ - 'description' => 'Ermöglicht es dem Benutzer, eine Aufgabe zu löschen.', - 'title' => 'Aufgabe löschen', - ], - 'download_files' => [ - 'description' => 'Der Benutzer darf Server-Dateien herunterladen.', - 'title' => 'Dateien herunterladen', - ], - 'edit_allocation' => [ - 'description' => 'Ermöglicht es dem Benutzer, die IP:Port Adresse für einen Server zu ändern.', - 'title' => 'Standardverbindung bearbeiten', - ], - 'edit_files' => [ - 'description' => 'Der Benutzer darf die Server-Dateien bearbeiten.', - 'title' => 'Dateien bearbeiten', - ], - 'edit_schedule' => [ - 'description' => 'Der Benutzer darf geplante Aktionen für den Server bearbeiten.', - 'title' => 'Aktionen bearbeiten', - ], - 'edit_startup' => [ - 'description' => 'Ermöglicht einem Benutzer, Startvariablen für einen Server zu ändern.', - 'title' => 'Startbefehl bearbeiten', - ], - 'edit_subuser' => [ - 'description' => 'Der Benutzer darf Subuser bearbeiten.', - 'title' => 'Subuser bearbeiten', - ], - 'email' => 'E-Mail Adresse', - 'email_help' => 'Email Adresse für Einladungs Mail.', - 'file_header' => 'Dateien Verwaltung', - 'header' => 'Neuen Benutzer erstellen', - 'header_sub' => 'Erstelle einen neuen Benutzer und gebe ihm Zugriff auf diesen Server.', - 'kill' => [ - 'description' => 'Ermöglicht es dem Benutzer, den Serverprozess zu töten.', - 'title' => 'Server sofort beenden', - ], - 'list_files' => [ - 'description' => 'Der Benutzer darf die Server-Dateien sehen.', - 'title' => 'Dateien anzeigen', - ], - 'list_schedules' => [ - 'description' => 'Der Benutzer darf geplante Aktionen für den Server sehen.', - 'title' => 'Geplante Aktionen anzeigen', - ], - 'list_subusers' => [ - 'description' => 'Der Benutzer darf Subuser sehen.', - 'title' => 'Subusers anzeigen', - ], - 'list_tasks' => [ - 'title' => 'Aufgaben auflisten', - ], - 'move_files' => [ - 'description' => 'Der Benutzer darf die Server-Dateien umbenennen und verschieben.', - 'title' => 'Dateien umbenennen & verschieben', - ], - 'power_header' => 'Power Verwaltung', - 'power_kill' => [ - 'description' => 'Der Benutzer darf den Prozess des Servers töten.', - 'title' => 'Kill Server', - ], - 'power_restart' => [ - 'description' => 'Der Benutzer darf den Server neu starten.', - 'title' => 'Server neu starten', - ], - 'power_start' => [ - 'description' => 'Der Benutzer darf den Server starten.', - 'title' => 'Server starten', - ], - 'power_stop' => [ - 'description' => 'Der Benutzer darf den Server stoppen.', - 'title' => 'Server stoppen', - ], - 'queue_schedule' => [ - 'description' => 'Ermöglicht einem Benutzer, einen Zeitplan in die Warteschlange zu stellen.', - 'title' => 'Queue Schedule', - ], - 'queue_task' => [ - 'title' => 'Aufgabe einreihen', - ], - 'reset_db_password' => [ - 'description' => 'Der Benutzer darf das Datenbankpasswort zurücksetzen.', - 'title' => 'Datenbank Passwort zurücksetzen ', - ], - 'reset_sftp' => [ - 'description' => 'Der Benutzer darf dass SFTP Passwort zurücksetzen.', - 'title' => 'SFTP Passwort zurücksetzen', - ], - 'restart' => [ - 'description' => 'Der Benutzer darf den Server neu starten.', - 'title' => 'Server neu starten', - ], - 'save_files' => [ - 'description' => 'Der Benutzer darf bearbeitete Server-Dateien speichern.', - 'title' => 'Dateien speichern', - ], - 'send_command' => [ - 'description' => 'Der Benutzer darf die Konsole benutzen.', - 'title' => 'Konsolenbefehle senden', - ], - 'server_header' => 'Server Verwaltung', - 'set_connection' => [ - 'title' => 'Standard-Verbindung festlegen', - ], - 'sftp_header' => 'SFTP Verwaltung', - 'start' => [ - 'description' => 'Der Benutzer darf den Server starten.', - 'title' => 'Server starten', - ], - 'stop' => [ - 'description' => 'Der Benutzer darf den Server stoppen.', - 'title' => 'Server stoppen', - ], - 'subuser_header' => 'Subuser Verwaltung', - 'task_header' => 'Aktion Verwaltung', - 'toggle_schedule' => [ - 'description' => 'Der Benutzer darf geplante Aktionen für den Server de-/aktivieren.', - 'title' => 'De-/Aktivieren von Aktionen', - ], - 'toggle_task' => [ - 'description' => 'Der Benutzer darf geplante Aufgaben für den Server de-/aktivieren.', - ], - 'upload_files' => [ - 'description' => 'Der Benutzr darf Server-Dateien hochladen.', - 'title' => 'Dateien hochladen', - ], - 'view_allocations' => [ - 'description' => 'Ermöglicht dem Benutzer, alle einem Server zugewiesenen IPs und Ports anzuzeigen.', - 'title' => 'Zugewiesen IPs und Ports anzeigen', - ], - 'view_databases' => [ - 'description' => 'Der Benutzer darf die Datenbankinformationen sehen.', - 'title' => 'Datenbankinformationen anzeigen', - ], - 'view_schedule' => [ - 'description' => 'Der Benutzer darf Aktionen ansehen.', - 'title' => 'Aktionen anzeigen', - ], - 'view_sftp' => [ - 'description' => 'Der User darf die SFTP Informationen sehen (nicht das Passwort).', - 'title' => 'SFTP Informationen anzeigen', - ], - 'view_sftp_password' => [ - 'description' => 'Der Benutzer darf dass SFTP Passwort sehen.', - 'title' => 'SFTP Password anzeigen', - ], - 'view_startup' => [ - 'description' => 'Ermöglicht dem Benutzer, den Startbefehl und zugehörige Variablen für einen Server anzuzeigen.', - 'title' => 'Startbefehl anzeigen', - ], - 'view_subuser' => [ - 'description' => 'Der Benutzer darf Subuser genauer sehen.', - 'title' => 'View Subuser', - ], - 'view_task' => [ - 'description' => 'Der Benutzer darf Aufgaben ansehen.', - 'title' => 'Aufgaben ansehen', - ], - ], - 'update' => 'Benutzer bearbeiten', - 'user_assigned' => 'Benutzer zum Server hinzugefügt.', - 'user_updated' => 'Benutzer Rechte erfolgreich aktualisiert.', - ], -]; diff --git a/resources/lang/de/strings.php b/resources/lang/de/strings.php deleted file mode 100644 index 1551e6a7..00000000 --- a/resources/lang/de/strings.php +++ /dev/null @@ -1,102 +0,0 @@ - '2FA', - '2fa_token' => 'Authentifizierungs-Token', - 'account' => 'Benutzerkonto', - 'action' => 'Aktion', - 'admin' => 'Admin', - 'admin_control' => 'Administration', - 'admin_cp' => 'Administration', - 'again' => 'Nochmals', - 'alias' => 'Alias', - 'api_access' => 'API Access', - 'cancel' => 'Abbrechen', - 'captcha_invalid' => 'Der Captcha war ungültig.', - 'close' => 'Schließen', - 'configuration' => 'Konfiguration', - 'confirm_password' => 'Passwort bestätigen', - 'connection' => 'Verbindung', - 'cpu' => 'CPU', - 'create' => 'Erzeugen', - 'created' => 'Erstellt am', - 'created_at' => 'Erstellt am', - 'current_password' => 'Aktuelles Passwort', - 'danger' => 'Achtung', - 'data' => 'Data', - 'database' => 'Datenbank', - 'databases' => 'Datenbanken', - 'days' => [ - 'fri' => 'Freitag', - 'mon' => 'Montag', - 'sat' => 'Samstag', - 'sun' => 'Sontag', - 'thurs' => 'Donnerstag', - 'tues' => 'Dienstag', - 'wed' => 'Mittwoch', - ], - 'delete' => 'Löschen', - 'disabled' => 'Deaktiviert', - 'email' => 'E-Mail', - 'enabled' => 'Aktiviert', - 'expires' => 'läuft ab', - 'home' => 'Home', - 'id' => 'ID', - 'ip' => ':attribute muss eine gültige IP Adresse sein.', - 'language' => 'Sprache', - 'last_activity' => 'Letzte Aktivität', - 'last_run' => 'Letzte Ausführung', - 'last_used' => 'Zuletzt verwendet', - 'location' => 'Standort', - 'login' => 'Login', - 'logout' => 'Abmelden', - 'make_primary' => 'Primär machen', - 'memo' => 'Beschreibung', - 'memory' => 'Arbeitsspeicher', - 'minutes' => 'Minuten', - 'name' => 'Name', - 'never' => 'nie', - 'new' => 'Neu', - 'next_run' => 'Nächste Ausführung', - 'no' => 'Nein', - 'node' => 'Node', - 'none' => 'Nichts', - 'not_run_yet' => 'Wurde noch nicht ausgeführt', - 'optional' => 'Optional', - 'owner' => 'Owner', - 'password' => 'Passwort', - 'players' => 'Spieler', - 'port' => 'Port', - 'primary' => 'Primär', - 'public_key' => 'Öffentlicher Schlüssel', - 'queued' => 'Eingereiht', - 'read_only' => 'Read Only', - 'registered' => 'Registriert', - 'relation' => 'Relation', - 'required' => 'Benötigt', - 'restart' => 'Neu starten', - 'revoke' => 'Beenden', - 'root_administrator' => 'Root Administrator', - 'save' => 'Speichern', - 'search' => 'Suche', - 'seconds' => 'Sekunden', - 'security' => 'Sicherheit', - 'select_all' => 'Alle auswählen', - 'select_none' => 'Alle abwählen', - 'servers' => 'Server', - 'settings' => 'Einstellungen', - 'sftp' => 'SFTP', - 'sign_out' => 'Abmelden', - 'start' => 'Start', - 'status' => 'Status', - 'stop' => 'Anhalten', - 'submit' => 'Absenden', - 'subuser' => 'Subuser', - 'success' => 'Erfolgreich', - 'suspended' => 'Gesperrt', - 'tasks' => 'Aufgaben', - 'username' => 'Benutzername', - 'user_identifier' => 'Benutzername oder E-Mail', - 'whoops' => 'Uuups', - 'yes' => 'Ja', -]; diff --git a/resources/lang/de/validation.php b/resources/lang/de/validation.php deleted file mode 100644 index 7df22ff1..00000000 --- a/resources/lang/de/validation.php +++ /dev/null @@ -1,12 +0,0 @@ - ':attribute muss akzeptiert werden.', - 'array' => ':attribute muss ein Array sein.', - 'digits' => ':attribute muss aus :digits Zahlen bestehen.', - 'digits_between' => ':attribute muss aus :min bis :max Zahlen bestehen.', - 'email' => 'E-Mail', - 'ip' => ':attribute muss eine gültige IP Adresse sein.', - 'required' => 'Benötigt', - 'totp' => 'Das TOTP Token ist ungültig. Ist es abgelaufen?', -]; diff --git a/resources/lang/en/admin/pack.php b/resources/lang/en/admin/pack.php deleted file mode 100644 index e3a175f6..00000000 --- a/resources/lang/en/admin/pack.php +++ /dev/null @@ -1,16 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -return [ - 'notices' => [ - 'pack_updated' => 'Pack has been successfully updated.', - 'pack_deleted' => 'Successfully deleted the pack ":name" from the system.', - 'pack_created' => 'A new pack was successfully created on the system and is now available for deployment to servers.', - ], -]; diff --git a/resources/lang/en/admin/server.php b/resources/lang/en/admin/server.php index fa254c8d..a697d4e9 100644 --- a/resources/lang/en/admin/server.php +++ b/resources/lang/en/admin/server.php @@ -27,5 +27,8 @@ return [ 'details_updated' => 'Server details have been successfully updated.', 'docker_image_updated' => 'Successfully changed the default Docker image to use for this server. A reboot is required to apply this change.', 'node_required' => 'You must have at least one node configured before you can add a server to this panel.', + 'transfer_nodes_required' => 'You must have at least two nodes configured before you can transfer servers.', + 'transfer_started' => 'Server transfer has been started.', + 'transfer_not_viable' => 'The node you selected is not viable for this transfer.', ], ]; diff --git a/resources/lang/en/auth.php b/resources/lang/en/auth.php index 176de8f6..2a3a4526 100644 --- a/resources/lang/en/auth.php +++ b/resources/lang/en/auth.php @@ -1,22 +1,27 @@ 'You are not authorized to perform this action.', - 'auth_error' => 'There was an error while attempting to login.', - 'authentication_required' => 'Authentication is required to continue.', - 'remember_me' => 'Remember Me', 'sign_in' => 'Sign In', - 'forgot_password' => 'I\'ve forgotten my password!', - 'request_reset_text' => 'Forgotten your account password? It is not the end of the world, just provide your email below.', - 'reset_password_text' => 'Reset your account password.', - 'reset_password' => 'Reset Account Password', - 'email_sent' => 'An email has been sent to you with further instructions for resetting your password.', - 'failed' => 'The credentials provided do not match those we have on record, or the 2FA token provided was invalid.', + 'go_to_login' => 'Go to Login', + 'failed' => 'No account matching those credentials could be found.', + + 'forgot_password' => [ + 'label' => 'Forgot Password?', + 'label_help' => 'Enter your account email address to receive instructions on resetting your password.', + 'button' => 'Recover Account', + ], + + 'reset_password' => [ + 'button' => 'Reset and Sign In', + ], + + 'two_factor' => [ + 'label' => '2-Factor Token', + 'label_help' => 'This account requires a second layer of authentication in order to continue. Please enter the code generated by your device to complete this login.', + 'checkpoint_failed' => 'The two-factor authentication token was invalid.', + ], + 'throttle' => 'Too many login attempts. Please try again in :seconds seconds.', - 'password_requirements' => 'Passwords must contain at least one uppercase, lowercase, and numeric character and must be at least 8 characters in length.', - 'request_reset' => 'Locate Account', - '2fa_required' => '2-Factor Authentication', - '2fa_failed' => 'The 2FA token provided was invalid.', - 'totp_failed' => 'There was an error while attempting to validate TOTP.', + 'password_requirements' => 'Password must be at least 8 characters in length and should be unique to this site.', '2fa_must_be_enabled' => 'The administrator has required that 2-Factor Authentication be enabled for your account in order to use the Panel.', ]; diff --git a/resources/lang/en/base.php b/resources/lang/en/base.php deleted file mode 100644 index e2c66106..00000000 --- a/resources/lang/en/base.php +++ /dev/null @@ -1,89 +0,0 @@ - 'There was an error with one or more fields in the request.', - 'errors' => [ - 'return' => 'Return to Previous Page', - 'home' => 'Go Home', - '403' => [ - 'header' => 'Forbidden', - 'desc' => 'You do not have permission to access this resource on this server.', - ], - '404' => [ - 'header' => 'File Not Found', - 'desc' => 'We were unable to locate the requested resource on the server.', - ], - 'installing' => [ - 'header' => 'Server Installing', - 'desc' => 'The requested server is still completing the install process. Please check back in a few minutes, you should receive an email as soon as this process is completed.', - ], - 'suspended' => [ - 'header' => 'Server Suspended', - 'desc' => 'This server has been suspended and cannot be accessed.', - ], - 'maintenance' => [ - 'header' => 'Node Under Maintenance', - 'title' => 'Temporarily Unavailable', - 'desc' => 'This node is under maintenance, therefore your server can temporarily not be accessed.', - ], - ], - 'index' => [ - 'header' => 'Your Servers', - 'header_sub' => 'Servers you have access to.', - 'list' => 'Server List', - ], - 'api' => [ - 'index' => [ - 'list' => 'Your Keys', - 'header' => 'Account API', - 'header_sub' => 'Manage access keys that allow you to perform actions against the panel.', - 'create_new' => 'Create New API key', - 'keypair_created' => 'An API key has been successfully generated and is listed below.', - ], - 'new' => [ - 'header' => 'New API Key', - 'header_sub' => 'Create a new account access key.', - 'form_title' => 'Details', - 'descriptive_memo' => [ - 'title' => 'Description', - 'description' => 'Enter a brief description of this key that will be useful for reference.', - ], - 'allowed_ips' => [ - 'title' => 'Allowed IPs', - 'description' => 'Enter a line delimited list of IPs that are allowed to access the API using this key. CIDR notation is allowed. Leave blank to allow any IP.', - ], - ], - ], - 'account' => [ - 'details_updated' => 'Your account details have been successfully updated.', - 'invalid_password' => 'The password provided for your account was not valid.', - 'header' => 'Your Account', - 'header_sub' => 'Manage your account details.', - 'update_pass' => 'Update Password', - 'update_email' => 'Update Email Address', - 'current_password' => 'Current Password', - 'new_password' => 'New Password', - 'new_password_again' => 'Repeat New Password', - 'new_email' => 'New Email Address', - 'first_name' => 'First Name', - 'last_name' => 'Last Name', - 'update_identity' => 'Update Identity', - 'username_help' => 'Your username must be unique to your account, and may only contain the following characters: :requirements.', - 'language' => 'Language', - ], - 'security' => [ - 'session_mgmt_disabled' => 'Your host has not enabled the ability to manage account sessions via this interface.', - 'header' => 'Account Security', - 'header_sub' => 'Control active sessions and 2-Factor Authentication.', - 'sessions' => 'Active Sessions', - '2fa_header' => '2-Factor Authentication', - '2fa_token_help' => 'Enter the 2FA Token generated by your app (Google Authenticator, Authy, etc.).', - 'disable_2fa' => 'Disable 2-Factor Authentication', - '2fa_enabled' => '2-Factor Authentication is enabled on this account and will be required in order to login to the panel. If you would like to disable 2FA, simply enter a valid token below and submit the form.', - '2fa_disabled' => '2-Factor Authentication is disabled on your account! You should enable 2FA in order to add an extra level of protection on your account.', - 'enable_2fa' => 'Enable 2-Factor Authentication', - '2fa_qr' => 'Configure 2FA on Your Device', - '2fa_checkpoint_help' => 'Use the 2FA application on your phone to take a picture of the QR code to the left, or manually enter the code under it. Once you have done so, generate a token and enter it below.', - '2fa_disable_error' => 'The 2FA token provided was not valid. Protection has not been disabled for this account.', - ], -]; diff --git a/resources/lang/en/command/messages.php b/resources/lang/en/command/messages.php index 8741fc42..71731a2d 100644 --- a/resources/lang/en/command/messages.php +++ b/resources/lang/en/command/messages.php @@ -42,6 +42,10 @@ return [ ], 'server' => [ 'rebuild_failed' => 'Rebuild request for ":name" (#:id) on node ":node" failed with error: :message', + 'reinstall' => [ + 'failed' => 'Reinstall request for ":name" (#:id) on node ":node" failed with error: :message', + 'confirm' => 'You are about to reinstall against a group of servers. Do you wish to continue?', + ], 'power' => [ 'confirm' => 'You are about to perform a :action against :count servers. Do you wish to continue?', 'action_failed' => 'Power action request for ":name" (#:id) on node ":node" failed with error: :message', diff --git a/resources/lang/en/dashboard/account.php b/resources/lang/en/dashboard/account.php new file mode 100644 index 00000000..85411ef6 --- /dev/null +++ b/resources/lang/en/dashboard/account.php @@ -0,0 +1,28 @@ + [ + 'title' => 'Update your email', + 'updated' => 'Your email address has been updated.', + ], + 'password' => [ + 'title' => 'Change your password', + 'requirements' => 'Your new password should be at least 8 characters in length.', + 'updated' => 'Your password has been updated.', + ], + 'two_factor' => [ + 'button' => 'Configure 2-Factor Authentication', + 'disabled' => 'Two-factor authentication has been disabled on your account. You will no longer be prompted to provide a token when logging in.', + 'enabled' => 'Two-factor authentication has been enabled on your account! From now on, when logging in, you will be required to provide the code generated by your device.', + 'invalid' => 'The token provided was invalid.', + 'setup' => [ + 'title' => 'Setup two-factor authentication', + 'help' => 'Can\'t scan the code? Enter the code below into your application:', + 'field' => 'Enter token', + ], + 'disable' => [ + 'title' => 'Disable two-factor authentication', + 'field' => 'Enter token', + ], + ], +]; diff --git a/resources/lang/en/dashboard/index.php b/resources/lang/en/dashboard/index.php new file mode 100644 index 00000000..8ab11e99 --- /dev/null +++ b/resources/lang/en/dashboard/index.php @@ -0,0 +1,8 @@ + 'Search for servers...', + 'no_matches' => 'There were no servers found matching the search criteria provided.', + 'cpu_title' => 'CPU', + 'memory_title' => 'Memory', +]; diff --git a/resources/lang/en/exceptions.php b/resources/lang/en/exceptions.php index f1d58c6c..4d44c4ff 100644 --- a/resources/lang/en/exceptions.php +++ b/resources/lang/en/exceptions.php @@ -4,7 +4,7 @@ return [ 'daemon_connection_failed' => 'There was an exception while attempting to communicate with the daemon resulting in a HTTP/:code response code. This exception has been logged.', 'node' => [ 'servers_attached' => 'A node must have no servers linked to it in order to be deleted.', - 'daemon_off_config_updated' => 'The daemon configuration has been updated, however there was an error encountered while attempting to automatically update the configuration file on the Daemon. You will need to manually update the configuration file (core.json) for the daemon to apply these changes.', + 'daemon_off_config_updated' => 'The daemon configuration has been updated, however there was an error encountered while attempting to automatically update the configuration file on the Daemon. You will need to manually update the configuration file (config.yml) for the daemon to apply these changes.', ], 'allocations' => [ 'server_using' => 'A server is currently assigned to this allocation. An allocation can only be deleted if no server is currently assigned.', @@ -32,15 +32,6 @@ return [ 'invalid_json_provided' => 'The JSON file provided is not in a format that can be recognized.', ], ], - 'packs' => [ - 'delete_has_servers' => 'Cannot delete a pack that is attached to active servers.', - 'update_has_servers' => 'Cannot modify the associated option ID when servers are currently attached to a pack.', - 'invalid_upload' => 'The file provided does not appear to be valid.', - 'invalid_mime' => 'The file provided does not meet the required type :type', - 'unreadable' => 'The archive provided could not be opened by the server.', - 'zip_extraction' => 'An exception was encountered while attempting to extract the archive provided onto the server.', - 'invalid_archive_exception' => 'The pack archive provided appears to be missing a required archive.tar.gz or import.json file in the base directory.', - ], 'subusers' => [ 'editing_self' => 'Editing your own subuser account is not permitted.', 'user_is_owner' => 'You cannot add the server owner as a subuser for this server.', diff --git a/resources/lang/en/navigation.php b/resources/lang/en/navigation.php deleted file mode 100644 index c97c18a7..00000000 --- a/resources/lang/en/navigation.php +++ /dev/null @@ -1,32 +0,0 @@ - 'Home', - 'account' => [ - 'header' => 'ACCOUNT MANAGEMENT', - 'my_account' => 'My Account', - 'security_controls' => 'Security Controls', - 'api_access' => 'Account API', - 'my_servers' => 'My Servers', - ], - 'server' => [ - 'header' => 'SERVER MANAGEMENT', - 'console' => 'Console', - 'console-pop' => 'Fullscreen Console', - 'file_management' => 'File Management', - 'file_browser' => 'File Browser', - 'create_file' => 'Create File', - 'upload_files' => 'Upload Files', - 'subusers' => 'Subusers', - 'schedules' => 'Schedules', - 'configuration' => 'Configuration', - 'port_allocations' => 'Allocation Settings', - 'sftp_settings' => 'SFTP Settings', - 'startup_parameters' => 'Startup Parameters', - 'databases' => 'Databases', - 'edit_file' => 'Edit File', - 'admin_header' => 'ADMINISTRATIVE', - 'admin' => 'Server Configuration', - 'server_name' => 'Server Name', - ], -]; diff --git a/resources/lang/en/server.php b/resources/lang/en/server.php deleted file mode 100644 index 7782db9b..00000000 --- a/resources/lang/en/server.php +++ /dev/null @@ -1,334 +0,0 @@ - [ - 'title' => 'Viewing Server :name', - 'header' => 'Server Console', - 'header_sub' => 'Control your server in real time.', - ], - 'schedule' => [ - 'header' => 'Schedule Manager', - 'header_sub' => 'Manage all of this server\'s schedules in one place.', - 'current' => 'Current Schedules', - 'new' => [ - 'header' => 'Create New Schedule', - 'header_sub' => 'Create a new set of scheduled tasks for this server.', - 'submit' => 'Create Schedule', - ], - 'manage' => [ - 'header' => 'Manage Schedule', - 'submit' => 'Update Schedule', - 'delete' => 'Delete Schedule', - ], - 'task' => [ - 'time' => 'After', - 'action' => 'Perform Action', - 'payload' => 'With Payload', - 'add_more' => 'Add Another Task', - ], - 'actions' => [ - 'command' => 'Send Command', - 'power' => 'Power Action', - ], - 'toggle' => 'Toggle Status', - 'run_now' => 'Trigger Schedule', - 'schedule_created' => 'Successfully created a new schedule for this server.', - 'schedule_updated' => 'Schedule has been updated.', - 'unnamed' => 'Unnamed Schedule', - 'setup' => 'Schedule Setup', - 'day_of_week' => 'Day of Week', - 'day_of_month' => 'Day of Month', - 'hour' => 'Hour of Day', - 'minute' => 'Minute of Hour', - 'time_help' => 'The schedule system supports the use of Cronjob syntax when defining when tasks should begin running. Use the fields above to specify when these tasks should begin running or select options from the multiple select menus.', - 'task_help' => 'Times for tasks are relative to the previously defined task. Each schedule may have no more than 5 tasks assigned to it and tasks may not be scheduled more than 15 minutes apart.', - ], - 'tasks' => [ - 'task_created' => 'Successfully created a new task on the Panel.', - 'task_updated' => 'Task has successfully been updated. Any currently queued task actions will be cancelled and run again at the next defined time.', - 'header' => 'Scheduled Tasks', - 'header_sub' => 'Automate your server.', - 'current' => 'Current Scheduled Tasks', - 'actions' => [ - 'command' => 'Send Command', - 'power' => 'Send Power Option', - ], - 'new_task' => 'Add New Task', - 'toggle' => 'Toggle Status', - 'new' => [ - 'header' => 'New Task', - 'header_sub' => 'Create a new scheduled task for this server.', - 'task_name' => 'Task Name', - 'day_of_week' => 'Day of Week', - 'custom' => 'Custom Value', - 'day_of_month' => 'Day of Month', - 'hour' => 'Hour', - 'minute' => 'Minute', - 'sun' => 'Sunday', - 'mon' => 'Monday', - 'tues' => 'Tuesday', - 'wed' => 'Wednesday', - 'thurs' => 'Thursday', - 'fri' => 'Friday', - 'sat' => 'Saturday', - 'submit' => 'Create Task', - 'type' => 'Task Type', - 'chain_then' => 'Then, After', - 'chain_do' => 'Do', - 'chain_arguments' => 'With Arguments', - 'payload' => 'Task Payload', - 'payload_help' => 'For example, if you selected Send Command enter the command here. If you selected Send Power Option put the power action here (e.g. restart).', - ], - 'edit' => [ - 'header' => 'Manage Task', - 'submit' => 'Update Task', - ], - ], - 'users' => [ - 'header' => 'Manage Users', - 'header_sub' => 'Control who can access your server.', - 'configure' => 'Configure Permissions', - 'list' => 'Accounts with Access', - 'add' => 'Add New Subuser', - 'update' => 'Update Subuser', - 'user_assigned' => 'Successfully assigned a new subuser to this server.', - 'user_updated' => 'Successfully updated permissions.', - 'edit' => [ - 'header' => 'Edit Subuser', - 'header_sub' => 'Modify user\'s access to server.', - ], - 'new' => [ - 'header' => 'Add New User', - 'header_sub' => 'Add a new user with permissions to this server.', - 'email' => 'Email Address', - 'email_help' => 'Enter the email address for the user you wish to invite to manage this server.', - 'power_header' => 'Power Management', - 'file_header' => 'File Management', - 'subuser_header' => 'Subuser Management', - 'server_header' => 'Server Management', - 'task_header' => 'Schedule Management', - 'database_header' => 'Database Management', - 'power_start' => [ - 'title' => 'Start Server', - 'description' => 'Allows user to start the server.', - ], - 'power_stop' => [ - 'title' => 'Stop Server', - 'description' => 'Allows user to stop the server.', - ], - 'power_restart' => [ - 'title' => 'Restart Server', - 'description' => 'Allows user to restart the server.', - ], - 'power_kill' => [ - 'title' => 'Kill Server', - 'description' => 'Allows user to kill the server process.', - ], - 'send_command' => [ - 'title' => 'Send Console Command', - 'description' => 'Allows sending a command from the console. If the user does not have stop or restart permissions they cannot send the application\'s stop command.', - ], - 'access_sftp' => [ - 'title' => 'SFTP Allowed', - 'description' => 'Allows user to connect to the SFTP server provided by the daemon.', - ], - 'list_files' => [ - 'title' => 'List Files', - 'description' => 'Allows user to list all files and folders on the server but not view file contents.', - ], - 'edit_files' => [ - 'title' => 'Edit Files', - 'description' => 'Allows user to open a file for viewing only. SFTP is not effected by this permission.', - ], - 'save_files' => [ - 'title' => 'Save Files', - 'description' => 'Allows user to save modified file contents. SFTP is not effected by this permission.', - ], - 'move_files' => [ - 'title' => 'Rename & Move Files', - 'description' => 'Allows user to move and rename files and folders on the filesystem.', - ], - 'copy_files' => [ - 'title' => 'Copy Files', - 'description' => 'Allows user to copy files and folders on the filesystem.', - ], - 'compress_files' => [ - 'title' => 'Compress Files', - 'description' => 'Allows user to make archives of files and folders on the system.', - ], - 'decompress_files' => [ - 'title' => 'Decompress Files', - 'description' => 'Allows user to decompress .zip and .tar(.gz) archives.', - ], - 'create_files' => [ - 'title' => 'Create Files', - 'description' => 'Allows user to create a new file within the panel.', - ], - 'upload_files' => [ - 'title' => 'Upload Files', - 'description' => 'Allows user to upload files through the file manager.', - ], - 'delete_files' => [ - 'title' => 'Delete Files', - 'description' => 'Allows user to delete files from the system.', - ], - 'download_files' => [ - 'title' => 'Download Files', - 'description' => 'Allows user to download files. If a user is given this permission they can download and view file contents even if that permission is not assigned on the panel.', - ], - 'list_subusers' => [ - 'title' => 'List Subusers', - 'description' => 'Allows user to view a listing of all subusers assigned to the server.', - ], - 'view_subuser' => [ - 'title' => 'View Subuser', - 'description' => 'Allows user to view permissions assigned to subusers.', - ], - 'edit_subuser' => [ - 'title' => 'Edit Subuser', - 'description' => 'Allows a user to edit permissions assigned to other subusers.', - ], - 'create_subuser' => [ - 'title' => 'Create Subuser', - 'description' => 'Allows user to create additional subusers on the server.', - ], - 'delete_subuser' => [ - 'title' => 'Delete Subuser', - 'description' => 'Allows a user to delete other subusers on the server.', - ], - 'view_allocations' => [ - 'title' => 'View Allocations', - 'description' => 'Allows user to view all of the IPs and ports assigned to a server.', - ], - 'edit_allocation' => [ - 'title' => 'Edit Default Connection', - 'description' => 'Allows user to change the default connection allocation to use for a server.', - ], - 'view_startup' => [ - 'title' => 'View Startup Command', - 'description' => 'Allows user to view the startup command and associated variables for a server.', - ], - 'edit_startup' => [ - 'title' => 'Edit Startup Command', - 'description' => 'Allows a user to modify startup variables for a server.', - ], - 'list_schedules' => [ - 'title' => 'List Schedules', - 'description' => 'Allows a user to list all schedules (enabled and disabled) for this server.', - ], - 'view_schedule' => [ - 'title' => 'View Schedule', - 'description' => 'Allows a user to view a specific schedule\'s details including all of the assigned tasks.', - ], - 'toggle_schedule' => [ - 'title' => 'Toggle Schedule', - 'description' => 'Allows a user to toggle a schedule to be active or inactive.', - ], - 'queue_schedule' => [ - 'title' => 'Queue Schedule', - 'description' => 'Allows a user to queue a schedule to run it\'s tasks on the next process cycle.', - ], - 'edit_schedule' => [ - 'title' => 'Edit Schedule', - 'description' => 'Allows a user to edit a schedule including all of the schedule\'s tasks. This will allow the user to remove individual tasks, but not delete the schedule itself.', - ], - 'create_schedule' => [ - 'title' => 'Create Schedule', - 'description' => 'Allows a user to create a new schedule.', - ], - 'delete_schedule' => [ - 'title' => 'Delete Schedule', - 'description' => 'Allows a user to delete a schedule from the server.', - ], - 'view_databases' => [ - 'title' => 'View Database Details', - 'description' => 'Allows user to view all databases associated with this server including the usernames and passwords for the databases.', - ], - 'reset_db_password' => [ - 'title' => 'Reset Database Password', - 'description' => 'Allows a user to reset passwords for databases.', - ], - 'delete_database' => [ - 'title' => 'Delete Databases', - 'description' => 'Allows a user to delete databases for this server from the Panel.', - ], - 'create_database' => [ - 'title' => 'Create Database', - 'description' => 'Allows a user to create additional databases for this server.', - ], - ], - ], - 'allocations' => [ - 'mass_actions' => 'Mass Actions', - 'delete' => 'Delete Allocations', - ], - 'files' => [ - 'exceptions' => [ - 'invalid_mime' => 'This type of file cannot be edited via the Panel\'s built-in editor.', - 'max_size' => 'This file is too large to edit via the Panel\'s built-in editor.', - ], - 'header' => 'File Manager', - 'header_sub' => 'Manage all of your files directly from the web.', - 'loading' => 'Loading initial file structure, this could take a few seconds.', - 'path' => 'When configuring any file paths in your server plugins or settings you should use :path as your base path. The maximum size for web-based file uploads to this node is :size.', - 'seconds_ago' => 'seconds ago', - 'file_name' => 'File Name', - 'size' => 'Size', - 'last_modified' => 'Last Modified', - 'add_new' => 'Add New File', - 'add_folder' => 'Add New Folder', - 'mass_actions' => 'Mass Actions', - 'delete' => 'Delete Files', - 'edit' => [ - 'header' => 'Edit File', - 'header_sub' => 'Make modifications to a file from the web.', - 'save' => 'Save File', - 'return' => 'Return to File Manager', - ], - 'add' => [ - 'header' => 'New File', - 'header_sub' => 'Create a new file on your server.', - 'name' => 'File Name', - 'create' => 'Create File', - ], - ], - 'config' => [ - 'name' => [ - 'header' => 'Server Name', - 'header_sub' => 'Change this server\'s name.', - 'details' => 'The server name is only a reference to this server on the panel, and will not affect any server specific configurations that may display to users in games.', - ], - 'startup' => [ - 'header' => 'Start Configuration', - 'header_sub' => 'Control server startup arguments.', - 'command' => 'Startup Command', - 'edit_params' => 'Edit Parameters', - 'update' => 'Update Startup Parameters', - 'startup_regex' => 'Input Rules', - 'edited' => 'Startup variables have been successfully edited. They will take effect the next time this server is started.', - ], - 'sftp' => [ - 'header' => 'SFTP Configuration', - 'header_sub' => 'Account details for SFTP connections.', - 'details' => 'SFTP Details', - 'conn_addr' => 'Connection Address', - 'warning' => 'The SFTP password is your account password. Ensure that your client is set to use SFTP and not FTP or FTPS for connections, there is a difference between the protocols.', - ], - 'database' => [ - 'header' => 'Databases', - 'header_sub' => 'All databases available for this server.', - 'your_dbs' => 'Configured Databases', - 'host' => 'MySQL Host', - 'reset_password' => 'Reset Password', - 'no_dbs' => 'There are no databases listed for this server.', - 'add_db' => 'Add a new database.', - ], - 'allocation' => [ - 'header' => 'Server Allocations', - 'header_sub' => 'Control the IPs and ports available on this server.', - 'available' => 'Available Allocations', - 'help' => 'Allocation Help', - 'help_text' => 'The list to the left includes all available IPs and ports that are open for your server to use for incoming connections.', - ], - ], -]; diff --git a/resources/lang/en/server/users.php b/resources/lang/en/server/users.php new file mode 100644 index 00000000..ce77c410 --- /dev/null +++ b/resources/lang/en/server/users.php @@ -0,0 +1,33 @@ + [ + 'websocket_*' => 'Allows access to the websocket for this server.', + 'control_console' => 'Allows the user to send data to the server console.', + 'control_start' => 'Allows the user to start the server instance.', + 'control_stop' => 'Allows the user to stop the server instance.', + 'control_restart' => 'Allows the user to restart the server instance.', + 'control_kill' => 'Allows the user to kill the server instance.', + 'user_create' => 'Allows the user to create new user accounts for the server.', + 'user_read' => 'Allows the user permission to view users associated with this server.', + 'user_update' => 'Allows the user to modify other users associated with this server.', + 'user_delete' => 'Allows the user to delete other users associated with this server.', + 'file_create' => 'Allows the user permission to create new files and directories.', + 'file_read' => 'Allows the user to see files and folders associated with this server instance, as well as view their contents.', + 'file_update' => 'Allows the user to update files and folders associated with the server.', + 'file_delete' => 'Allows the user to delete files and directories.', + 'file_archive' => 'Allows the user to create file archives and decompress existing archives.', + 'file_sftp' => 'Allows the user to perform the above file actions using a SFTP client.', + 'allocation_read' => 'Allows access to the server allocation management pages.', + 'allocation_update' => 'Allows user permission to make modifications to the server\'s allocations.', + 'database_create' => 'Allows user permission to create a new database for the server.', + 'database_read' => 'Allows user permission to view the server databases.', + 'database_update' => 'Allows a user permission to make modifications to a database. If the user does not have the "View Password" permission as well they will not be able to modify the password.', + 'database_delete' => 'Allows a user permission to delete a database instance.', + 'database_view_password' => 'Allows a user permission to view a database password in the system.', + 'schedule_create' => 'Allows a user to create a new schedule for the server.', + 'schedule_read' => 'Allows a user permission to view schedules for a server.', + 'schedule_update' => 'Allows a user permission to make modifications to an existing server schedule.', + 'schedule_delete' => 'Allows a user to delete a schedule for the server.', + ], +]; diff --git a/resources/lang/en/strings.php b/resources/lang/en/strings.php index 2407dcd3..ddade2ce 100644 --- a/resources/lang/en/strings.php +++ b/resources/lang/en/strings.php @@ -2,9 +2,11 @@ return [ 'email' => 'Email', + 'email_address' => 'Email address', 'user_identifier' => 'Username or Email', 'password' => 'Password', - 'confirm_password' => 'Confirm Password', + 'new_password' => 'New password', + 'confirm_password' => 'Confirm new password', 'login' => 'Login', 'home' => 'Home', 'servers' => 'Servers', @@ -86,4 +88,8 @@ return [ 'sat' => 'Saturday', ], 'last_used' => 'Last Used', + 'enable' => 'Enable', + 'disable' => 'Disable', + 'save' => 'Save', + 'copyright' => '© 2015 - :year Pterodactyl Software', ]; diff --git a/resources/lang/en/validation.php b/resources/lang/en/validation.php index 201880ec..a82aaa1b 100644 --- a/resources/lang/en/validation.php +++ b/resources/lang/en/validation.php @@ -101,5 +101,6 @@ return [ // Internal validation logic for Pterodactyl 'internal' => [ 'variable_value' => ':env variable', + 'invalid_password' => 'The password provided was invalid for this account.', ], ]; diff --git a/resources/lang/es/admin/nests.php b/resources/lang/es/admin/nests.php deleted file mode 100644 index 83cb0c43..00000000 --- a/resources/lang/es/admin/nests.php +++ /dev/null @@ -1,32 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -return [ - 'notices' => [ - 'service_created' => 'Un nuevo nido, a :name, ha sido creado con éxito.', - 'service_deleted' => 'Se ha eliminado correctamente el nido solicitado del panel.', - 'service_updated' => 'Se actualizaron correctamente las opciones de configuración del nido.', - 'functions_updated' => 'Se ha actualizado el archivo de funciones de nido. Tendrá que reiniciar los nodos para que estos cambios se apliquen.', - ], - 'options' => [ - 'notices' => [ - 'option_deleted' => 'Se ha eliminado correctamente la opción del huevo solicitada del Panel.', - 'option_updated' => 'Opción del huevo se ha actualizado correctamente.', - 'script_updated' => 'Opción del huevo de script de instalación se ha actualizado y se ejecutará cuando se instalan servidores.', - 'option_created' => 'Nueva opción del huevo se ha creado correctamente. Es necesario reiniciar los demonios ejecutándose para aplicar este nuevo huevo.', - ], - ], - 'variables' => [ - 'notices' => [ - 'variable_deleted' => 'La variable ":variable" se ha eliminado y ya no estará disponible para los servidores una vez reconstruida.', - 'variable_updated' => 'Se ha actualizado la variable ":variable". Es necesario reconstruir los servidores que utilizan esta variable con el fin de aplicar los cambios.', - 'variable_created' => 'Nueva variable de éxito ha sido creado y asignado a esta opción del huevo.', - ], - ], -]; diff --git a/resources/lang/es/admin/node.php b/resources/lang/es/admin/node.php deleted file mode 100644 index 7a5c70e1..00000000 --- a/resources/lang/es/admin/node.php +++ /dev/null @@ -1,23 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -return [ - 'validation' => [ - 'fqdn_not_resolvable' => 'El FQDN o la dirección IP proporcionada no se resuelve a una dirección IP válida.', - 'fqdn_required_for_ssl' => 'Se requiere de la onu FQDN que resuelva una dirección IP pública para poder usar SSL para este nodo.', - ], - 'notices' => [ - 'allocations_added' => 'Las asignaciones se han agregado con éxito un este nodo.', - 'node_deleted' => 'Nodo se ha eliminado con éxito desde el panel de.', - 'location_required' => 'Necesita al menos una ubicación configurada antes de poder agregar la onu un nodo este panel.', - 'node_created' => 'Creado con éxito nuevo nodo. Puede configurar automáticamente el daemon en esta máquina visitando la pestaña \'Configuración\'. Antes de que usted puede agregar cualquier cantidad de servidores primero debe asignar al menos una dirección IP y el puerto.', - 'node_updated' => 'La información del nodo se ha actualizado. Si se ha cambiado la configuración del demonio, deberá reiniciarlo para que los cambios surtan efecto.', - 'unallocated_deleted' => 'Se eliminaron todos los puertos asignados para :ip.', - ], -]; diff --git a/resources/lang/es/admin/pack.php b/resources/lang/es/admin/pack.php deleted file mode 100644 index 6940ec3b..00000000 --- a/resources/lang/es/admin/pack.php +++ /dev/null @@ -1,16 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -return [ - 'notices' => [ - 'pack_updated' => 'El paquete se ha actualizado correctamente.', - 'pack_deleted' => 'Eliminado correctamente el paquete ":name" del sistema.', - 'pack_created' => 'Un nuevo paquete se creó con éxito en el sistema y ahora está disponible para la implementación en los servidores.', - ], -]; diff --git a/resources/lang/es/admin/server.php b/resources/lang/es/admin/server.php deleted file mode 100644 index 0b77f44d..00000000 --- a/resources/lang/es/admin/server.php +++ /dev/null @@ -1,31 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -return [ - 'exceptions' => [ - 'no_new_default_allocation' => 'Está intentando eliminar la asignación predeterminada para este servidor, pero no hay una asignación alternativa para usar.', - 'marked_as_failed' => 'Este servidor fue marcado como que falló una instalación previa. El estado actual no se puede cambiar en este estado.', - 'bad_variable' => 'Hubo un error de validación con la variable: name.', - 'daemon_exception' => 'Hubo una excepción al intentar comunicarse con el daemon que dio como resultado un código de respuesta HTTP /:code. Esta excepción ha sido registrada.', - 'default_allocation_not_found' => 'La asignación predeterminada solicitada no se encontró en las asignaciones de este servidor.', - ], - 'alerts' => [ - 'startup_changed' => 'Se ha actualizado la configuración de inicio de este servidor. Si se ha cambiado el servicio o la opción de este servidor una reinstalación será ocurriendo ahora.', - 'server_deleted' => 'Se ha eliminado correctamente el servidor.', - 'server_created' => 'El servidor se creó correctamente en el panel. Por favor, permite que el demonio de unos pocos minutos para instalar por completo este servidor.', - 'build_updated' => 'Los detalles de la compilación para este servidor se han actualizado. Algunos cambios pueden requerir un reinicio para tener efecto.', - 'suspension_toggled' => 'El estado de la suspensión del servidor se ha cambiado a :status.', - 'rebuild_on_boot' => 'Este servidor se ha marcado como que requiere una reconstrucción de Contenedor Docker. El servidor se reconstruirá la próxima vez que se inicie.', - 'install_toggled' => 'Se ha cambiado el estado de la instalación de este servidor.', - 'server_reinstalled' => 'Este servidor se ha puesto en cola para una reinstalación que comenzar ahora.', - 'details_updated' => 'Los detalles del servidor se han actualizado correctamente.', - 'docker_image_updated' => 'Cambió correctamente la imagen predeterminada de Docker para utilizarla en este servidor. Se requiere un reinicio para aplicar este cambio.', - 'node_required' => 'Debe tener al menos un nodo configurado antes de poder agregar un servidor a este panel.', - ], -]; diff --git a/resources/lang/es/admin/user.php b/resources/lang/es/admin/user.php deleted file mode 100644 index 066652e2..00000000 --- a/resources/lang/es/admin/user.php +++ /dev/null @@ -1,18 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -return [ - 'exceptions' => [ - 'user_has_servers' => 'No se puede eliminar un usuario con servidores activos conectados a su cuenta. Elimine los servidores antes de continuar.', - ], - 'notices' => [ - 'account_created' => 'La cuenta se ha creado correctamente.', - 'account_updated' => 'La cuenta se ha actualizado correctamente.', - ], -]; diff --git a/resources/lang/es/auth.php b/resources/lang/es/auth.php deleted file mode 100644 index 7171865b..00000000 --- a/resources/lang/es/auth.php +++ /dev/null @@ -1,28 +0,0 @@ - 'La autenticación de dos pasos falló.', - '2fa_must_be_enabled' => 'El administrador ha requerido activar 2-Factor Authentication en tu cuenta para utilizar el panel', - '2fa_required' => 'Autenticación de dos pasos', - 'authentication_required' => 'Autenticación es requerida para continuar.', - 'auth_error' => 'Hubo un error mientras se intentaba ingresar.', - 'confirmpassword' => 'Confirmar contraseña', - 'emailsent' => 'Tu email de reinicio de contraseña está en camino.', - 'email_sent' => 'Un email te ha sido enviado con instrucciones para reiniciar tu contraseña.', - 'errorencountered' => 'Hubo un error intentando procesar esta petición.', - 'failed' => 'Estas credenciales no coinciden con nuestros registros.', - 'forgot_password' => 'He olvidado mi contraseña!', - 'not_authorized' => 'No estas autorizado para realizar esta acción. ', - 'password_requirements' => 'La contraseña debe contener al menos una mayúscula, minúscula un carácter numérico y debe ser al menos de 8 caracteres.', - 'remeberme' => 'Recuerda me', - 'remember_me' => 'Recuerda me ', - 'request_reset' => 'Localizar cuenta', - 'request_reset_text' => 'Olvidaste tu contraseña? No es el fin del mundo, solo provee tu correo abajo.', - 'resetpassword' => 'Cambiar constraseña', - 'reset_password' => 'Restaurar contraseña ', - 'reset_password_text' => 'Restablece la contraseña de su cuenta.', - 'sendlink' => 'Enviar Link Para Resetear la Contraseña', - 'sign_in' => 'Iniciar Sesión', - 'throttle' => 'Demasiados intentos de inicio de sesión. Por favor, inténtelo de nuevo en :seconds segundos.', - 'totp_failed' => 'Hubo un error al intentar validar TOTP.', -]; diff --git a/resources/lang/es/base.php b/resources/lang/es/base.php deleted file mode 100644 index eec59b76..00000000 --- a/resources/lang/es/base.php +++ /dev/null @@ -1,375 +0,0 @@ - [ - 'current_password' => 'Contraseña actual', - 'delete_user' => 'Eliminar usuario', - 'details_updated' => 'Los detalles de su cuenta se han actualizado correctamente.', - 'email_password' => 'Enviar contraseña al email', - 'exception' => 'Ocurrió un error al intentar actualizar tu cuenta', - 'first_name' => 'Nombre', - 'header' => 'Tu cuenta', - 'header_sub' => 'Administra los detalles de tu cuenta.', - 'invalid_pass' => 'Esta contraseña es inválida para esta cuenta', - 'invalid_password' => 'La contraseña proporcionada por su cuenta no era válido.', - 'last_name' => 'Apellido', - 'new_email' => 'Nuevo email', - 'new_password' => 'Nueva contraseña', - 'new_password_again' => 'Repetir la nueva contraseña', - 'totp_apps' => 'Necesitas una app que soporte TOTP (p.ej. Google Authenticator, DUO Mobile, Authy, Enpass) para usar esta función.', - 'totp_checkpoint_help' => 'Por favor escanea con tu teléfono el código QR a la derecha para verificar tu configuración TOTP, y escribe abajo el código de 6 dígitos generado. Pulsa la tecla enter cuando hayas terminado.', - 'totp_disable' => 'Desactivar la autenticación de dos pasos', - 'totp_disable_help' => 'Para desactivar la autenticación de dos pasos en esta cuenta es necesario un token de TOTP válido. Cuando sea proporcionado, se desactivará la autenticación de dos pasos.', - 'totp_enable' => 'Activar la autenticación de dos pasos', - 'totp_enabled' => 'Se ha activado la autenticación de dos pasos en tu cuenta. Pulsa "Cerrar" para terminar.', - 'totp_enabled_error' => 'El token TOTP provisto no ha podido ser verificado. Por favor, inténtalo de nuevo.', - 'totp_enable_help' => 'Parece que no tienes activada la autenticación de dos pasos. Este método añade una barrera adicional frente el uso no autorizado de tu cuenta. Tras activarlo, además de la contraseña será necesario que proveas un código que te será enviado al teléfono, email u otro medio soportado configurado en el sistema.', - 'totp_header' => 'Autenticación de dos pasos', - 'totp_qr' => 'Código QR de TOTP', - 'totp_token' => 'Token TOTP', - 'update_email' => 'Actualizar email', - 'update_identitity' => 'Actualizar identidad', - 'update_identity' => 'Actualización De La Identidad', - 'update_pass' => 'Cambiar contraseña', - 'update_user' => 'Actualizar usuario', - 'username_help' => 'Tu nombre de usuario debe ser único, y solo puede contener estos caracteres: :requirements.', - ], - 'api' => [ - 'index' => [ - 'create_new' => 'Crea nueva API Key', - 'header' => 'Acceso a la API', - 'header_sub' => 'Gestiona tus acceso de API Keys', - 'keypair_created' => 'Una API Key-Pair se ha generado. Su API token secreto es :token. Por favor, tome nota de esta clave como no se mostrará de nuevo.', - 'list' => 'API Keys', - ], - 'new' => [ - 'allowed_ips' => [ - 'description' => 'Introduzca una lista delimitada por líneas de direcciones IP a las que se les permite acceder a la API mediante esta clave. Se permite la notación CIDR. Deje en blanco para permitir cualquier IP.', - 'title' => 'Autorizar IPs', - ], - 'base' => [ - 'information' => [ - 'description' => 'Regresa a la lista de todos los servidores que esta cuenta tiene acceso.', - 'title' => 'Información básica ', - ], - 'title' => 'Información básica ', - ], - 'descriptive_memo' => [ - 'description' => 'Ingresa una breve descripción del propósito de uso de esta API Key.', - 'title' => 'Memo descriptivo', - ], - 'form_title' => 'Detalles', - 'header' => 'Nueva API Key', - 'header_sub' => 'Crea un nuevo acceso de API Key', - 'location_management' => [ - 'list' => [ - 'description' => 'Permite el listado de todas las locaciones y sus nodos asociados.', - 'title' => 'Lista de locaciones ', - ], - 'title' => 'Gestor de locación', - ], - 'node_management' => [ - 'allocations' => [ - 'description' => 'Permite ver todas las asignaciones de recursos en el panel para todos los nodos.', - 'title' => 'Lista de asignaciones', - ], - 'create' => [ - 'description' => 'Permite crear un nuevo nodo en el sistema.', - 'title' => 'Crear Nodo', - ], - 'delete' => [ - 'description' => 'Permite la eliminación de nodos', - 'title' => 'Eliminar Nodos', - ], - 'list' => [ - 'description' => 'Permite listado de todos los nodos existentes en el sistema.', - 'title' => 'Enumerar nodos', - ], - 'title' => 'Gestión de nodos', - 'view' => [ - 'description' => 'Permite ver detalles sobre un nodo en especifico incluyendo servicios activos.', - 'title' => 'Lista de nodo único', - ], - ], - 'server_management' => [ - 'build' => [ - 'description' => 'Permite la modificación los parámetros del servidor como memoria, CPU, y espacio del disco junto con una un asignado y IPs por defecto.', - 'title' => 'Actualizar parámetros', - ], - 'command' => [ - 'description' => 'Permite un usuario enviar comandos a un servidor en especifico.', - 'title' => 'Enviar comando', - ], - 'config' => [ - 'description' => 'Permite modificar configuraciones del servidor (nombre, dueño, y token de acceso).', - 'title' => 'Actualizar configuraciones', - ], - 'create' => [ - 'description' => 'Permite crear un nuevo servidor en el sistema.', - 'title' => 'Crear servidor', - ], - 'delete' => [ - 'description' => 'Permite eliminar un servidor del sistema.', - 'title' => 'Eliminar servidor', - ], - 'list' => [ - 'description' => 'Permite listar todos los servidores actualmente en el sistema.', - 'title' => 'Listar servidores', - ], - 'power' => [ - 'description' => 'Permite acceso al control del encendido y apagado del servidor.', - 'title' => 'Estado de funcionamiento del servidor', - ], - 'server' => [ - 'description' => 'Permite acceso a la visualización de la información para un solo servidor, incluyendo estadísticas y asignación de recursos.', - 'title' => 'Información del servidor', - ], - 'suspend' => [ - 'description' => 'Permite suspender una instancia del servidor.', - 'title' => 'Suspender servidor', - ], - 'title' => 'Administrar servidor', - 'unsuspend' => [ - 'description' => 'Permite eliminar la suspensión de una instancia del servidor.', - 'title' => 'Reactivar servidor', - ], - 'view' => [ - 'description' => 'Permite ver los detalles sobre un servidor específico incluyendo el daemon_token además de la información actual de procesos.', - 'title' => 'Mostrar solo este servidor', - ], - ], - 'service_management' => [ - 'list' => [ - 'description' => 'Permite listar todos los servicios configurados en el sistema.', - 'title' => 'Enumerar servicios', - ], - 'title' => 'Administración de servicios', - 'view' => [ - 'description' => 'Permite listar detalles sobre cada servicio en el sistema incluyendo opciones y variables.', - 'title' => 'Listar solo este servicio', - ], - ], - 'user_management' => [ - 'create' => [ - 'description' => 'Permite crear un nuevo usuario en el sistema.', - 'title' => 'Crear usuario', - ], - 'delete' => [ - 'description' => 'Permite borrar un usuario.', - 'title' => 'Eliminar usuario', - ], - 'list' => [ - 'description' => 'Permite listado de todos los usuarios existentes en el sistema.', - 'title' => 'Lista de usuarios', - ], - 'title' => 'Gestor de usuario', - 'update' => [ - 'description' => 'Permite la modificación de detalles de usuario (correo, contraseña, información TOTP).', - 'title' => 'Actualizar usuario', - ], - 'view' => [ - 'description' => 'Permite ver los detalles sobre un usuario especifico, incluyendo los servicios activos.', - 'title' => 'Lista de usuario único', - ], - ], - ], - 'permissions' => [ - 'admin' => [ - 'location' => [ - 'list' => [ - 'desc' => 'Permite el listado de locaciones y sus nodos asociados.', - 'title' => 'Lista de locaciones', - ], - ], - 'location_header' => 'Control de locación', - 'node' => [ - 'create' => [ - 'desc' => 'Permite la creación de un nuevo nodo en el sistema.', - 'title' => 'Crear Nodo', - ], - 'delete' => [ - 'desc' => 'Permite la eliminación de un nodo desde el sistema.', - 'title' => 'Eliminar Nodo', - ], - 'list' => [ - 'desc' => 'Permite el listado de todos los nodos existentes en el sistema.', - 'title' => 'Lista de Nodos', - ], - 'view-config' => [ - 'desc' => 'Peligro. Esto permite la vista del archivo de configuración que es usado por daemon, y expone los tokens secreto de daemon.', - 'title' => 'Ver configuración de los nodos', - ], - 'view' => [ - 'desc' => 'Permite ver detalles sobre un nodo especifico incluyendo servicios activos.', - 'title' => 'Ver Nodo', - ], - ], - 'node_header' => 'Control del Nodo', - 'option' => [ - 'list' => [ - 'title' => 'Lista de opciones', - ], - 'view' => [ - 'title' => 'Ver opciones ', - ], - ], - 'option_header' => 'Opciones de Control', - 'pack' => [ - 'list' => [ - 'title' => 'Lista de paquetes', - ], - 'view' => [ - 'title' => 'Ver paquete', - ], - ], - 'pack_header' => 'Control de paquete', - 'server' => [ - 'create' => [ - 'desc' => 'Permite la creación de un nuevo servidor en el sistema.', - 'title' => 'Crear servidor', - ], - 'delete' => [ - 'desc' => 'Permite la eliminación de un servidor desde el sistema.', - 'title' => 'Eliminar Servidor', - ], - 'edit-build' => [ - 'desc' => 'Permite la edición de los parámetros del servidor como la asignación de CPU y memoria.', - 'title' => 'Editar parámetros del servidor', - ], - 'edit-container' => [ - 'desc' => 'Permite la modificación del contenedor de docker el cual el servidor ejecutara.', - 'title' => 'Editar contenedor del servidor', - ], - 'edit-details' => [ - 'desc' => 'Permite la edición de detalles del servidor como nombre, dueño, descripción, Key secreta.', - 'title' => 'Edita detalles del servidor', - ], - 'edit-startup' => [ - 'desc' => 'Permite la modificación del comando de inicio y parámetros del servidor.', - 'title' => 'Editar inicio del servidor', - ], - 'install' => [ - 'title' => 'Cambiar estado de instalación', - ], - 'list' => [ - 'desc' => 'Permite listado de todos los servidores existentes en el sistema.', - 'title' => 'Lista de servidores', - ], - 'rebuild' => [ - 'title' => 'Re instalar servidor', - ], - 'suspend' => [ - 'desc' => 'Permite la suspensión y quitar suspensiones a un servidor.', - 'title' => 'Suspender servidor', - ], - 'view' => [ - 'desc' => 'Permite ver un solo servidor incluyendo servicios y detalles.', - 'title' => 'Ver servidor', - ], - ], - 'server_header' => 'Control del servidor', - 'service' => [ - 'list' => [ - 'desc' => 'Permite listado de todos los servicios configurado en el sistema.', - 'title' => 'Lista de servicios', - ], - 'view' => [ - 'desc' => 'Permite listado de detalles sobre el servicio en el sistema incluyendo opciones del servicio y variables.', - 'title' => 'Ver servicio', - ], - ], - 'service_header' => 'Control de servicio', - 'user' => [ - 'create' => [ - 'desc' => 'Permite la creación de un nuevo usuario en el sistema.', - 'title' => 'Crear usuario', - ], - 'delete' => [ - 'desc' => 'Permite la eliminación de un usuario.', - 'title' => 'Eliminar usuario', - ], - 'edit' => [ - 'desc' => 'Permite modificaciones de detalles del usuario.', - 'title' => 'Actualizar usuario', - ], - 'list' => [ - 'desc' => 'Permite listado de todos los usuarios existente en el sistema.', - 'title' => 'Lista de usuarios', - ], - 'view' => [ - 'desc' => 'Permite ver detalles sobre un usuario en especifico incluyendo servicios activos.', - 'title' => 'Ver usuario', - ], - ], - 'user_header' => 'Control de usuario', - ], - 'user' => [ - 'server' => [ - 'command' => [ - 'desc' => 'Permite envió de un comando a un servidor activo.', - 'title' => 'Enviar comando.', - ], - 'list' => [ - 'desc' => 'Permite listado de todos los servidores a un usuario que sea propietario o tenga acceso a los subusuarios.', - 'title' => 'Lista de servidores', - ], - 'power' => [ - 'desc' => 'Permite cambiar el estatus de poder para un servidor.', - 'title' => 'Alternar poder', - ], - 'view' => [ - 'desc' => 'Permite ver un servidor en especifico que un usuario pueda acceder.', - 'title' => 'Ver servidor', - ], - ], - 'server_header' => 'Permisos de usuario en el servidor', - ], - ], - ], - 'confirm' => 'Esta seguro?', - 'errors' => [ - '403' => [ - 'desc' => 'No tienes permiso para acceder a este recurso en este servidor.', - 'header' => 'Prohibido', - ], - '404' => [ - 'desc' => 'No hemos podido localizar el recurso solicitado en este servidor.', - 'header' => '404 archivo no encontrado', - ], - 'home' => 'Ir a Home', - 'installing' => [ - 'desc' => 'El servidor solicitante sigue completando el proceso de instalación. Por favor hecha un vistazo en unos minutos, recibirá un correo tan pronto el proceso se haya completado.', - 'header' => 'Instalando servidor', - ], - 'return' => 'Volver a la página anterior', - 'suspended' => [ - 'desc' => 'El servidor a sido suspendido y no podrá ser accedido.', - 'header' => 'Servidor suspendido', - ], - ], - 'form_error' => 'El siguiente error fuer encontrado mientras se estaba procesando el pedido.', - 'index' => [ - 'header' => 'Consola del servidor', - 'header_sub' => 'Controla tu servidor en tiempo real.', - 'list' => 'Lista de servidores', - ], - 'no_servers' => 'Actualmente no tienes ningún servidor asignado en tu cuenta.', - 'password_req' => 'La contraseña de tener los siguiente requerimientos: por lo menos una letra en mayúscula, una letra en minúscula, un dígito y, debe ser como mínimo 8 caracteres.', - 'security' => [ - '2fa_checkpoint_help' => 'Utilice el 2FA aplicación en su teléfono para tomar una foto del código QR de la izquierda, o introducir manualmente el código debajo de ella. Una vez hecho esto, generar un token y entrar en él a continuación.', - '2fa_disabled' => '2-Factor de Autenticación está deshabilitado en tu cuenta! Usted debe habilitar 2FA con el fin de añadir un nivel extra de protección en su cuenta.', - '2fa_disable_error' => 'El 2FA token proporcionado no es válido. La protección no ha sido deshabilitado para esta cuenta.', - '2fa_enabled' => '2-Factor de Autenticación está habilitada en esta cuenta y será necesario iniciar la sesión en el panel de. Si usted desea deshabilitar el 2FA, simplemente ingrese un token válido a continuación y envíe el formulario.', - '2fa_header' => '2-Factor De Autenticación', - '2fa_qr' => 'Confgure 2FA en Su Dispositivo', - '2fa_token_help' => 'Introduzca el 2FA Token generado por la aplicación (Google Authenticator, Authy, etc.).', - 'disable_2fa' => 'Deshabilitar 2-Factor De Autenticación', - 'enable_2fa' => 'Habilitar 2-Factor De Autenticación', - 'header' => 'Seguridad De La Cuenta', - 'header_sub' => 'Control de sesiones activas y 2-Factor de Autenticación.', - 'sessions' => 'Sesiones Activas', - 'session_mgmt_disabled' => 'Su anfitrión no ha habilitado la capacidad de gestionar la cuenta de las sesiones a través de esta interfaz.', - ], - 'server_name' => 'Nombre del servidor', - 'validation_error' => 'Hubo un error con uno o más campos en la solicitud.', -]; diff --git a/resources/lang/es/command/messages.php b/resources/lang/es/command/messages.php deleted file mode 100644 index 7fcc7457..00000000 --- a/resources/lang/es/command/messages.php +++ /dev/null @@ -1,91 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -return [ - 'location' => [ - 'no_location_found' => 'No se pudo localizar un registro coincidente el código corto.', - 'ask_short' => 'Ubicación De Código Corto', - 'ask_long' => 'Descripción De La Localización', - 'created' => 'Creado con éxito una nueva ubicación (:name) con un ID :id.', - 'deleted' => 'Elimina correctamente la ubicación solicitada.', - ], - 'user' => [ - 'search_users' => 'Introduzca un nombre de Usuario, UUID, o Dirección de Correo electrónico', - 'select_search_user' => 'Id del usuario para borrar (Usa 0, para buscar).', - 'deleted' => 'Usuario borrado con éxito desde el Panel de.', - 'confirm_delete' => 'Está seguro de que desea borrar este usuario desde el Panel?', - 'no_users_found' => 'Los usuarios No se encontraron resultados para el término de búsqueda proporcionado.', - 'multiple_found' => 'Varias cuentas se han encontrado para la proporcionada por el usuario, no se puede eliminar un usuario, porque de el --no-interacción de la bandera.', - 'ask_admin' => 'Es este usuario administrador?', - 'ask_email' => 'Dirección De Correo Electrónico', - 'ask_username' => 'Nombre de usuario', - 'ask_name_first' => 'Primer Nombre', - 'ask_name_last' => 'Apellido', - 'ask_password' => 'Contraseña', - 'ask_password_tip' => 'Si desea crear una cuenta con una contraseña aleatoria enviado por correo electrónico al usuario, vuelva a ejecutar este comando (CTRL+C) y pasar el `--no-password` de la bandera.', - 'ask_password_help' => 'Las contraseñas deben tener al menos 8 caracteres y contener al menos una letra mayúscula y el número.', - '2fa_help_text' => [ - 'Este comando desactivará la autenticación de 2 factores para la cuenta de un usuario si está habilitada. Esto sólo debe ser utilizado como una cuenta de recuperación de comando si el usuario está bloqueado de su cuenta.', - 'Si esto no es lo que quería hacer, presione CTRL+C para salir de este proceso.', - ], - '2fa_disabled' => '2-Factor de autenticación ha sido desactivado por :email.', - ], - 'schedule' => [ - 'output_line' => 'Despacho de trabajo para la primera tarea en `programar` (:hash).', - ], - 'maintenance' => [ - 'deleting_service_backup' => 'Eliminar el servicio de copia de seguridad de archivo :file.', - ], - 'server' => [ - 'rebuild_failed' => 'Reconstruir la solicitud de ":name" (#:id) en el nodo ":node" con el error: :message', - ], - 'environment' => [ - 'mail' => [ - 'ask_smtp_host' => 'Host SMTP (e.g. smtp.gmail.com)', - 'ask_smtp_port' => 'Puerto SMTP', - 'ask_smtp_username' => 'El nombre de Usuario SMTP', - 'ask_smtp_password' => 'Contraseña SMTP', - 'ask_mailgun_domain' => 'Mailgun De Dominio', - 'ask_mailgun_secret' => 'Mailgun Secreto', - 'ask_mandrill_secret' => 'Mandrill Secreto', - 'ask_postmark_username' => 'Matasellos Clave de API', - 'ask_driver' => 'El controlador que debe ser utilizado para el envío de correos electrónicos?', - 'ask_mail_from' => 'Dirección de correo electrónico los correos electrónicos se originan a partir de', - 'ask_mail_name' => 'Nombre que los correos electrónicos deben aparecer a partir de', - 'ask_encryption' => 'Método de encriptación a utilizar', - ], - 'database' => [ - 'host_warning' => 'Es muy recomendable no usar "localhost" como el host de base de datos, como hemos visto, los frecuentes problemas de conexión de socket. Si desea utilizar una conexión local debe ser el uso de "127.Cero.Cero.1".', - 'host' => 'Host De Base De Datos', - 'port' => 'Puerto De Base De Datos', - 'database' => 'Nombre De Base De Datos', - 'username_warning' => 'El uso de la "raíz" de la cuenta para las conexiones de MySQL no sólo es muy mal visto, no está permitido por esta aplicación. Necesitarás haber creado un usuario de MySQL para este software.', - 'username' => 'Base De Datos De Nombre De Usuario', - 'password_defined' => 'Parece que ya tiene un usuario y contraseña de conexión definido, te gustaría cambiar?', - 'password' => 'Contraseña De Base De Datos', - 'connection_error' => 'No se puede conectar con el servidor MySQL usando los credenciales. El error devuelto fue ":error".', - 'creds_not_saved' => 'Sus credenciales de conexión NO se han guardado. Usted tendrá que proporcionar conexión válida la información antes de proceder.', - 'try_again' => 'Volver y probar otra vez?', - ], - 'app' => [ - 'app_url_help' => 'La dirección URL de la aplicación DEBE comenzar con https:// o http:// dependiendo de si usted está usando SSL o no. Si no se incluye el esquema de sus correos electrónicos y otros contenidos proporcionará un enlace a la ubicación incorrecta.', - 'app_url' => 'Dirección URL de la aplicación', - 'timezone_help' => 'La zona horaria debe coincidir con una de las zonas horarias compatibles de PHP. Si usted no está seguro, por favor consulte http://php.net/manual/es/zonas horarias.php.', - 'timezone' => 'La Zona Horaria De Aplicación', - 'cache_driver' => 'Caché De Controlador', - 'session_driver' => 'Controlador De Sesión', - 'using_redis' => 'Ha seleccionado el controlador Redis para una o más opciones, proporcione la información de conexión válida a continuación. En la mayoría de los casos se pueden utilizar los valores predeterminados a menos que usted haya modificado su configuración.', - 'redis_host' => 'Redis Host', - 'redis_password' => 'Redis Contraseña', - 'redis_port' => 'Redis Puerto', - 'redis_pass_defined' => 'Parece una contraseña ya está definida para Redis, te gustaría cambiar?', - 'redis_pass_help' => 'De forma predeterminada, un servidor Redis instancia no tiene contraseña ya que se ejecuta localmente y inaccessable para el mundo exterior. Si este es el caso, simplemente pulsa enter sin introducir un valor.', - ], - ], -]; diff --git a/resources/lang/es/exceptions.php b/resources/lang/es/exceptions.php deleted file mode 100644 index 907f9674..00000000 --- a/resources/lang/es/exceptions.php +++ /dev/null @@ -1,56 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -return [ - 'daemon_connection_failed' => 'Hubo una excepción al intentar comunicarse con el demonio que resulta en un HTTP/:code código de respuesta. Esta excepción ha sido registrado.', - 'node' => [ - 'servers_attached' => 'Un nodo no debe tener servidores vinculados a la misma, en orden a ser eliminados.', - 'daemon_off_config_updated' => 'La configuración del demonio se ha actualizado, sin embargo hubo un error al intentar actualizar automáticamente el archivo de configuración del Demonio. Usted tendrá que actualizar manualmente el archivo de configuración (core.json) para el demonio para aplicar estos cambios. El demonio respondió con un HTTP/:code código de respuesta y el error ha sido iniciado.', - ], - 'allocations' => [ - 'too_many_ports' => 'La adición de más de 1000 puertos en un único momento no es compatible. Por favor, use un rango menor.', - 'invalid_mapping' => 'La cartografía proporcionada por :port no era válido y no puede ser procesado.', - 'cidr_out_of_range' => 'La notación CIDR sólo permite máscaras entre los /25 e /32.', - ], - 'service' => [ - 'delete_has_servers' => 'Un servicio con los servidores activos conectados a no se puede eliminar desde el Panel de.', - 'options' => [ - 'delete_has_servers' => 'Una opción de servicio con los servidores activos conectados a no se puede eliminar desde el Panel de.', - 'invalid_copy_id' => 'La opción de servicio seleccionado para la copia de una secuencia de comandos o bien no existe, o es copia de un mismo script.', - 'must_be_child' => 'La "Configuración de la Copia De la" directiva para que esta opción debe ser un niño opción para el servicio seleccionado.', - ], - 'variables' => [ - 'env_not_unique' => 'La variable de entorno :name debe ser único para esta opción de servicio.', - 'reserved_name' => 'La variable de entorno :name está protegido y no puede ser asignado a una variable.', - ], - ], - 'packs' => [ - 'delete_has_servers' => 'No se puede eliminar un paquete que está conectado a los servidores activos.', - 'update_has_servers' => 'No puede modificar la opción asociada ID cuando los servidores están conectados actualmente a un pack.', - 'invalid_upload' => 'El archivo no parece ser válido.', - 'invalid_mime' => 'El archivo no cumple con los requisitos tipo :type', - 'unreadable' => 'El archivo siempre y no puede ser abierto por el servidor.', - 'zip_extraction' => 'Una excepción se encontró al intentar extraer el archivo proporcionado en el servidor.', - 'invalid_archive_exception' => 'El pack archivo siempre parece que falta un archivo necesaria.alquitrán.gz o de importación.archivo json en el directorio de base de.', - ], - 'subusers' => [ - 'editing_self' => 'La edición de su propio subuser cuenta no está permitido.', - 'user_is_owner' => 'Usted puede agregar el propietario del servidor como un subuser para este servidor.', - 'subuser_exists' => 'Un usuario con esa dirección de correo electrónico ya está asignado como subuser para este servidor.', - ], - 'databases' => [ - 'delete_has_databases' => 'No se puede eliminar una base de datos de servidor de host que tiene bases de datos activas vinculados a ella.', - ], - 'tasks' => [ - 'chain_interval_too_long' => 'El intervalo máximo de tiempo para un encadenado tarea es de 15 minutos.', - ], - 'locations' => [ - 'has_nodes' => 'No se puede eliminar una ubicación que tenga activa de los nodos conectados a él.', - ], -]; diff --git a/resources/lang/es/navigation.php b/resources/lang/es/navigation.php deleted file mode 100644 index 7f70b0d3..00000000 --- a/resources/lang/es/navigation.php +++ /dev/null @@ -1,29 +0,0 @@ - 'Home', - 'account' => [ - 'header' => 'GESTIÓN DE CUENTAS', - 'my_account' => 'Mi Cuenta', - 'security_controls' => 'Controles de Seguridad', - 'api_access' => 'Acceso de API', - 'my_servers' => 'Mis Servidores', - ], - 'server' => [ - 'header' => 'GESTIÓN DEL SERVIDOR', - 'console' => 'Consola', - 'console-pop' => 'Consola de Pantalla Completa', - 'file_management' => 'Gestión de Archivos', - 'file_browser' => 'Explorador de Archivos', - 'create_file' => 'Crea archivo', - 'upload_files' => 'Sube archivos', - 'subusers' => 'Subusadores', - 'schedules' => 'Horarios', - 'configuration' => 'Configuración', - 'port_allocations' => 'Asignaciones de Puertos', - 'sftp_settings' => 'Configuración de SFTP', - 'startup_parameters' => 'Parámetros de Inicio', - 'databases' => 'Bases de Datos', - 'edit_file' => 'Edita Archivo', - ], -]; diff --git a/resources/lang/es/pagination.php b/resources/lang/es/pagination.php deleted file mode 100644 index a3b15b62..00000000 --- a/resources/lang/es/pagination.php +++ /dev/null @@ -1,12 +0,0 @@ - 'Siguiente »', - 'previous' => '« Previos ', - 'sidebar' => [ - 'account_settings' => 'Configuración de la cuenta', - 'files' => 'Administrador de archivos', - 'manage' => 'Administrar servidor', - 'servers' => 'Tus servidores', - ], -]; diff --git a/resources/lang/es/passwords.php b/resources/lang/es/passwords.php deleted file mode 100644 index 366e6b49..00000000 --- a/resources/lang/es/passwords.php +++ /dev/null @@ -1,9 +0,0 @@ - 'Las contraseñas tienen que tener por lo menos 6 caracteres y la confirmación debe de coincidir.', - 'reset' => 'Tu contraseña ha sido restaurada!', - 'sent' => 'Le hemos enviado un correo de cambio de contraseña!', - 'token' => 'El código de cambio de contraseña es inválido.', - 'user' => 'No podemos encontrar un usuario con ese nombre.', -]; diff --git a/resources/lang/es/server.php b/resources/lang/es/server.php deleted file mode 100644 index 4c031e2d..00000000 --- a/resources/lang/es/server.php +++ /dev/null @@ -1,348 +0,0 @@ - [ - 'socket_error' => 'No ha sido posible conectar al servidor Socket.IO principal. Es posible que haya problemas de red, y el panel podría no funcionar correctamente.', - 'socket_status' => 'El estado de este servidor ha cambiado ha', - 'socket_status_crashed' => 'Este servidor fue detectado como ESTRELLADO.', - ], - 'config' => [ - 'allocation' => [ - 'available' => 'Asignaciones disponibles', - 'header' => 'Asignaciones de servidor/es', - 'header_sub' => 'Controla las IPs y puertos disponibles en este servidor.', - 'help' => 'Ayuda de asignación', - 'help_text' => 'La lista a la izquierda incluye todas las IPs y puertos disponibles para ser utilizadas por tu servidor.', - ], - 'database' => [ - 'add_db' => 'Añadir nueva base de datos.', - 'header' => 'Bases de datos', - 'header_sub' => 'Todas las bases de datos disponibles para este servidor.', - 'host' => 'Host MySQL', - 'no_dbs' => 'No hay bases de datos disponibles en este servidor.', - 'reset_password' => 'Reiniciar contraseña', - 'your_dbs' => 'Tus bases de datos', - ], - 'sftp' => [ - 'change_pass' => 'Cambiar la contraseña de SFTP', - 'conn_addr' => 'Dirección de conexión', - 'details' => 'Detalles de SFTP', - 'header' => 'Configuración de SFTP', - 'header_sub' => 'Detalles de las cuentas SFTP', - 'warning' => 'Asegúrate de que tu cliente esté configurado para utilizar el protocolo SFTP en vez de FTP o FTPS. Son protocolos diferentes no compatibles entre sí.', - ], - 'startup' => [ - 'command' => 'Comando de inicio', - 'edited' => 'La variable de inicio ha sido actualizada de manera exitosa. Tomara efecto la siguiente vez que reinicie el servidor.', - 'edit_params' => 'Editar parámetros', - 'header' => 'Comenzar configuración', - 'header_sub' => 'argumento de control de inicio del servidor.', - 'startup_regex' => 'Verificación Regex', - 'startup_var' => 'Variable de comando de inicio', - 'update' => 'Actualiza los parámetros de inicio', - ], - ], - 'files' => [ - 'add' => [ - 'create' => 'Crear archivo', - 'header' => 'Nuevo archivo', - 'header_sub' => 'Crear un nuevo archivo en tu servidor.', - 'name' => 'Nombre de archivo', - ], - 'add_folder' => 'Agregar nueva carpeta', - 'add_new' => 'Agregar nuevo archivo', - 'back' => 'Volver al gestor de archivos', - 'delete' => 'Eliminar', - 'edit' => [ - 'header' => 'Editar archivo', - 'header_sub' => 'Hacer modificación a un archivo desde la web.', - 'return' => 'Volver al gestor de archivos', - 'save' => 'Guardar archivo', - ], - 'exceptions' => [ - 'invalid_mime' => 'Este tipo de archivo no se puede editar a través del editor incorporado del Panel.', - 'list_directory' => 'Ha ocurrido un error intentado obtener los contenidos de este directorio. Por favor, vuelvalo a intentarr.', - 'max_size' => 'Este archivo es demasiado grande para editarlo a través del editor incorporado del Panel.', - ], - 'file_name' => 'Nombre de archivo', - 'header' => 'Gestor de archivos', - 'header_sub' => 'Gestiona todos tus archivos directamente desde la web.', - 'last_modified' => 'Ultima modificación', - 'loading' => 'Cargar estructura de archivo inicial, esto puede tomar pocos segundos.', - 'mass_actions' => 'Mas acciones', - 'path' => 'Cuando configures cualquier ubicación de un archivo en tu servidor plugins o configuraciones deberás usar :path como la ubicación base. El tamaño máximo del archivo base web para subir a este nodo es :size. -', - 'saved' => 'Archivo guardado con éxito.', - 'seconds_ago' => 'segundos transcurridos', - 'size' => 'Tamaño ', - 'yaml_notice' => 'Estas editando un archivo YAML. Este archivo no acepta tabulacion, deben usar espacio. Hemos logrado que presionar tabulador insertara un :dropdown spaces.', - ], - 'index' => [ - 'add_new' => 'Agregar un nuevo servidor', - 'allocation' => 'Asignación', - 'command' => 'Envía un comando a la consola', - 'connection' => 'Conexión por defecto', - 'control' => 'Control del servidor', - 'cpu_use' => 'Uso de CPU', - 'disk_space' => 'Espacio en el disco', - 'header' => 'Consola del servidor', - 'header_sub' => 'Controla tu servidor en tiempo real.', - 'memory_use' => 'Uso de memoria', - 'mem_limit' => 'Limite de memoria', - 'server_info' => 'Información del servidor', - 'title' => 'Ver nombre de servidor', - 'usage' => 'Uso', - 'xaxis' => 'Tiempo (2s de incremento)', - ], - 'schedule' => [ - 'actions' => [ - 'command' => 'Enviar Comando', - 'power' => 'El Poder De Acción', - ], - 'current' => 'Horarios Actualizados', - 'day_of_month' => 'Día de Mes', - 'day_of_week' => 'El día de la Semana', - 'header' => 'Schedule Manager', - 'header_sub' => 'Administre todos los horarios de este servidor en un solo lugar.', - 'hour' => 'La hora del Día', - 'manage' => [ - 'delete' => 'Eliminar Horario', - 'header' => 'Administrar Horario', - 'submit' => 'La Programación De Actualización', - ], - 'minute' => 'Hora de la Hora', - 'new' => [ - 'header' => 'Crear Nuevo Horario', - 'header_sub' => 'Crear un nuevo conjunto de tareas programadas para este servidor.', - 'submit' => 'Crear Calendario', - ], - 'schedule_created' => 'Se ha creado correctamente una tarea para este servidor.', - 'setup' => 'Configuración De La Programación', - 'task' => [ - 'action' => 'Realizar La Acción', - 'add_more' => 'Añadir Otra Tarea', - 'payload' => 'Con Una Carga Útil', - 'time' => 'Tras', - ], - 'task_help' => 'Los tiempos para las tareas relativas a la definida anteriormente tarea. Cada programa puede tener más de 5 tareas que se le asignen tareas y no puede ser programado más de 15 minutos de distancia.', - 'time_help' => 'La programación del sistema es compatible con el uso de Cronjob la sintaxis de la hora de definir cuando las tareas deben comenzar a correr. Utilice los campos de arriba para especificar cuando estas tareas deben empezar a ejecutar o seleccionar opciones de la selección múltiple de los menús.', - 'toggle' => 'Cambiar estado', - 'unnamed' => 'Sin Nombre Horario', - ], - 'tasks' => [ - 'actions' => [ - 'command' => 'Enviar Comando', - 'power' => 'Enviar La Opción De La Energía', - ], - 'current' => 'Actual De Las Tareas Programadas', - 'edit' => [ - 'header' => 'Gestionar Tareas', - 'submit' => 'La Tarea De Actualización', - ], - 'header' => 'Tareas Programadas', - 'header_sub' => 'Automatizar el servidor.', - 'new' => [ - 'chain_arguments' => 'Con Argumentos', - 'chain_do' => '¿', - 'chain_then' => 'Luego, Después De', - 'custom' => 'Valor Personalizado', - 'day_of_month' => 'Día de Mes', - 'day_of_week' => 'El día de la Semana', - 'fri' => 'Viernes', - 'header' => 'Nueva Tarea', - 'header_sub' => 'Crear una nueva tarea programada para este servidor.', - 'hour' => 'Hora', - 'minute' => 'Minutos', - 'mon' => 'Lunes', - 'payload' => 'La Tarea De Carga', - 'payload_help' => 'Por ejemplo, si selecciona Enviar Comando introduzca el comando. Si selecciona Enviar la Opción de la Energía poner el poder de la acción aquí (e.g. restart).', - 'sat' => 'Sábado', - 'submit' => 'Crear Tarea', - 'sun' => 'Domingo', - 'task_name' => 'Nombre De La Tarea', - 'thurs' => 'Jueves', - 'tues' => 'Martes', - 'type' => 'Tipo De Tarea', - 'wed' => 'Miércoles', - ], - 'new_task' => 'Agregar Nueva Tarea', - 'task_created' => 'Creado con éxito una nueva tarea en el Panel de.', - 'task_updated' => 'La tarea ha sido actualizado. Cualquier se encuentra en la cola de tareas acciones serán cancelados y ejecutar de nuevo en el próximo tiempo definido.', - 'toggle' => 'Cambiar Estado', - ], - 'users' => [ - 'add' => 'Agregar Nuevo Subuser', - 'configure' => 'Configurar Los Permisos De', - 'edit' => [ - 'header' => 'Editar Subuser', - 'header_sub' => 'Modificar el acceso del usuario al servidor.', - ], - 'header' => 'Administrar Usuarios', - 'header_sub' => 'Controlar quién puede acceder a su servidor de.', - 'list' => 'Cuentas con Acceso', - 'new' => [ - 'access_sftp' => [ - 'description' => 'Permite al usuario conectarse al servidor SFTP proporcionado por el daemon.', - 'title' => 'SFTP permitido', - ], - 'compress_files' => [ - 'description' => 'Permite que el usuario pueda hacer de los archivos de los archivos y carpetas en el sistema.', - 'title' => 'Comprimir Los Archivos', - ], - 'copy_files' => [ - 'description' => 'Permite a los usuarios copiar archivos y carpetas en el sistema de ficheros.', - 'title' => 'Copiar Archivos', - ], - 'create_files' => [ - 'description' => 'Permite al usuario crear un nuevo archivo en el panel de.', - 'title' => 'Crear Archivos', - ], - 'create_schedule' => [ - 'description' => 'Permite a un usuario crear una nueva programación.', - 'title' => 'Crear Calendario', - ], - 'create_subuser' => [ - 'description' => 'Permite al usuario crear más subusers en el servidor.', - 'title' => 'Crear Subuser', - ], - 'database_header' => 'Administración De Base De Datos', - 'decompress_files' => [ - 'description' => 'Permite que el usuario para descomprimir .zip y .alquitrán(.gz) archivos.', - 'title' => 'Descomprimir Los Archivos', - ], - 'delete_files' => [ - 'description' => 'Permite al usuario eliminar archivos del sistema.', - 'title' => 'Eliminar Archivos', - ], - 'delete_schedule' => [ - 'description' => 'Permite a un usuario para eliminar un programa desde el servidor.', - 'title' => 'Eliminar Horario', - ], - 'delete_subuser' => [ - 'description' => 'Permite a un usuario para eliminar otros subusers en el servidor.', - 'title' => 'Eliminar Subuser', - ], - 'download_files' => [ - 'description' => 'Permite al usuario descargar archivos. Si un usuario se da este permiso se puede descargar y ver el contenido del archivo, incluso si ese permiso no está asignado en el panel.', - 'title' => 'Descargar Archivos', - ], - 'edit_files' => [ - 'description' => 'Permite al usuario abrir un archivo solo para visualización.', - 'title' => 'Editar Archivos', - ], - 'edit_schedule' => [ - 'description' => 'Permite a un usuario editar un horario que incluye todas las tareas del cronograma. Esto permitirá al usuario eliminar tareas individuales, pero no eliminar el calendario en sí.', - 'title' => 'Modificar La Programación', - ], - 'edit_startup' => [ - 'description' => 'Permite que un usuario modifique el inicio variables para un servidor.', - 'title' => 'Edición De Comandos De Inicio', - ], - 'edit_subuser' => [ - 'description' => 'Permite a un usuario para editar los permisos asignados a otras subusers.', - 'title' => 'Editar Subuser', - ], - 'email' => 'Dirección De Correo Electrónico', - 'email_help' => 'Introduzca la dirección de correo electrónico para el usuario que quiere invitar a administrar este servidor.', - 'file_header' => 'La Gestión De Archivos', - 'header' => 'Añadir Nuevo Usuario', - 'header_sub' => 'Agregar un nuevo usuario con permisos para este servidor.', - 'list_files' => [ - 'description' => 'Permite al usuario a la lista de todos los archivos y carpetas en el servidor, pero no ver el contenido del archivo.', - 'title' => 'Lista De Archivos', - ], - 'list_schedules' => [ - 'description' => 'Permite a un usuario a la lista de todos los horarios (activado y desactivado) para este servidor.', - 'title' => 'Lista De Horarios', - ], - 'list_subusers' => [ - 'description' => 'Permite al usuario ver una lista de todos los subusers asignadas al servidor.', - 'title' => 'Lista De Subusers', - ], - 'move_files' => [ - 'description' => 'Permite al usuario mover y renombrar archivos y carpetas en el sistema de ficheros.', - 'title' => 'Renombrar Y Mover Archivos', - ], - 'power_header' => 'Administración De Energía', - 'power_kill' => [ - 'description' => 'Permite que el usuario pueda matar el proceso del servidor.', - 'title' => 'Matar Servidor', - ], - 'power_restart' => [ - 'description' => 'Permite al usuario reiniciar el servidor.', - 'title' => 'Reinicie El Servidor', - ], - 'power_start' => [ - 'description' => 'Permite al usuario iniciar el servidor.', - 'title' => 'Inicio Del Servidor', - ], - 'power_stop' => [ - 'description' => 'Permite al usuario detener el servidor.', - 'title' => 'Detener El Servidor', - ], - 'queue_schedule' => [ - 'description' => 'Permite a un usuario poner en cola un horario para ejecutar sus tareas en el siguiente ciclo de proceso.', - 'title' => 'Cola De Horario', - ], - 'reset_db_password' => [ - 'description' => 'Permite a un usuario para restablecer las contraseñas de las bases de datos de.', - 'title' => 'Restablecer Contraseña De Base De Datos', - ], - 'reset_sftp' => [ - 'description' => 'Le permite al usuario cambiar el SFTP contraseña para el servidor.', - 'title' => 'Restablecer Contraseña SFTP', - ], - 'save_files' => [ - 'description' => 'Permite que el usuario guarde el archivo modificado contenido.', - 'title' => 'Guardar Archivos', - ], - 'send_command' => [ - 'description' => 'Permite el envío de un comando desde la consola. Si el usuario no tiene permiso para detener o reiniciar, no puede enviar el comando de detención de la aplicación.', - 'title' => 'Enviar Comandos De La Consola', - ], - 'server_header' => 'Administración Del Servidor', - 'set_connection' => [ - 'description' => 'Permite al usuario establecer la conexión por defecto que se utiliza para un servidor, así como ver los puertos disponibles.', - 'title' => 'Conjunto De Conexión Predeterminado', - ], - 'sftp_header' => 'SFTP Gestión', - 'subuser_header' => 'Subuser De Gestión', - 'task_header' => 'La Programación De La Administración', - 'toggle_schedule' => [ - 'description' => 'Permite a un usuario para cambiar de un programa a ser activo o inactivo.', - 'title' => 'Alternar Horario', - ], - 'upload_files' => [ - 'description' => 'Permite a los usuarios cargar archivos a través del administrador de archivos.', - 'title' => 'Subir Archivos', - ], - 'view_databases' => [ - 'description' => 'Permite al usuario ver todas las bases de datos asociadas con este servidor, incluidos los nombres de usuario y contraseñas de las bases de datos de.', - 'title' => 'Ver Detalles De Base De Datos', - ], - 'view_schedule' => [ - 'description' => 'Permite a un usuario ver los detalles de un programa específico, incluidas todas las tareas asignadas', - 'title' => 'Ver Programación', - ], - 'view_sftp' => [ - 'description' => 'Permite al usuario ver la información SFTP del servidor pero no la contraseña.', - 'title' => 'Ver SFTP Detalles', - ], - 'view_sftp_password' => [ - 'description' => 'Permite al usuario ver el SFTP contraseña para el servidor.', - 'title' => 'Ver SFTP Contraseña', - ], - 'view_startup' => [ - 'description' => 'Permite al usuario ver los comandos de inicio y las variables asociadas a un servidor.', - 'title' => 'Vista De Comandos De Inicio', - ], - 'view_subuser' => [ - 'description' => 'Permite al usuario ver los permisos asignados a subusers.', - 'title' => 'Ver Subuser', - ], - ], - 'update' => 'Actualización Subuser', - 'user_assigned' => 'Correctamente asignado un nuevo subuser a este servidor.', - 'user_updated' => 'Actualizado correctamente los permisos de.', - ], -]; diff --git a/resources/lang/es/strings.php b/resources/lang/es/strings.php deleted file mode 100644 index 922cbafe..00000000 --- a/resources/lang/es/strings.php +++ /dev/null @@ -1,101 +0,0 @@ - 'Autenticación de dos pasos', - '2fa_token' => 'Token de autenticación', - 'account' => 'Cuenta', - 'action' => 'Acción', - 'admin' => 'Administrador', - 'admin_control' => 'Control de administración', - 'admin_cp' => 'Panel de Control Administrativo', - 'again' => 'Repetir', - 'api_access' => 'acceso a la API', - 'cancel' => 'Cancelar', - 'captcha_invalid' => 'El captcha es invalido.', - 'child_tasks' => 'Tareas', - 'close' => 'Cerrar', - 'configuration' => 'Configuraciones', - 'confirm_password' => 'Confirma la contraseña', - 'connection' => 'Corrección', - 'cpu' => 'CPU', - 'create' => 'Crear', - 'created' => 'Creado', - 'created_at' => 'Creado en', - 'current_password' => 'Contraseña actual', - 'danger' => 'Peligro', - 'data' => 'Dato', - 'database' => 'Base de dato', - 'databases' => 'Base de datos', - 'days' => [ - 'fri' => 'Viernes', - 'mon' => 'Lunes', - 'sat' => 'Sábado', - 'sun' => 'Domingo', - 'thurs' => 'Jueves', - 'tues' => 'Martes', - 'wed' => 'Miércoles', - ], - 'delete' => 'Borrar', - 'disabled' => 'Deshabilitado', - 'email' => 'Email', - 'enabled' => 'Activado', - 'expires' => 'Expira', - 'home' => 'Inicio', - 'id' => 'ID', - 'ip' => 'El :atribute debe ser una IP valida.', - 'language' => 'Idiomas', - 'last_activity' => 'Ultima actividad', - 'last_run' => 'Ultima ejecución', - 'location' => 'Ubicación', - 'login' => 'Iniciar Sesión', - 'logout' => 'Cerrar Sesión ', - 'make_primary' => 'Hacer primaria', - 'memo' => 'Memorándum', - 'memory' => 'Memoria', - 'minutes' => 'Minutos', - 'name' => 'Nombre', - 'never' => 'Nunca', - 'new' => 'Nuevo ', - 'next_run' => 'Siguiente ejecución', - 'no' => 'No', - 'node' => 'Nodo', - 'none' => 'Ninguno', - 'not_run_yet' => 'No ejecutado', - 'optional' => 'Opcional', - 'owner' => 'Dueño', - 'password' => 'Las contraseñas tienen que tener por lo menos 6 caracteres y la confirmación debe de coincidir.', - 'players' => 'Jugadores', - 'port' => 'Puerto ', - 'primary' => 'Primaria ', - 'public_key' => 'Llave publica', - 'queued' => 'En cola ', - 'read_only' => 'Solo leer', - 'registered' => 'Registrado ', - 'relation' => 'Relación ', - 'required' => 'El :atribute es requerido.', - 'restart' => 'Reiniciar', - 'revoke' => 'Revocar ', - 'root_administrator' => 'Administrador root', - 'save' => 'Guardar', - 'search' => 'Buscar', - 'seconds' => 'Segundos', - 'security' => 'Seguridad ', - 'select_all' => 'Selecciona Todo', - 'select_none' => 'Selecciona Ninguno', - 'servers' => 'Servidores', - 'settings' => 'Ajustes', - 'sftp' => 'SFTP', - 'sign_out' => 'Desconectar', - 'start' => 'Iniciar', - 'status' => 'Estado', - 'stop' => 'Parar', - 'submit' => 'Envia', - 'subuser' => 'Subusador', - 'success' => 'Correcto', - 'suspended' => 'Suspendido', - 'tasks' => 'Tareas', - 'username' => 'Nombre de usuario', - 'user_identifier' => 'Nombre de usuario o Email', - 'whoops' => 'Whoops', - 'yes' => 'Sí', -]; diff --git a/resources/lang/es/validation.php b/resources/lang/es/validation.php deleted file mode 100644 index 7a727d85..00000000 --- a/resources/lang/es/validation.php +++ /dev/null @@ -1,89 +0,0 @@ - 'El :attribute debe de ser aceptado.', - 'active_url' => 'El :attribute no es una URL válida.', - 'after' => 'El :attribute debe ser una fecha posterior a :date.', - 'after_or_equal' => 'El :attribute debe ser una fecha igual o posterior a :date.', - 'alpha' => 'El :attribute solo puede contener letras.', - 'alpha_dash' => 'El atributo solo debe contener letras, números y guiones.', - 'alpha_num' => 'El atributo solo debe contener letras y números.', - 'array' => 'el atributo debe ser array.', - 'attributes' => [ - 'password' => 'contraseña', - 'username' => 'usuario', - ], - 'before' => 'El :artibute debe ser fecha antes :date.', - 'before_or_equal' => 'El :atribute debe ser fecha antes o igual a :date.', - 'between' => [ - 'array' => 'El :atributo tiene entre :mim y :max detalles.', - 'file' => 'El :atribute debe ser entre :mim y :max Kilobytes', - 'numeric' => 'El :atribute debe ser entre :mim y :max', - 'string' => 'El :atribute debe ser entre :mim y :max caracteres.', - ], - 'boolean' => 'El :atribute debe ser verdadero o falso.', - 'confirmed' => 'El :atribute de confirmación no coincide.', - 'custom' => [ - 'attribute-name' => [ - 'rule-name' => 'mensaje personalizado', - ], - ], - 'date' => 'El :atribute no tiene una fecha valida.', - 'date_format' => 'El :atribute no marca el formato :format', - 'different' => 'El :attribute y :other deben de ser diferentes.', - 'digits' => 'El :attribute debe de estar compuesto por :digits dígitos.', - 'digits_between' => 'El :attribute debe de estar compuesto por entre :min y :max dígitos.', - 'dimensions' => 'El :attribute tiene dimensiones inválidas.', - 'distinct' => 'El campo :attribute tiene un valor duplicado.', - 'email' => 'Email', - 'exists' => 'El :attribute seleccionado es inválido.', - 'file' => ':attribute tiene que ser un archivo.', - 'filled' => 'El :atribute campo es requerido.', - 'image' => 'El :attribute debe ser una imagen.', - 'in' => 'El :attribute seleccionado es invalido.', - 'integer' => 'El :atribute debe ser entero.', - 'internal' => [ - 'variable_value' => ':env variable', - ], - 'in_array' => 'El :attribute no existe en :other.', - 'ip' => 'El :atribute debe ser una IP valida.', - 'json' => 'El :atribute debe ser una JSON string valida.', - 'max' => [ - 'array' => 'El :atribute no debe tener mas que :max objetos.', - 'file' => 'El :atribute debe no debe ser mayor que :max Kilobytes.', - 'numeric' => 'El :atribute no debe ser mayor que :max.', - 'string' => 'El :atribute no debe ser mayor que :max caracteres.', - ], - 'mimes' => 'El :atribute debe ser un archivo de tipo :values.', - 'mimetypes' => 'El :atribute debe ser un archivo de tipo :values.', - 'min' => [ - 'array' => 'El :atribute debe ser como mínimo :min objetos.', - 'file' => 'El :atribute debe ser como mínimo :min Kilobytes.', - 'numeric' => 'El :atribute debe ser como mínimo :min.', - 'string' => 'El :atribute debe ser como mínimo :min caracteres.', - ], - 'not_in' => 'El :atributed seleccionado no es valido.', - 'numeric' => 'El :atribute debe ser un numero.', - 'present' => 'El :atribute debe estar presente.', - 'regex' => 'El formato de :atribute es invalido.', - 'required' => 'El :atribute es requerido.', - 'required_if' => 'El :atribute es requerido cuando :other es :value.', - 'required_unless' => 'El :atribute es requerido a no ser que :other es en :value.', - 'required_with' => 'El :atribute es requerido cuando :values esta presente.', - 'required_without' => 'El :atribute es requerido cuando :values no esta presente.', - 'required_without_all' => 'El :atribute es requerido cuando nada de :values estén presente.', - 'required_with_all' => 'El :atribute es requerido cuando :values esta presente.', - 'same' => 'El :atribute y :other debe coincidir.', - 'size' => [ - 'array' => 'El campo :attribute debe contener :size elementos.', - 'file' => 'El campo :attribute debe tener :size kilobytes.', - 'numeric' => 'El campo :attribute debe ser :size.', - 'string' => 'El campo :attribute debe tener :size caracteres.', - ], - 'string' => 'El campo :attribute debe ser una cadena.', - 'timezone' => 'El campo :attribute debe ser una zona válida.', - 'totp' => 'El token totp es inválido. ¿Ha expirado?', - 'unique' => 'El campo :attribute ya ha sido tomado.', - 'uploaded' => 'El campo :attribute no ha podido ser cargado.', - 'url' => 'El formato de :attribute es inválido.', -]; diff --git a/resources/lang/et/auth.php b/resources/lang/et/auth.php deleted file mode 100644 index 97df4ebb..00000000 --- a/resources/lang/et/auth.php +++ /dev/null @@ -1,23 +0,0 @@ - '2-astmelise autentimise token oli vale', - '2fa_required' => '2-astmeline autentimine', - 'authentication_required' => 'Jätkamiseks on sisselogimine vajalik', - 'auth_error' => 'Logimisel on tekkinud viga ', - 'confirmpassword' => 'Kinnita salasõna', - 'email_sent' => 'Salasõna taastamiseks on Teie emailile saadetud juhised', - 'errorencountered' => 'Selle käsu täitmisel juhtus viga', - 'failed' => 'Kasutajanimi või salasõna on vale', - 'forgot_password' => 'Unustasid parooli?', - 'not_authorized' => 'Teil ei ole luba selle käsu sooritamiseks', - 'password_requirements' => 'Salasõna peab sisalda vähemalt ühte väikest tähte, suurt tähte, numbreid ja salasõna pikkus peab olema vähemalt 8 tähemärki', - 'remeberme' => 'Mäleta mind', - 'remember_me' => 'Mäleta mind', - 'resetpassword' => 'Taasta salasõna', - 'reset_password' => 'Taasta kasutaja salasõna ', - 'sendlink' => 'Saada salasõna taastamiseks email', - 'sign_in' => 'Logi sisse', - 'throttle' => 'Liiga mitu ebaõnnestunud sisselogimist. Proovige uuesti :seconds sekundi pärast', - 'totp_failed' => 'TOTP token oli väär. Veenduge et sisestatud token teie seadme jaoks on õige', -]; diff --git a/resources/lang/et/base.php b/resources/lang/et/base.php deleted file mode 100644 index 5f00e636..00000000 --- a/resources/lang/et/base.php +++ /dev/null @@ -1,176 +0,0 @@ - [ - 'current_password' => 'Praegune salasõna', - 'delete_user' => 'Kustuta kasutaja', - 'email_password' => 'Emaili salasõna', - 'exception' => 'Kasutaja uuendamisel tekkis viga', - 'first_name' => 'Eesnimi', - 'header' => 'Kasutaja haldamine', - 'header_sub' => 'Muuda oma kasutaja seadeid', - 'invalid_pass' => 'Sisestatud salasõna on väär', - 'last_name' => 'Perekonnanimi', - 'new_email' => 'Uus e-maili aadress', - 'new_password' => 'Uus salasõna', - 'new_password_again' => 'Korda uut salasõna', - 'totp_apps' => 'Sul peab olema TOTP toetav applikatsioon (nt Google Authenticator,DUO Mobile,Authy,Enpass), et kasutada seda', - 'totp_checkpoint_help' => 'Palun kinnita oma TOTP seaded skänneerides QR kood ja sisesta 6 numbriline kood', - 'totp_disable' => 'Keela 2-tasemeline autentimine', - 'totp_disable_help' => 'TOTP keelamiseks peate sisestama TOTP tokeni. Pärast kinnitust lülitatakse sellel kasutajal TOTP välja. ', - 'totp_enable' => 'Luba 2-astmeline autentimine', - 'totp_enabled' => 'Teie kasutaja on aktiveeritud TOTP kaudu. Palun väljuge, et lõpetada', - 'totp_enabled_error' => 'TOTP tokenit ei olnud võimalik kontrollida. Palun proovige uuesti.', - 'totp_enable_help' => 'Paistab, et Teil pole 2-astmeline autentimine aktiveeritud. See meetod lisab teie kasutajale turvalisust juurde. Selle aktiveerides peate te sisestama oma moblasse koodi, enne kui logite sisse', - 'totp_header' => '2-astmeline autentimine', - 'totp_qr' => 'TOTP QR kood', - 'totp_token' => 'TOTP Token', - 'update_email' => 'Uuenda email', - 'update_identitity' => 'Uuenda informatsiooni', - 'update_pass' => 'Uuenda salasõna', - 'update_user' => 'Uuenda kasutajat ', - 'username_help' => 'Sinu kasutajanimi peab olema unikaalne sinu kasutajale ja võib ainult sisaldada järgmiseid märke: :requirements', - ], - 'api' => [ - 'index' => [ - 'create_new' => 'Loo uus API võti ', - 'header' => 'API Ligipääs', - 'header_sub' => 'Halda oma API võtmeid', - 'list' => 'API Võtmed', - ], - 'new' => [ - 'allowed_ips' => [ - 'description' => 'Siesta nimekiri IP-dest, mis võivad ühendada API-sse selle võtmega. CIDR kasutamine on lubatud. Jäta tühjaks, et lubada igat IP-d', - 'title' => 'Lubatud IP-d', - ], - 'base' => [ - 'information' => [ - 'description' => 'Tagastab nimekirja serveritest, millega see kasutaja seotud on', - 'title' => 'Baas informatsioon', - ], - 'title' => 'Baas informatsioon', - ], - 'descriptive_memo' => [ - 'description' => 'Sisesta lühikirjeldus milleks uut API võtit kasutatakse ', - 'title' => 'Meeldetuletus', - ], - 'form_title' => 'Detailid', - 'header' => 'Uus API võti', - 'header_sub' => 'Loo uus API võti', - 'location_management' => [ - 'list' => [ - 'description' => 'Lubab kõikide kohtade ja nendega seotud nodede kuvamist', - 'title' => 'Kuva asukohad', - ], - 'title' => 'Asukoha seaded', - ], - 'node_management' => [ - 'allocations' => [ - 'description' => 'Lubab kõikide asukohtade kõvamist kõikide nodede puhul', - 'title' => 'Kuva allokeerimised', - ], - 'create' => [ - 'description' => 'Lubab uue node loomist süsteemis', - 'title' => 'Loo node', - ], - 'delete' => [ - 'description' => 'Lubab node kustutamist', - 'title' => 'Kustuta node', - ], - 'list' => [ - 'description' => 'Lubab kõigi node näitamist ', - 'title' => 'Kuva noded', - ], - 'title' => 'Node kontrollimine', - 'view' => [ - 'description' => 'Lubab vaadata detaile spetsiifilise node kohta, sealhulgas ka aktiivseid teenuseid', - 'title' => 'Kuva üksik node', - ], - ], - 'server_management' => [ - 'build' => [ - 'description' => 'Lubab muuta serveri ehitamise parameetreid näiteks nagu: RAM, CPU, kõvaketta hulk ja tavaline IP', - 'title' => 'Uuenda ehitust', - ], - 'config' => [ - 'description' => 'Lubab muuta serveri seadeid (nimi, omanik ja ligipääsu token)', - ], - 'create' => [ - 'description' => 'Lubab uue serveri loomist süsteemis ', - ], - 'power' => [ - 'title' => 'Serveri seis', - ], - 'unsuspend' => [ - 'description' => 'Lubab serveri taastamist pausilt', - ], - 'view' => [ - 'description' => 'Lubab info kuvamist koos daemon_token, ning teiste protsesside kohta', - 'title' => 'Näita ühte serverit ', - ], - ], - 'service_management' => [ - 'list' => [ - 'description' => 'Lubab kõikide teenuste kuvamist süsteemis ', - 'title' => 'Kuva teenused', - ], - 'title' => 'Teenuste haldamine', - 'view' => [ - 'description' => 'Lubab iga teenuse kohta detailse info kuvamist. Sealhulgas erinevad valikud ja seadistused ', - 'title' => 'Kuva üksik teenus', - ], - ], - 'user_management' => [ - 'create' => [ - 'description' => 'Lubab uue kasutaja loomist süsteemis', - 'title' => 'Loo kasutaja', - ], - 'delete' => [ - 'description' => 'Lubab kasutaja kustutamist', - 'title' => 'Kustuta kasutaja', - ], - 'list' => [ - 'description' => 'Lubab kõikide kasutajate kuvamist süsteemis', - 'title' => 'Näita kasutajaid ', - ], - 'title' => 'Kasutaja haldamine', - 'update' => [ - 'description' => 'Lubab kasutaja info muutumist (email, salasõna, TOTP) ', - 'title' => 'Uuenda kasutajat', - ], - 'view' => [ - 'description' => 'Lubab kasutaja detailide kuvamist aktiivsete teenuste puhul', - 'title' => 'Kuva üksik kasutaja', - ], - ], - ], - ], - 'confirm' => 'Oled sa kindel?', - 'errors' => [ - '404' => [ - 'desc' => 'Me ei suutnud leida vajalikku faili serverist ', - ], - 'home' => 'Mine pealehele', - 'return' => 'Tagasi eelmisele lehele ', - ], - 'form_error' => 'Järgmised vead tekkisid eelmise ülesande täitmisel', - 'index' => [ - 'header' => 'Serveri konsool', - 'header_sub' => 'Kontrolli oma serverit reaalajas', - 'list' => 'Serverite nimekiri', - ], - 'no_servers' => 'Hetkel ei ole ühtegi serverit teie kasutaja all', - 'password_req' => 'Salasõna peab täitma järgmiseid nõudeid: Üks suur täht, üks väike täht, üks number ja miinimum pikkusega 8', - 'security' => [ - '2fa_checkpoint_help' => 'Kasuta 2-astmelise autentimise applikatsiooni, et oma moblaga teha pilti või sisesta kood käsitsi. Pärast seda genereeri token ja sisesta see', - '2fa_disabled' => '2-astmeline autentimine on sellel kasutajal maas. Ekstra turvalisuse tagamiseks peaksid sa lisama 2-astmelise autentimise', - '2fa_header' => '2-astmeline autentimine', - '2fa_qr' => 'Seadista 2-astmeline autentimine oma seadmes', - 'enable_2fa' => 'Luba 2-astmeline autentimine', - 'header' => 'Turvaseaded', - 'header_sub' => 'Kontrolli ühendusi ja 2-astmelist autentimist', - 'sessions' => 'Aktiivsed ühendused', - ], - 'server_name' => 'Serveri nimi', - 'view_as_admin' => 'Te vaatate seda serveri listi administraatorina. Selle tõttu kõik installeeritud serverid sellel süsteemil on nähtavad. Teie omatud serveritel on sinine täpp serveri nime kõrval. ', -]; diff --git a/resources/lang/et/pagination.php b/resources/lang/et/pagination.php deleted file mode 100644 index c10d9915..00000000 --- a/resources/lang/et/pagination.php +++ /dev/null @@ -1,17 +0,0 @@ - 'Järgmine »', - 'previous' => '« Eelmine', - 'sidebar' => [ - 'account_controls' => 'Kasutaja seaded', - 'account_security' => 'Konto turvalisus', - 'account_settings' => 'Konto seaded', - 'files' => 'Failihaldur', - 'manage' => 'Halda serverit', - 'overview' => 'Serveri ülevaade', - 'servers' => 'Teie serverid', - 'server_controls' => 'Serveri seaded', - 'subusers' => 'Halda alamkasutajaid', - ], -]; diff --git a/resources/lang/et/passwords.php b/resources/lang/et/passwords.php deleted file mode 100644 index 26014fa5..00000000 --- a/resources/lang/et/passwords.php +++ /dev/null @@ -1,7 +0,0 @@ - 'Salasõna', - 'token' => 'Teie salasõna taastamis token on vale', - 'user' => 'Me ei leia kasutajat selle e-maili aadressiga', -]; diff --git a/resources/lang/et/server.php b/resources/lang/et/server.php deleted file mode 100644 index 92aafd4a..00000000 --- a/resources/lang/et/server.php +++ /dev/null @@ -1,192 +0,0 @@ - [ - 'socket_error' => 'Me ei suutnud luua ühendust peamise Socket.IO serveriga, see võib tuleneda internetiprobleemidest või daemoni rikkest. Paneel ei pruugi töötada ootuspäraselt', - 'socket_status' => 'Selle serveri olek on muutunud', - 'socket_status_crashed' => 'See server on maas', - ], - 'config' => [ - 'allocation' => [ - 'header_sub' => 'Kontrolli serveri IP-si ja Porte', - 'help' => 'Eraldamise abi', - ], - 'database' => [ - 'header' => 'Andmebaasid', - 'header_sub' => 'Kõik serverile saadaolevad andmebaasid', - 'host' => 'MySQL host ', - 'no_dbs' => 'Sellel serveril puuduvad andmebaasid', - 'reset_password' => 'Taasta salasõna', - 'your_dbs' => 'Teie andmebaasid', - ], - 'sftp' => [ - 'change_pass' => 'Vaheta SFTP salasõna', - 'conn_addr' => 'Ühenduse aadress', - 'details' => 'SFTP Detailid', - 'header' => 'STFP seadistus', - 'header_sub' => 'Kasutaja seaded SFTP ühenduse jaoks', - 'warning' => 'Palun veenduge, et Teie klient kasutab SFTP mitte FTP ega FTPS ühendusi, nende protokollid on erinevad', - ], - 'startup' => [ - 'command' => 'Käivitamise käsk', - 'edit_params' => 'Muuda parameetreid', - 'header' => 'Alusta seadistamist', - 'header_sub' => 'Kontrolli serveri käivitamisel kasutatavaid argumente', - 'update' => 'Uuenda käivitamise argumente', - ], - ], - 'files' => [ - 'edit' => [ - 'header' => 'Muuda faili', - 'header_sub' => 'Muuda faili läbi paneeli', - 'return' => 'Tagasi failihaldurisse', - 'save' => 'Salvesta fail', - ], - 'file_name' => 'Faili nimi', - 'header' => 'Failihaldur', - 'header_sub' => 'Hallata kõiki oma faile otse veebis', - 'last_modified' => 'Viimati muudetud', - 'loading' => 'Laen algset faili struktuuri, see võib võtta mõne sekundi', - 'path' => 'Seadistades oma failiteed serveri pluginate või seadete jaoks peaksite kasutama :path oma baasiks. Faili maksimum suurus failihalduri kaudu lisades on :size', - 'saved' => 'Fail on edukalt salvestatud', - 'seconds_ago' => 'sekundit tagasi', - 'size' => 'Suurus', - 'yaml_notice' => 'Hetkel muudate YAML tüüpi faili. Sellised failid EI AKSEPTEERI tabi kasutust, selle asemel kasuta tühikut. Tabi kasutamisel sisestatakse :dropdown tühikut ', - ], - 'index' => [ - 'add_new' => 'Lisa uus server', - 'allocation' => 'Eraldamine', - 'command' => 'Sisesta konsooli kaudu käsklus', - 'connection' => 'Peamine ühendus', - 'control' => 'Serveri seaded', - 'cpu_use' => 'CPU kasutus', - 'header' => 'Serveri konsool', - 'header_sub' => 'Kontrolli oma serverit reaalajas', - 'memory_use' => 'Mälu kasutus ', - ], - 'tasks' => [ - 'actions' => [ - 'command' => 'Saada käsk', - 'power' => 'Saada toite lülitus', - ], - 'new' => [ - 'payload_help' => 'Näiteks: Valides Send Command sisesta käsk siit. Valides Send Power Option sisesta süsteemi toite valik siia', - ], - 'new_task' => 'Lisa uus käsk', - 'toggle' => 'Vaheta staatust', - ], - 'users' => [ - 'add' => 'Lisa uus alamkasutaja', - 'configure' => 'Seadista õiguseid', - 'edit' => [ - 'header' => 'Muuda alamkasutajat', - 'header_sub' => 'Muuda kasutaja õiguseid serveris', - ], - 'header' => 'Halda kasutajaid', - 'header_sub' => 'Kontrolli, kes pääseb teie serverisse', - 'list' => 'Õigustega kontod', - 'new' => [ - 'command' => [ - 'description' => 'Lubab käskluste saatmist läbi konsooli, kui kasutajal pole stop ja start õigusi, siis nad ei saa applikatsiooni peatada', - 'title' => 'Saada konsooli käsk', - ], - 'compress_files' => [ - 'description' => 'Lubab kasutajal arhiive failide ja kasutatdes süsteemis', - 'title' => 'Paki faile', - ], - 'create_task' => [ - 'description' => 'Lubab kasutajal luua uusi käske', - ], - 'decompress_files' => [ - 'description' => 'Lubab kasutajal lahtipakkuda .zip ja .tar(.gz) tüüpi faile', - ], - 'download_files' => [ - 'title' => 'Lae failid alla', - ], - 'edit_subuser' => [ - 'description' => 'Lubab kasutajal alamkasutaja õiguste muutmist', - 'title' => 'Muuda alamkasutajat', - ], - 'email' => 'e-maili aadress', - 'email_help' => 'Sisestage e-maili aadress kasutaja jaoks, kellele soovite anda serveri jaoks õigusi', - 'file_header' => 'Faili haldamine', - 'header' => 'Lisa uus kasutaja', - 'header_sub' => 'Lisa uus kasutaja õigustega siia serverisse', - 'kill' => [ - 'description' => 'Lubab kasutajal peatada serverit', - 'title' => 'Peata server', - ], - 'list_files' => [ - 'description' => 'Lubab kasutajal kuvada kõiki faile ja kaustu, kuid ei luba vaadata failide sisu', - 'title' => 'Kuva failid', - ], - 'list_subusers' => [ - 'description' => 'Lubab kasutajal vaadata kõiki alamkasutajaid serveris', - 'title' => 'Kuva alamkasutajad', - ], - 'list_tasks' => [ - 'description' => 'Luba kasutajal kuvada kõik ülesanded (sees ja väljas) serveris.', - 'title' => 'Kuva ülesanded', - ], - 'move_files' => [ - 'description' => 'Lubab kasutajal liigutada ja ümber nimetada faile ja kaustu', - 'title' => 'Nimeta ja liiguta faile', - ], - 'power_header' => 'Toite haldamine', - 'queue_task' => [ - 'description' => 'Lubab panna käsu järjekorda, mida jooksutada järgmine tsükkel', - 'title' => 'Pane ülesanne järjekorda', - ], - 'reset_db_password' => [ - 'description' => 'Lubab kasutajal taastada andmebaasi salasõnu', - 'title' => 'Taasta andmebaasi salasõna', - ], - 'reset_sftp' => [ - 'description' => 'Lubab kasutajal muuta serveri SFTP salasõna', - 'title' => 'Taasta SFTP salasõna', - ], - 'save_files' => [ - 'title' => 'Salvesta failid', - ], - 'server_header' => 'Serveri haldus', - 'set_connection' => [ - 'description' => 'Lubab kasutajal muuta peamist ühendustja porte, mida kasutatakse serveri jaoks', - 'title' => 'Vali vaikimisi ühendus', - ], - 'sftp_header' => 'SFTP haldamine', - 'start' => [ - 'description' => 'Lubab kasutajal alustada serverit', - 'title' => 'Käivita server', - ], - 'stop' => [ - 'description' => 'Lubab kasutajal peatada serveri', - 'title' => 'Peata server', - ], - 'subuser_header' => 'Alamkasutaja haldus', - 'task_header' => 'Ülesannete haldamine', - 'toggle_task' => [ - 'description' => 'Lubab kasutajal ülesande lülitada sisse või välja', - 'title' => 'Lülita task', - ], - 'upload_files' => [ - 'description' => 'Lubab kasutajal laadida faile ülesse läbi failihalduri', - 'title' => 'Lae faile', - ], - 'view_databases' => [ - 'description' => 'Lubab kasutajal vaadata kõiki andmebaase ja nendega seotuid kasutajanimesi ja paroole', - 'title' => 'Vaata andmebaasi detaile', - ], - 'view_sftp' => [ - 'description' => 'Lubab kasutajal vaadata serveri SFTP informatsiooni, kuid mitte parooli', - 'title' => 'Vaata SFTP detaile', - ], - 'view_sftp_password' => [ - 'description' => 'Lubab kasutajal vaadata SFTP parooli serveri jaoks', - 'title' => 'Vaata SFTP salasõna', - ], - 'view_startup' => [ - 'description' => 'Lubab kasutajal vaadata startup käske ja nendega seotud muutujaid', - ], - ], - ], -]; diff --git a/resources/lang/et/strings.php b/resources/lang/et/strings.php deleted file mode 100644 index 4be0faf5..00000000 --- a/resources/lang/et/strings.php +++ /dev/null @@ -1,72 +0,0 @@ - '2FA', - '2fa_token' => 'Autentimise token', - 'account' => 'Kasutaja', - 'action' => 'Tegevus', - 'admin_control' => 'Admin kontroll', - 'admin_cp' => 'Admin Juhtpaneel', - 'again' => 'Uuesti', - 'alias' => 'Teise nimega', - 'api_access' => 'API ligipääs', - 'cancel' => 'Katkesta', - 'close' => 'Sulge', - 'configuration' => 'Seadistused', - 'confirm_password' => 'Kinnita salasõna', - 'connection' => 'Ühendus', - 'cpu' => 'CPU', - 'create' => 'Loo', - 'created' => 'Loodud', - 'created_at' => 'Loodud: ', - 'current_password' => 'Praegune salasõna', - 'danger' => 'Ohtlik', - 'data' => 'Andmed', - 'database' => 'Andmebaas', - 'databases' => 'Andmebaasid', - 'expires' => 'Aegub', - 'home' => 'Koduleht', - 'id' => 'ID', - 'ip' => ':attribute peab olema IP aadress ', - 'language' => 'Keel', - 'last_activity' => 'Viimati aktiivne', - 'login' => 'Logi sisse', - 'logout' => 'Logi välja', - 'memo' => 'Märge', - 'memory' => 'Mälu', - 'name' => 'Nimi', - 'never' => 'mitte kunagi', - 'new' => 'Uus', - 'next_run' => 'Järgmine ring', - 'no' => 'Ei', - 'node' => 'Node', - 'none' => 'Puudub', - 'password' => 'Salasõna', - 'players' => 'Mängijad', - 'port' => 'Port', - 'primary' => 'Peamine', - 'public_key' => 'Avalik võti', - 'queued' => 'Järjekorras', - 'registered' => 'Registreeritud', - 'relation' => 'Positsioon', - 'required' => ':attribute täitmine on vajalik', - 'revoke' => 'Eemalda', - 'root_administrator' => 'Root administraator ', - 'save' => 'Salvesta ', - 'search' => 'Otsi', - 'security' => 'Turvalisus', - 'servers' => 'Serverid', - 'settings' => 'Seaded', - 'sftp' => 'SFTP', - 'sign_out' => 'Logi välja', - 'start' => 'Alusta', - 'status' => 'Staatus ', - 'stop' => 'Peata', - 'submit' => 'Sisesta', - 'success' => 'Edukas', - 'suspended' => 'Peatatud', - 'username' => 'Kasutajanimi', - 'user_identifier' => 'Kasutajanimi', - 'whoops' => 'Oih!', - 'yes' => 'Jah', -]; diff --git a/resources/lang/et/validation.php b/resources/lang/et/validation.php deleted file mode 100644 index 6bdef9dd..00000000 --- a/resources/lang/et/validation.php +++ /dev/null @@ -1,66 +0,0 @@ - ':attribute peab olema aktsepteeritud ', - 'active_url' => ':attribute on väär URL', - 'after' => ':attribute peab olema kuupäev pärast :date', - 'after_or_equal' => ':attribute peab olema pärast või võrdne :date kuupäevaga', - 'alpha' => ':attribute võib ainult sisaldada tähti', - 'alpha_dash' => ':attribute võib ainult sisalda tähti, numbreid ja mõttekriipse', - 'alpha_num' => ':attribute võib ainult sisaldada tähti ja numbreid', - 'array' => ':attribute peab olema massiiv', - 'before' => ':attribute peab olema aktsepteeritud enne järgmist kuupäeva :date', - 'before_or_equal' => 'Sisestatud :attribute peab olema suurem/võrdne :date kuupäevaga', - 'between' => [ - 'array' => ':attribute peab olema :min ja :max vahemikus', - 'file' => ':attribute peab olema :min ja :max kb vahemikus', - 'numeric' => ':attribute peab olema vahemikus :min ja :max', - 'string' => ':attribute peab olema vahemikus :min ja :max tähte', - ], - 'boolean' => ':attribute peab olema tõene või väär ', - 'confirmed' => ':attribute kinnitus ei klapi', - 'custom' => [ - 'attribute-name' => [ - 'rule-name' => 'muudetud-sõnum', - ], - ], - 'date' => ':attribute ei ole õige kuupäev', - 'date_format' => ':attribute ei klapi järgmise formaadiga :format', - 'distinct' => ':attribute väljal on duplikaat sisestus', - 'exists' => 'Valitud :attribute on vigane ', - 'filled' => ':attribute väli tuleb täita', - 'image' => ':attribute peab olema pilt', - 'in' => 'Valitud :attribute on väär', - 'in_array' => ':attribute väli ei eksisteeri :other', - 'ip' => ':attribute peab olema IP aadress ', - 'max' => [ - 'numeric' => ':attribute ei tohi ületada :max', - ], - 'mimes' => ':attribute peab olema järgmine faili tüüp :values', - 'mimetypes' => ':attribute peab olema järgmine faili tüüp :values', - 'min' => [ - 'array' => ':attribute peab sisaldama vähemalt :min objekti', - 'file' => ':attribute peab olema vähemalt :min kilobaiti', - 'numeric' => ':attribute peab olema üle :min', - 'string' => ':attribute peab olema vähemalt :min märki', - ], - 'not_in' => ':attribute on väär', - 'numeric' => ':attribute peab olema number', - 'present' => ':attribute väli peab eksisteerima', - 'regex' => ':attribute väli on väär', - 'required' => ':attribute täitmine on vajalik', - 'required_unless' => ':attribute väli peab olema täidetud v.a. kui :other on:values-s', - 'required_without_all' => ':attribute tuleb täita, kui :values on olemas ', - 'size' => [ - 'array' => ':attribute peab sisalda :size objekti', - 'file' => ':attribute peab olema :size kilobaiti', - 'numeric' => ':attribute peab olema selles suuruses :size', - 'string' => ':attribute peab olema :size tähemärki', - ], - 'string' => ':attribute peab olema string', - 'timezone' => ':attribute peab olema kehtiv tsoon', - 'totp' => 'TOTP token on vale. Kas see on aegunud? ', - 'unique' => ':attribute on juba võetud', - 'uploaded' => ':attribute üleslaadimine ebaõnnestus', - 'url' => ':attribute formaat on vale', -]; diff --git a/resources/lang/fr/auth.php b/resources/lang/fr/auth.php deleted file mode 100644 index 76de5e98..00000000 --- a/resources/lang/fr/auth.php +++ /dev/null @@ -1,24 +0,0 @@ - 'La clé 2FA est invalide.', - '2fa_must_be_enabled' => "L'administrateur a demandé que l'authentification à 2 facteurs (2FA) soit activée pour votre compte afin d'utiliser le panel.", - '2fa_required' => 'Authentification à 2 Facteurs (2FA)', - 'authentication_required' => "L'authentification est nécessaire pour continuer.", - 'auth_error' => "Une erreur s'est produite lors de la tentative de connexion.", - 'confirmpassword' => 'Confirmez le Mot de Passe', - 'emailsent' => "L'email de réinitialisation de votre mot de passe vous a été envoyé.", - 'email_sent' => 'Un e-mail contenant les instructions pour réinitialiser votre mot de passe vous a été envoyé.', - 'failed' => 'Ce combo e-mail / mot de passe ne correspond à aucun compte enregistré.', - 'forgot_password' => "J'ai oublié mon mot de passe !", - 'not_authorized' => "Vous n'êtes pas autorisé à effectuer cette action.", - 'password_requirements' => 'Un mot de passe doit contenir au moins une majuscule, une minuscule, un chiffre et doit faire au minimum 8 caractères de long.', - 'remember_me' => 'Se souvenir de moi', - 'request_reset' => 'Récupérer le compte', - 'request_reset_text' => 'Vous avez oublié votre mot de passe ? Nous vous inquiétez pas, il suffit de fournir votre e-mail dans le champ ci-dessous.', - 'resetpassword' => 'Réinitialiser mot de passe', - 'reset_password' => 'Réinitialiser le mot de passe du compte.', - 'reset_password_text' => 'Réinitialiser le mot de passer de votre compte.', - 'sendlink' => 'Envoyer le lien de réinitialisation du mot de passe.', - 'sign_in' => 'Connexion', -]; diff --git a/resources/lang/fr/base.php b/resources/lang/fr/base.php deleted file mode 100644 index b6b57946..00000000 --- a/resources/lang/fr/base.php +++ /dev/null @@ -1,348 +0,0 @@ - [ - 'current_password' => 'Mot de passe actuel', - 'delete_user' => 'Supprimer Utilisateur ', - 'details_updated' => 'Les détails de votre comptes ont été modifié avec succès.', - 'email_password' => 'Email Mot de passe ', - 'exception' => 'Une erreur est survenue durant la tentative de mise à jour de votre compte.', - 'first_name' => 'Prénom', - 'header' => 'Gestion du compte', - 'header_sub' => 'Gérer les détails de votre compte.', - 'invalid_pass' => "Le mot de passe fourni n'est pas valide pour ce compte. ", - 'invalid_password' => "Le mot de passe fourni n'est pas valide pour ce compte. ", - 'last_name' => 'Nom', - 'new_email' => 'Nouvel adresse e-mail', - 'new_password' => 'Nouveau mot de passe', - 'new_password_again' => 'Répéter le nouveau mot de passe', - 'totp_apps' => 'Vous devez posséder une application fournissant une authentification à deux facteurs (2FA) pour utiliser cette option. (ex : Authy, Enpass, DUO Mobile, Google Authenticator ...)', - 'totp_checkpoint_help' => "Veuillez vérifier vos paramètres 2FA en scannant le QR code sur votre droite avec votre application d'authentification, puis entrez le code à 6 chiffre qui s'affiche dans le champ ci-dessous. Appuyez sur entrée lorsque vous avez finit.", - 'totp_disable' => 'Désactiver l’authentification à deux facteurs ', - 'totp_disable_help' => "Pour désactiver l'authentification 2FA sur ce compte, vous devez fournir un token 2FA valide. Une fois ce token validé, la protection sera désactivée sur ce compte.", - 'totp_enable' => 'Activer l’authentification à deux facteurs', - 'totp_enabled' => 'Votre compte a été activé avec la vérification 2FA. Cliquez sur le bouton de fermeture pour terminer.', - 'totp_enabled_error' => "Le jeton 2FA fourni n'a pas pu être vérifié. Veuillez réessayer.", - 'totp_enable_help' => "Il semble que l'authentification à deux facteurs ne soit pas activée. Cette méthode d'authentification ajoute une barrière supplémentaire empêchant toute connexion non autorisée sur votre compte. Si vous l'activez, vous devrez entrer un code généré sur votre téléphone ou sur un autre périphérique compatible 2FA avant de terminer votre connexion.", - 'totp_header' => 'Authentification à deux facteurs', - 'totp_qr' => 'QR Code 2FA', - 'totp_token' => 'Jeton 2FA', - 'update_email' => "Modifier l'adresse e-mail", - 'update_identitity' => 'Modifier les informations', - 'update_pass' => 'Modifier le mot de passe', - 'update_user' => "Modifier l'utilisateur", - 'username_help' => "Votre nom d'utilisateur doit être unique à votre compte et ne doit être composé que des caractères suivants : :requirements.", - ], - 'api' => [ - 'index' => [ - 'create_new' => 'Créer une nouvelle clé API', - 'header' => "Accès à l'API", - 'header_sub' => "Gérer vos clés d'accès à l'API.", - 'keypair_created' => "Une paire de clés API vient d'être générée. Votre jeton secret API est :jeton . Veuillez prendre note de cette clé car elle ne sera plus affichée.", - 'list' => 'Clés API.', - ], - 'new' => [ - 'allowed_ips' => [ - 'description' => "Entrez une liste d'IP ayant accès à l'API utilisant cette clé, chacune séparées par un saut de ligne. La notation CIDR est autorisée. Laissez vide pour autoriser n'importe quelle IP.", - 'title' => 'IPs autorisées', - ], - 'base' => [ - 'information' => [ - 'description' => 'Retourne une liste de tout les serveur auquel ce compte peux accéder.', - 'title' => 'Information de base', - ], - 'title' => 'Information de base ', - ], - 'descriptive_memo' => [ - 'description' => "Entrez une courte description de ce pourquoi cette clef d'API sera utilisée.", - 'title' => 'Résumé', - ], - 'form_title' => 'Détails ', - 'header' => "Nouvelle clef d'API ", - 'header_sub' => 'Crée une nouvelle clef d’accès API ', - 'location_management' => [ - 'list' => [ - 'description' => 'Autoriser à lister touts les emplacements et leurs noeuds associés.', - 'title' => 'Liste des emplacements ', - ], - 'title' => "Gestionnaire d'emplacements ", - ], - 'node_management' => [ - 'allocations' => [ - 'description' => 'Permet de voir toutes les allocations sur le panel pour toutes les nodes.', - 'title' => 'Liste des allocations ', - ], - 'create' => [ - 'description' => 'Autoriser à crée un nouveau noeud sur le système.', - 'title' => 'Crée noeud', - ], - 'delete' => [ - 'description' => 'Autoriser à supprimer un noeud.', - 'title' => 'Supprimer Nœud ', - ], - 'list' => [ - 'description' => 'Autoriser à lister touts les noeuds actuellement sur le système. ', - 'title' => 'Liste des noeuds ', - ], - 'title' => 'Gestion du noeud ', - 'view' => [ - 'description' => "Autoriser à voir les détails à propos d'un noeud spécifique incluant des services actifs. ", - 'title' => 'Liste de noeuds simple', - ], - ], - 'server_management' => [ - 'build' => [ - 'title' => 'Mettre à jour la construction', - ], - 'command' => [ - 'description' => 'Autoriser un utilisateur à envoyer une commande spécifique au serveur.', - 'title' => 'Envoyer une commande ', - ], - 'config' => [ - 'description' => 'Autoriser à modifier la configuration serveur (nom, propriétaire et jeton d’accès).', - 'title' => 'Mettre à jour la configuration', - ], - 'create' => [ - 'description' => 'Autoriser à crée un nouveau serveur sur le système. ', - 'title' => 'Crée un serveur', - ], - 'delete' => [ - 'description' => 'Autoriser à supprimer un serveur.', - 'title' => 'Supprimer serveur', - ], - 'list' => [ - 'description' => 'Autoriser à lister tout les serveur actuellement sur le système.', - 'title' => 'Liste des serveurs ', - ], - 'power' => [ - 'description' => "Permet de contrôler d'allumer et d’éteindre le serveur.", - 'title' => 'Alimentation Serveur ', - ], - 'server' => [ - 'description' => 'Permet de voir toutes les informations sur un seul serveur, incluant ses dernières statistiques et ce qui lui est alloué.', - 'title' => 'Info Serveur ', - ], - 'suspend' => [ - 'description' => 'Autoriser à suspendre une instance serveur ', - 'title' => 'Serveur suspendu ', - ], - 'title' => 'Gestionnaire serveur ', - 'unsuspend' => [ - 'description' => 'Autoriser à dé-suspendre une instance serveur ', - 'title' => 'Serveur non suspendu ', - ], - 'view' => [ - 'description' => "Autoriser à voir les détails à propose d'un serveur spécifique incluant le daemon_token comme information de processus actuelle -", - 'title' => 'Montrer serveur simple ', - ], - ], - 'service_management' => [ - 'list' => [ - 'description' => 'Autoriser à lister tout les services configuré sur le système. ', - 'title' => 'Liste des Services ', - ], - 'title' => 'Gestionnaire de service', - 'view' => [ - 'description' => 'Autoriser à lister les détails à propos de chaque service sur le système incluant un service avec des options et des variables.', - 'title' => 'Liste des services uniques', - ], - ], - 'user_management' => [ - 'create' => [ - 'description' => 'Allouer à crée une nouvel utilisateur sur le système.', - 'title' => 'Créer utilisateur', - ], - 'delete' => [ - 'description' => 'Autoriser à supprimer un utilisateur.', - 'title' => 'Supprimer Utilisateur ', - ], - 'list' => [ - 'description' => 'Autoriser à lister tout les utilisateur actuellement sur le serveur.', - 'title' => 'Liste des utilisateurs', - ], - 'title' => "Gestion d'Utilisateur", - 'update' => [ - 'description' => "Autoriser à modifier les détails d'un utilisateur (email, mot de passe, informations TOTP).", - 'title' => "Mettre à jour l'utilisateur", - ], - 'view' => [ - 'description' => "Autoriser à voir les détails à propos d'une utilisateur spécifique incluant un service actif. ", - 'title' => 'Lister les utilisateur uniques', - ], - ], - ], - 'permissions' => [ - 'admin' => [ - 'location' => [ - 'list' => [ - 'desc' => 'Autoriser à lister touts les emplacements et ses noeuds associes.', - 'title' => 'Liste des emplacements', - ], - ], - 'location_header' => "Contrôles de l'emplacement", - 'node' => [ - 'create' => [ - 'desc' => 'Autoriser à crée un nouveau noeud sur le système.', - 'title' => 'Crée un noeud ', - ], - 'delete' => [ - 'desc' => 'Autoriser à supprimer un noeud sur le système.', - 'title' => 'Supprimer Nœud ', - ], - 'list' => [ - 'desc' => 'Autoriser à lister tout les noeuds actuellement sur le système.', - 'title' => 'Liste des noeuds ', - ], - 'view-config' => [ - 'desc' => 'Attention. Cette autorisation permet de voir le fichier de configuration du noeud utilisé par le daemon, et expose le jeton secret de ce dernier. ', - 'title' => 'Voir la configuration du noeud ', - ], - 'view' => [ - 'desc' => "Autoriser à voir les détails à propos d'un noeuds spécifique incluant un service actif.", - 'title' => 'Voir Nœud ', - ], - ], - 'node_header' => 'Contrôle du Nœud ', - 'option' => [ - 'list' => [ - 'title' => 'Liste des options', - ], - 'view' => [ - 'title' => 'Voir les options ', - ], - ], - 'option_header' => 'Options de contrôle', - 'pack' => [ - 'list' => [ - 'desc' => '', - 'title' => 'Liste des packs ', - ], - 'view' => [ - 'title' => 'Voir Pack', - ], - ], - 'pack_header' => 'Gestion du Pack', - 'server' => [ - 'create' => [ - 'desc' => 'Autoriser à crée un nouveau serveur sur le système.', - 'title' => 'Crée un Serveur ', - ], - 'delete' => [ - 'desc' => 'Autoriser à supprimer un serveur du système.', - 'title' => 'Supprimer Serveur', - ], - 'edit-build' => [ - 'desc' => 'Autoriser à éditer les paramètres de construction du serveur tel que le processeur et la mémoire. ', - 'title' => 'Editer la construction du serveur', - ], - 'edit-container' => [ - 'desc' => 'Autoriser pour les modifications du container Docker dans lequel le serveur fonctionne.', - 'title' => 'Editer le container du serveur', - ], - 'edit-details' => [ - 'desc' => 'Autoriser à éditer les détails du serveur tels que le nom, le propriétaire, et la clef secrète. ', - 'title' => 'Editer les détails du serveur ', - ], - 'edit-startup' => [ - 'desc' => 'Autorise à modifier la commande de lancement et les paramètres.', - 'title' => 'Editer les paramètres de démarrage du serveur', - ], - 'install' => [ - 'title' => "Activer le statut de l'installation", - ], - 'list' => [ - 'desc' => 'Autorise à lister tout les serveur actuellement sur le système.', - 'title' => 'Liste des serveurs ', - ], - 'rebuild' => [ - 'title' => 'Recréer un serveur', - ], - 'suspend' => [ - 'desc' => 'Autorise à suspendre ou dé-suspendre un serveur donné. ', - 'title' => 'Suspendre le serveur ', - ], - 'view' => [ - 'desc' => 'Autoriser à voir un simple serveur incluant des services et des détails.', - 'title' => 'Voir serveur ', - ], - ], - 'server_header' => 'Contrôle du serveur', - 'service' => [ - 'list' => [ - 'desc' => 'Autorise à lister tout les services configurés sur le système', - 'title' => 'Liste des services ', - ], - 'view' => [ - 'desc' => 'Autoriser à lister les détails à propos de chaque service sur le système incluant des options de service et des variables', - 'title' => 'Voir le service ', - ], - ], - 'service_header' => 'Contrôle du service', - 'user' => [ - 'create' => [ - 'desc' => 'Autorise à crée un nouvel utilisateur sur le système. ', - 'title' => 'Crée un utilisateur', - ], - 'delete' => [ - 'desc' => 'Autoriser à supprimer un utilisateur ', - 'title' => 'Supprimer utilisateur ', - ], - 'edit' => [ - 'desc' => "Autoriser les modifications des détails d'un utilisateur.", - 'title' => "Mettre à jour l'utilisateur", - ], - 'list' => [ - 'desc' => 'Autoriser à lister tout les utilisateurs actuellement sur le serveur ', - 'title' => 'Liste des utilisateur ', - ], - 'view' => [ - 'desc' => "Autoriser à voir les détails à propos d'une configuration spécifique incluant des services actifs.", - 'title' => "Voir l'utilisateurs", - ], - ], - 'user_header' => "Contrôles de l'utilisateurs ", - ], - 'user' => [ - 'server' => [ - 'command' => [ - 'desc' => "Autoriser l'envoi d'une commande à un serveur en cours d'exécution.", - 'title' => "Envoi d'une commande", - ], - 'list' => [ - 'desc' => 'Autoriser à lister tous les serveurs dont un utilisateur est propriétaire ou auquel il a accès en tant que sous-utilisateur.', - 'title' => 'Liste des serveurs ', - ], - 'power' => [ - 'desc' => "Autoriser à inverser le status d'alimentation pour le serveur.", - 'title' => 'Inverser Alimentation ', - ], - 'view' => [ - 'desc' => "Autorise l'affichage d'un utilisateur spécifique du serveur.", - 'title' => 'Voir serveur', - ], - ], - 'server_header' => "Permission serveur de l'utilisateur ", - ], - ], - ], - 'confirm' => 'Êtes vous sûr ? ', - 'errors' => [ - '403' => [ - 'desc' => "Vous n'avez pas la permission d’accéder à cette ressource sur ce serveur.", - ], - '404' => [ - 'header' => 'Fichier introuvable.', - ], - ], - 'security' => [ - '2fa_header' => 'Authentification à deux facteurs', - 'enable_2fa' => "Activer l'authentification à deux facteurs.", - 'header' => 'Sécurité du compte', - 'header_sub' => "Contrôler les sessions actives et l'authentification à 2 facteurs.", - 'sessions' => 'Sessions actives', - ], - 'server_name' => 'Nom du Serveur', - 'validation_error' => 'Il y à une erreur avec un ou plusieurs champs dans la requête. ', - 'view_as_admin' => "Vous regardez cette liste de serveurs en tant qu'admin. En tant que tel, tous les serveurs présents sur le système seront affichés. Tous les serveurs où vous êtes indiqué comme étant le propriétaire sont marqués d'un point bleu à gauche de leur nom.", -]; diff --git a/resources/lang/fr/pagination.php b/resources/lang/fr/pagination.php deleted file mode 100644 index 3a6a041b..00000000 --- a/resources/lang/fr/pagination.php +++ /dev/null @@ -1,12 +0,0 @@ - [ - 'account_controls' => 'Contrôles de compte', - 'account_security' => 'Sécurité du compte ', - 'account_settings' => 'Paramètres de Compte', - 'files' => 'Gestionnaire de Fichiers', - 'manage' => 'Gérer le Serveur', - 'overview' => "Vue d'ensemble du serveur", - ], -]; diff --git a/resources/lang/fr/passwords.php b/resources/lang/fr/passwords.php deleted file mode 100644 index a54aefea..00000000 --- a/resources/lang/fr/passwords.php +++ /dev/null @@ -1,7 +0,0 @@ - 'Mot de passe', - 'reset' => 'Votre mot de passe à été réinitialisé! ', - 'sent' => 'Nous vous avons envoyé par e-mail votre lien de réinitialisation de mot de passe!', -]; diff --git a/resources/lang/fr/server.php b/resources/lang/fr/server.php deleted file mode 100644 index d5b2b1f0..00000000 --- a/resources/lang/fr/server.php +++ /dev/null @@ -1,159 +0,0 @@ - [ - 'socket_error' => 'Nous sommes incapable de nous connecter au serveur Socket.IO principal, il y a peut-être actuellement des problèmes de réseau. Le panel risque de ne pas fonctionner comme prévu.', - 'socket_status' => 'Le status du serveur a été changé par', - 'socket_status_crashed' => 'Ce serveur a été détecté comme planté.', - ], - 'config' => [ - 'allocation' => [ - 'available' => 'Allocations Disponibles', - 'header' => 'Allocations du Serveur', - 'header_sub' => 'Control des IP et des ports disponible sur ce serveur.', - 'help' => "Aide d'allocation", - ], - 'database' => [ - 'add_db' => 'Ajouter une nouvelle base de donnée.', - 'header' => 'Base de données ', - 'header_sub' => 'Toutes les bases de données disponible pour ce serveur.', - 'host' => 'Hote MySQL', - 'no_dbs' => "Il n'y a pas de bases de données listées pour ce serveur.", - 'reset_password' => 'Réinitialiser le mot de passe ', - 'your_dbs' => 'Vos Bases de Données', - ], - 'sftp' => [ - 'change_pass' => 'Changer le mot de passe SFTP', - 'conn_addr' => 'Adresse de connexion ', - 'details' => 'Détails du SFTP', - 'header' => 'Configurations SFTP', - 'header_sub' => 'Informations de compte pour la connexion SFTP.', - ], - 'startup' => [ - 'command' => 'Commande de lancement ', - 'edited' => 'les variables de lancement ont bien étés édites. Elles vont prendre effet la prochaine fois que le serveur sera lancé. ', - 'edit_params' => 'Editer les paramètres ', - 'header' => 'Lancer la configuration', - 'header_sub' => 'Contrôle des arguments du lancement du serveur.', - 'startup_regex' => 'Vérification Regex', - 'startup_var' => 'Variable de commande de lancement', - 'update' => 'Mettre à jour les paramètres de lancement', - ], - ], - 'files' => [ - 'add' => [ - 'create' => 'Crée fichier ', - 'header' => 'Nouveau fichier', - 'header_sub' => 'Crée un nouveau fichier sur votre serveur.', - 'name' => 'Nom de fichier', - ], - 'add_folder' => 'Ajouter nouveau dossier', - 'add_new' => 'Ajouter un nouveau fichier', - 'back' => 'Retourner au gestionnaire de fichiers', - 'delete' => 'Supprimer ', - 'edit' => [ - 'header' => 'Editer le fichier ', - 'header_sub' => 'Faire des modification sur le fichier depuis le web.', - 'return' => 'Retourner au gestionnaire de fichiers ', - 'save' => 'Sauvegarder le fichier', - ], - 'exceptions' => [ - 'invalid_mime' => "Ce type de fichier ne peux pas être éditer via le panneau d'edition built-in.", - 'list_directory' => "Une erreur s'est produite lors de la tentative d'obtention du contenu de ce répertoire. Veuillez réessayer.", - 'max_size' => "Ce fichier est trop grand pour être édité via l'éditeur du panneau built-in. ", - ], - 'file_name' => 'Nom de fichier', - 'header' => 'Gestionnaire de fichier', - 'header_sub' => 'Gérez tous vos fichiers directement depuis le web.', - 'last_modified' => 'Dernière modifications ', - 'loading' => 'Chargement de la structure initiale du fichier, cela peut prendre quelques secondes.', - 'mass_actions' => 'Action de masse', - 'saved' => 'Le fichier à été sauvegardé avec succès. ', - 'yaml_notice' => "Vous éditez un fichier YAML. Ces fichiers n'acceptent pas les tabulations, il faut impérativement que cela soit des espaces. Nous avons prévu le coup et avons fait en sorte qu'appuyer sur tab insérera :dropdown espaces. ", - ], - 'index' => [ - 'allocation' => 'Allocation ', - 'command' => 'Entrer une commande console', - 'cpu_use' => 'Utilisation CPU', - 'server_info' => 'Informations Serveur', - 'usage' => 'Utilisation', - ], - 'tasks' => [ - 'current' => 'Taches actuellement prévues ', - 'header_sub' => 'Automatisez votre serveur.', - 'new' => [ - 'custom' => 'Valeurs custom', - 'day_of_month' => 'Jour du Mois', - 'day_of_week' => 'Jour de la Semaine', - 'tues' => 'Mardi', - ], - ], - 'users' => [ - 'new' => [ - 'create_subuser' => [ - 'title' => 'Créer Sous-utilisateur', - ], - 'edit_files' => [ - 'title' => 'Editer Fichiers -', - ], - 'email' => 'Adresse Mail', - 'list_tasks' => [ - 'description' => 'Autoriser un utilisateur à lister toutes les taches (activées et désactivées) sur le serveur.', - 'title' => 'Liste des tâches', - ], - 'move_files' => [ - 'title' => 'Renommer et Déplacer Fichiers ', - ], - 'power_kill' => [ - 'description' => "Autoriser l'utilisateur à fermer le processus du serveur.", - ], - 'power_start' => [ - 'description' => "Autoriser l'utilisateur à lancer le serveur.", - ], - 'start' => [ - 'title' => 'Lancer Serveur', - ], - 'stop' => [ - 'description' => "Autoriser l'utilisateur à arrêter le serveur.", - 'title' => 'Arrêter Serveur', - ], - 'subuser_header' => 'Gestionnaire de sous-utilisateur ', - 'upload_files' => [ - 'title' => 'Envoyer Fichiers', - ], - 'view_allocations' => [ - 'title' => 'Voir les emplacements ', - ], - 'view_databases' => [ - 'title' => 'Voir le détail de la bases de données ', - ], - 'view_schedule' => [ - 'title' => 'Voir les planning', - ], - 'view_sftp' => [ - 'description' => "Autoriser l'utilisateur à voir les informations du SFTP mais pas le mot de passe.", - 'title' => 'Voir les détails du SFTP', - ], - 'view_sftp_password' => [ - 'description' => "Autoriser l'utilisateur à voir le mot de passe SFTP pour le serveur. ", - 'title' => 'Voir le mot de passe SFTP ', - ], - 'view_startup' => [ - 'description' => "Autoriser l'utilisateur à voir la commande de lancement du serveur et les variables associé. ", - 'title' => 'Voir la commande de lancement', - ], - 'view_subuser' => [ - 'description' => 'Autoriser l’utilisateur à voir les permissions assignées aux sous-utilisateurs.', - 'title' => 'Voir sous-utilisateur ', - ], - 'view_task' => [ - 'description' => "Autoriser l’utilisateur à voir les détails d'une tache spécifique.", - 'title' => 'Voir tâche', - ], - ], - 'update' => 'Sous-utilisateur mis à jour', - 'user_assigned' => 'Un sous-utilisateur à été assigné avec succès à ce serveur. ', - 'user_updated' => 'Permission mises à jour avec success. ', - ], -]; diff --git a/resources/lang/fr/strings.php b/resources/lang/fr/strings.php deleted file mode 100644 index c1cc7017..00000000 --- a/resources/lang/fr/strings.php +++ /dev/null @@ -1,76 +0,0 @@ - '2FA', - '2fa_token' => 'Jeton d’authentification ', - 'account' => 'Compte', - 'action' => 'Action', - 'admin' => 'Administrateur ', - 'admin_control' => 'Administration', - 'admin_cp' => "Panneau d'administration", - 'again' => 'Encore', - 'alias' => 'Alias', - 'api_access' => 'Accès API', - 'cancel' => 'Annuler', - 'captcha_invalid' => 'Le captcha fourni est invalide.', - 'child_tasks' => 'Taches enfant', - 'close' => 'Fermer', - 'configuration' => 'Configuration', - 'confirm_password' => 'Confirmer le mot de passe', - 'connection' => 'Connexion', - 'cpu' => 'Processeur', - 'create' => 'Créer', - 'created' => 'Crée ', - 'created_at' => 'Crée le ', - 'current_password' => 'Mot de passe actuel ', - 'danger' => 'Attention ', - 'data' => 'Donnée', - 'database' => 'Base de données ', - 'databases' => 'Base de données ', - 'days' => [ - 'fri' => 'Vendredi', - 'mon' => 'Lundi', - ], - 'disabled' => 'Désactivé', - 'email' => 'E-mail', - 'enabled' => 'Activé', - 'expires' => 'Expire ', - 'home' => 'Accueil', - 'id' => 'ID', - 'ip' => ':attribute doit être une IP valide.', - 'language' => 'Langage', - 'last_activity' => 'Dernière activité', - 'last_run' => 'Dernier lancement', - 'location' => 'Emplacement ', - 'login' => 'Se connecter', - 'logout' => 'Déconnexion', - 'memo' => 'Mémo', - 'memory' => 'Mémoire', - 'minutes' => 'Minutes ', - 'name' => 'Nom', - 'never' => 'jamais', - 'new' => 'Nouveau', - 'no' => 'Non', - 'node' => 'Node', - 'not_run_yet' => 'Pas encore lancé', - 'password' => 'Mot de passe', - 'players' => 'Joueurs', - 'port' => 'Port', - 'primary' => 'Primaire', - 'public_key' => 'Clef publique ', - 'required' => 'Le champ :attribute est requis.', - 'revoke' => 'Révoquer', - 'root_administrator' => 'Administrateur root', - 'save' => 'Sauvegarder', - 'security' => 'Sécurité', - 'servers' => 'Serveurs', - 'sign_out' => "S'enregistrer ", - 'status' => 'Status ', - 'submit' => 'Soumettre', - 'suspended' => 'Suspendu', - 'under_maintenance' => 'Sous maintenantce', - 'username' => "Nom d'utilisateur", - 'user_identifier' => "Nom d'utilisateur ou email", - 'whoops' => 'Oups ', - 'yes' => 'Oui', -]; diff --git a/resources/lang/fr/validation.php b/resources/lang/fr/validation.php deleted file mode 100644 index 5c7a0c92..00000000 --- a/resources/lang/fr/validation.php +++ /dev/null @@ -1,66 +0,0 @@ - ':attribute doit-être accepté.', - 'active_url' => ":attribute n'est pas une URL valide.", - 'after' => 'Le :attribute dois être une date après le :date ', - 'after_or_equal' => 'Le :attribut dois être une date supérieure ou égale au :date. ', - 'alpha' => ':attribute doit uniquement contenir des lettres.', - 'alpha_dash' => ':attribute doit uniquement contenir des lettres, chiffres et tirets.', - 'alpha_num' => ':attribute doit uniquement contenir des lettres et chiffres.', - 'array' => 'Le :attribute dois être une tableau.', - 'before' => ':attribute doit être une date avant :date.', - 'before_or_equal' => 'Le :attribute dois être une date avant ou égale à :date.', - 'between' => [ - 'array' => 'Le :attribute dois avoir entre :min et :max éléments. ', - 'file' => 'Le :attribute dois compris entre :min et :max kilobytes.', - 'numeric' => ':attribute doit être entre :min et :max.', - 'string' => 'Le :attribute dois être compris entre :min et :max caractères.', - ], - 'boolean' => ':attribute doit être "true" (vrais) ou "false" (faux).', - 'confirmed' => ':attribute confirmation ne correspond pas.', - 'custom' => [ - 'attribute-name' => [ - 'rule-name' => 'message personnalisé ', - ], - ], - 'date' => "Le :attribute n'est pas une date valide.", - 'date_format' => ':attribute ne correspond pas au format :format.', - 'different' => 'Le :attribute et :other doivent êtres différents. ', - 'digits' => 'Le :attribute dois comprendre :digits chiffres.', - 'digits_between' => 'Le :attribute dois être compris entre :min et :max chiffres.', - 'dimensions' => "Le :attribute as une taille d'image invalide.", - 'email' => 'E-mail', - 'file' => 'Le :attribute dois être un fichier.', - 'filled' => 'Le champ :attribute est requis', - 'image' => 'Le :attribute dois être une image.', - 'in_array' => 'Le champ :attribute ne dois pas exister dans :other.', - 'ip' => ':attribute doit être une IP valide.', - 'max' => [ - 'array' => 'Le :attribute ne peuvent avoir plus de :max éléments ', - 'file' => ':attribute ne doit pas dépasser :max kilooctets.', - 'numeric' => ':attribute ne doit pas être supérieur à :max.', - 'string' => 'Le :attribute ne peut pas être supérieur à :max caractères.', - ], - 'mimes' => 'Le :attribute dois être un fichier de type :values.', - 'mimetypes' => 'Le :attribute dois être un fichier de type :values.', - 'min' => [ - 'array' => 'Le :attribute dois avoir au minimum :min éléments ', - 'file' => ":attribute doit être d'au moins :min kilooctets.", - 'numeric' => ":attribute doit être d'au moins :min.", - 'string' => 'Le :attribute dois avoir moins de :min caractères.', - ], - 'numeric' => 'Le :attribute dois être un nombre.', - 'present' => 'Le :attribute champ dois être remplis.', - 'regex' => 'Le format de ce :attribute est invalide.', - 'required' => 'Le champ :attribute est requis.', - 'required_if' => 'Le champ :attribute est requis quand :other est à :value.', - 'required_without_all' => "Lorsque :values n'est/ne sont pas présent(s), le champ :attribute est requis.", - 'same' => ':attribute et :other doivent correspondres.', - 'size' => [ - 'array' => 'Le :attribute dois contenir :size éléments.', - 'file' => ':attribute doit faire :size kilooctets.', - 'numeric' => ':attribute doit être de :size.', - ], - 'timezone' => ':attribute doit être une zone valide.', -]; diff --git a/resources/lang/it/auth.php b/resources/lang/it/auth.php deleted file mode 100644 index c0a72f5b..00000000 --- a/resources/lang/it/auth.php +++ /dev/null @@ -1,22 +0,0 @@ - 'Il Token 2FA è invalido', - '2fa_must_be_enabled' => "L'amministratore richiede che la Autenticazione 2-Fattori sia attiva sul tuo account per usare il Panello.", - '2fa_required' => 'Autenzicazione 2 Fattori', - 'authentication_required' => 'La autenticazione è richiesta per continuare.', - 'auth_error' => "C'è stato un errore facendo il login.", - 'confirmpassword' => 'Conferma Password', - 'emailsent' => 'La email con le istruzioni su come recuperare la password è in arrivo.', - 'email_sent' => 'Una email con le istruzioni per recuperare la password ti è stata mandata.', - 'errorencountered' => 'Un errore è accaduto durante la elaborazione di questo processo.', - 'failed' => 'Le credenziali non combaciano con i nostri dati.', - 'forgot_password' => 'Ho dimenticato la password!', - 'remeberme' => 'Ricordami', - 'remember_me' => 'Ricordamelo', - 'request_reset' => 'Trova Account', - 'request_reset_text' => 'Hai perso la password? Non è la fine del mondo, metti la tua mail qua sotto !', - 'resetpassword' => 'Ripristina Password', - 'reset_password' => "Resetta password dell'account", - 'sendlink' => 'Manda link reset Password', -]; diff --git a/resources/lang/it/base.php b/resources/lang/it/base.php deleted file mode 100644 index f2fa9152..00000000 --- a/resources/lang/it/base.php +++ /dev/null @@ -1,365 +0,0 @@ - [ - 'current_password' => 'Password Corrente', - 'delete_user' => 'Elimina Utente', - 'details_updated' => 'I tuoi dettagli del account sono stati aggiornati con successo.', - 'email_password' => 'Email Password', - 'exception' => "E' stato trovato un'errore procedendo la richiesta d'aggiornamento account.", - 'first_name' => 'Primo Nome', - 'header' => 'CONTROLLO ACCOUNT', - 'header_sub' => 'Controlla i dettagli del tuo account.', - 'invalid_pass' => 'Password invalida per questo account', - 'invalid_password' => 'La password inserita per il tuo account non è valida.', - 'last_name' => 'Cognome', - 'new_email' => 'Nuovo Indirizzo Email', - 'new_password' => 'Nuova password', - 'new_password_again' => 'Ripeti la Nuova Password', - 'totp_apps' => "Devi avere un'applicazione che supporti TOTP (Google Authenticator, DUO Mobile, Authy, Enpass) per usare questa opzione.", - 'totp_checkpoint_help' => 'Per favore verifica che le tue impostazioni TOTP facendo la scansione del Codice QR a destra usando un autenticatore sul tuo cellulare, e digita i 6 numeri generati dalla applicazione nella seguente casella. Premi invio quando hai finito.', - 'totp_disable' => 'Disabilita la Autenticazione a 2-Fattori', - 'totp_disable_help' => 'Per disabilitare la funzione TOTP in questo account dovrai dare un token TOTP valido. Quando verrà validato, la protezione TOTP verrà disabilitata in questo account', - 'totp_enable' => "Abilità l'autenticazione a due fattori", - 'totp_enabled' => 'Il tuo account è stato abilitato con la verificazione TOTP. Perfavore clicca il bottone chiudi per finire.', - 'totp_enabled_error' => 'Non è stato possibile verificare Il token TOTP inserito. Riprova.', - 'totp_enable_help' => "Sembra che tu non abbia l'autenticazione a due fattori abilitatà. Questo metodo di autenticazione aggiunge un'addizionale barriera di sicurezza prevenendo gli inautorizzati ad entrare nel tuo account. Se abiliti questa funzione ti sarà richiesto un codice generate dal tuo telefono o un'altra applicazione con supporto TOTP prima di finire il login", - 'totp_header' => 'Autenticazione a Due Fattori', - 'totp_qr' => 'Codice TOTP QR', - 'totp_token' => 'Token TOTP', - 'update_email' => 'Aggiorna Email', - 'update_identitity' => 'Aggiorna Identità', - 'update_pass' => 'Aggiorna Password', - 'update_user' => 'Aggiorna utente', - 'username_help' => 'II tuo username deve essere unico nel tuo account, e può contenere solo i seguenti caratteri: :requisements.', - ], - 'api' => [ - 'index' => [ - 'create_new' => 'Crea una nuova chiave API', - 'header' => 'Accesso API', - 'header_sub' => "Gestisci le tue chiavi per l'accesso all'API", - 'keypair_created' => 'Una Chiave API è stata generata. Il tuo codice segreto API è :token. Prendi nota di questo codice dato che non sarà più mostrato di nuovo.', - 'list' => 'Chiavi API', - ], - 'new' => [ - 'allowed_ips' => [ - 'description' => "Immetti qualunque IP che ha il permesso di accedere all'API usando questa key. Le notazioni CIDR sono permesse. Lascia vuoto per permettere qualunque IP.", - 'title' => 'IP permessi', - ], - 'base' => [ - 'information' => [ - 'description' => 'Il suo output è una lista di server che questo account possiede.', - 'title' => 'Informazioni Base', - ], - 'title' => 'Informazioni Base', - ], - 'descriptive_memo' => [ - 'description' => 'Immetti una breve descrizione su cosa questa chiave API verrà usata.', - 'title' => 'Descrizione Memo', - ], - 'form_title' => 'Dettagli', - 'header' => 'Nuova Chiave API', - 'header_sub' => 'Crea una nuova chiave di accesso API', - 'location_management' => [ - 'list' => [ - 'description' => 'Permette di mostrare le locazioni e i loro nodi associati.', - 'title' => 'Mostra Locazioni', - ], - 'title' => 'Gestione Locazioni', - ], - 'node_management' => [ - 'allocations' => [ - 'description' => 'Permette di vedere tutte le sedi del panello per tutti i nodi.', - 'title' => 'Mostra Sedi', - ], - 'create' => [ - 'description' => 'Permette di creare un nuovo nodo nel sistema.', - 'title' => 'Crea Nodo', - ], - 'delete' => [ - 'description' => 'Permette di eliminare un nodo.', - 'title' => 'Elimina nodo', - ], - 'list' => [ - 'description' => 'Permette di mostrare tutti i nodi nel sistema.', - 'title' => 'Elenco Nodi', - ], - 'title' => 'Amministrazione nodi', - 'view' => [ - 'description' => 'Permette di vedere i dettagli di un nodo specifico inclusi anche i servizi attivi.', - 'title' => 'Elenca un Singolo Nodo', - ], - ], - 'server_management' => [ - 'build' => [ - 'description' => "Permette l'utente di modificare i parametri del server come la memoria, CPU, e lo spazio del disco assegnati a esso e gli IP predefiniti.", - 'title' => 'Aggiorna versione', - ], - 'command' => [ - 'description' => 'Permette di inviare un commando in un server specifico', - 'title' => 'Invia Commando', - ], - 'config' => [ - 'description' => 'Permette di modificare la configurazione del server (nome, proprietario, e token di accesso).', - 'title' => 'Aggiorna Configurazione', - ], - 'create' => [ - 'description' => 'Permette di creare un nuovo server nel sistema.', - 'title' => 'Crea Server', - ], - 'delete' => [ - 'description' => 'Permette di eliminare un server.', - 'title' => 'Elimina Server', - ], - 'list' => [ - 'description' => 'Il suo output è una lista di tutti i server nel sistema.', - 'title' => 'Elenco Server', - ], - 'power' => [ - 'description' => 'Permette il controllo alle impostazioni di energia del server.', - 'title' => 'Server Power', - ], - 'server' => [ - 'description' => 'Permette di vedere le informazioni di un singolo server incluso lo stato e le allocazioni.', - 'title' => 'Informazioni Server', - ], - 'suspend' => [ - 'description' => "Permetti di sospendere un'instanza di un server.", - 'title' => 'Sospendi Server', - ], - 'title' => 'Gestione Server', - 'unsuspend' => [ - 'description' => "Permetti di sospendere un'instanza di un server.", - 'title' => 'Riattiva Server', - ], - 'view' => [ - 'description' => 'Permette di vedere i dettagli di un server specifico incluso il token e informazioni sui processi.', - 'title' => 'Mostra server singolo', - ], - ], - 'service_management' => [ - 'list' => [ - 'description' => 'Permetti di mostrare tutti i servizi configurati nel sistema.', - 'title' => 'Elenco Servizi', - ], - 'title' => 'Amministrazione servizi', - 'view' => [ - 'description' => 'Permette di elencare ogni servizio del sistema incluse le opzioni di servizio e le variabili.', - 'title' => 'Elenco Servizio Singolo', - ], - ], - 'user_management' => [ - 'create' => [ - 'description' => 'Permette di creare un nuovo utente in questo sistema.', - 'title' => 'Crea Utente', - ], - 'delete' => [ - 'description' => 'Permette di eliminare un utente.', - 'title' => 'Elimina Utente', - ], - 'list' => [ - 'description' => 'Permette di elencare tutti gli utenti presenti nel sistema.', - 'title' => 'Mostra utenti', - ], - 'title' => 'Gestione Utente', - 'update' => [ - 'description' => 'Permette di modificare delle credenziali utente (email, password, informazioni TOTP).', - 'title' => 'Aggiorna Utente', - ], - 'view' => [ - 'description' => "Permette di vedere dettagli riguardanti un'utente specifico includendo i servizi attivi.", - 'title' => 'Elenco Utente Singolo', - ], - ], - ], - 'permissions' => [ - 'admin' => [ - 'location' => [ - 'list' => [ - 'desc' => 'Permette di elencare tutte le locazioni e i loro nodi associati.', - 'title' => 'Elenco Locazioni', - ], - ], - 'location_header' => 'Controllo Locazione', - 'node' => [ - 'create' => [ - 'desc' => 'Permette di creare un nuovo nodo nel sistema.', - 'title' => 'Crea Nodo', - ], - 'delete' => [ - 'desc' => 'Permette di eliminare un nodo dal sistema.', - 'title' => 'Elimina Nodo', - ], - 'list' => [ - 'desc' => 'Permette di elencare tutti i nodi del sistema', - 'title' => 'Elenco Nodi', - ], - 'view-config' => [ - 'desc' => 'Attenzione. Questa opzione permette di vedere la configurazione del nodo usato dal demone, e mostra i codici segreti. ', - 'title' => 'Mostra Configurazione Nodo', - ], - 'view' => [ - 'desc' => 'Permette di mostrare i dettagli di un nodo specifico inclusi i servizi attivi.', - 'title' => 'Mostra Nodo', - ], - ], - 'node_header' => 'Controllo Nodo', - 'option' => [ - 'list' => [ - 'title' => 'Elenco Opzioni', - ], - 'view' => [ - 'title' => 'Mostra Opzione', - ], - ], - 'option_header' => 'Controllo Opzioni', - 'pack' => [ - 'list' => [ - 'title' => 'Elenco Pacchetti', - ], - 'view' => [ - 'title' => 'Mostra Pacchetto', - ], - ], - 'pack_header' => 'Gestione Pacchetti', - 'server' => [ - 'create' => [ - 'desc' => 'Permette la creazione di un nuovo server nel sistema.', - 'title' => 'Crea Server', - ], - 'delete' => [ - 'desc' => 'Permette di eliminare un server dal sistema.', - 'title' => 'Elimina Server', - ], - 'edit-build' => [ - 'desc' => 'Permette la modifica delle impostazioni del server come la CPU e memoria allocata. ', - 'title' => 'Modifica Installazione Server', - ], - 'edit-container' => [ - 'desc' => 'Permette di modificare il contenitore docker dove il server è in esecuzione.', - 'title' => 'Modifica Container Server', - ], - 'edit-details' => [ - 'desc' => 'Permette di modificare i dettagli server come nome, proprietario, descrizione, e chiave segreta.', - 'title' => 'Modifica Dettagli Server', - ], - 'edit-startup' => [ - 'desc' => 'Permette di modificare i comandi di avvio e i parametri del server.', - 'title' => 'Modifica Avvio Server', - ], - 'install' => [ - 'title' => 'Cambia Stato Installazione', - ], - 'list' => [ - 'desc' => 'Permette di elencare lo stato di tutti i server presenti nel sistema.', - 'title' => 'Elenco Server', - ], - 'rebuild' => [ - 'title' => 'Ricostruisci Server', - ], - 'suspend' => [ - 'desc' => 'Permette di sospendere e di riprendere un server dato.', - 'title' => 'Sospendi Server', - ], - 'view' => [ - 'desc' => 'Permette di vedere un singolo server incluso il servizio e dettagli.', - 'title' => 'Mostra Server', - ], - ], - 'server_header' => 'Controllo Server', - 'service' => [ - 'list' => [ - 'desc' => 'Permette di elencare tutti i servizi configurati nel sistema.', - 'title' => 'Elenco Servizi', - ], - 'view' => [ - 'desc' => 'Permette di elencare i dettagli di tutti i servizi del sistema incluse le opzioni e variabili.', - 'title' => 'Mostra Servizio', - ], - ], - 'service_header' => 'Controllo Servizio', - 'user' => [ - 'create' => [ - 'desc' => 'Permette la creazione di nuovi utenti nel sistema.', - 'title' => 'Crea Utente', - ], - 'delete' => [ - 'desc' => 'Permette di eliminare un utente.', - 'title' => 'Elimina Utente', - ], - 'edit' => [ - 'desc' => 'Permette di modificare i dettagli utente.', - 'title' => 'Aggiorna Utente.', - ], - 'list' => [ - 'desc' => 'Permette di vedere tutti gli utenti presenti nel sistema.', - 'title' => 'Elenco Utenti', - ], - 'view' => [ - 'desc' => 'Permette di vedere i dettagli di un specifico utente inclusi i servizi attivi.', - 'title' => 'Vedi Utente', - ], - ], - 'user_header' => 'Controllo Utente', - ], - 'user' => [ - 'server' => [ - 'command' => [ - 'desc' => 'Permette di eseguire i comandi di un server.', - 'title' => 'Manda Commando', - ], - 'list' => [ - 'desc' => 'Permette di elencare tutti i servizi che un utente usa o che ha accesso come sottoutente.', - 'title' => 'Elenco Server', - ], - 'power' => [ - 'desc' => 'Permette di cambiare lo stato di accensione di un server.', - 'title' => 'Attiva/Disattiva', - ], - 'view' => [ - 'desc' => "Permette di vedere i server specifici in cui l'utente può entrare.", - 'title' => 'Vedi Server', - ], - ], - 'server_header' => 'Permessi Server del utente', - ], - ], - ], - 'confirm' => 'Sei sicuro?', - 'errors' => [ - '403' => [ - 'desc' => 'Non hai il permesso di accedere a questa risorsa in questo server.', - 'header' => 'Proibito', - ], - '404' => [ - 'desc' => 'La risorsa non è stata trovata nel nostro server.', - 'header' => 'File Non Trovato', - ], - 'home' => 'Torna alla Home', - 'installing' => [ - 'desc' => 'Il server richiesto sta ancora completando la fase di installazione. Riprova tra qualche minuto, dovresti ricvere una email quando sarà completato.', - 'header' => 'Installazione Server', - ], - 'return' => 'Ritorna alla pagina precedente', - 'suspended' => [ - 'desc' => 'Il server è stato sospeso e non è accessibile.', - 'header' => 'Server Sospeso', - ], - ], - 'index' => [ - 'header' => 'Console Server', - 'header_sub' => 'Controlla il tuo server in tempo reale.', - 'list' => 'Lista server', - ], - 'no_servers' => 'Non hai dei server nel tuo account', - 'password_req' => 'La password deve avere almeno un carattere in maiuscolo e uno in minuscolo, un numero/carattere speciale e deve essere lunga almeno 8 caratteri', - 'security' => [ - '2fa_disabled' => "L'autenticazione a due fattori è disabilitata nel tuo account! Dovresti abilitare l'autenticazione a due passi per aggiungere un'ulteriore strato di protezione al tuo account.", - '2fa_qr' => "Configura l'autenticazione a 2 passi sul tuo dispositivo.", - '2fa_token_help' => 'Entra il token 2FA generato dalla tua applicazione(Google Authenticator, Authy, etc.).', - 'header' => 'Sicurezza Account', - 'sessions' => 'Sessioni Attive', - ], - 'server_name' => 'Nome Server', - 'view_as_admin' => 'Stai vedendo questo server come admin. Tutti i tipi di server nel sistema sono visibili. I server che possiedi sono marcati con un puntino blu vicino al loro nome.', -]; diff --git a/resources/lang/it/pagination.php b/resources/lang/it/pagination.php deleted file mode 100644 index 150f1325..00000000 --- a/resources/lang/it/pagination.php +++ /dev/null @@ -1,13 +0,0 @@ - 'Prossima »', - 'previous' => '« Indietro', - 'sidebar' => [ - 'account_controls' => 'Controlli Account', - 'account_security' => 'Sicurezza Account', - 'overview' => 'Panoramica Server', - 'servers' => 'Servers', - 'subusers' => 'Controlla Sub-Utenti', - ], -]; diff --git a/resources/lang/it/passwords.php b/resources/lang/it/passwords.php deleted file mode 100644 index 03cba7da..00000000 --- a/resources/lang/it/passwords.php +++ /dev/null @@ -1,7 +0,0 @@ - 'Password', - 'reset' => 'La tua password è stata reimpostata!', - 'token' => 'Il token del reset di questa password è invalido.', -]; diff --git a/resources/lang/it/server.php b/resources/lang/it/server.php deleted file mode 100644 index 6ad45033..00000000 --- a/resources/lang/it/server.php +++ /dev/null @@ -1,196 +0,0 @@ - [ - 'socket_error' => 'Impossibile collegarsi al server Socket.IO, ci potrebbe essere qualche problema di connessione. Il panello non funzionerà correttamente.', - 'socket_status' => 'Lo stato del server è cambiato a', - 'socket_status_crashed' => 'Questo server è stato identificato come CRASHATO.', - ], - 'config' => [ - 'allocation' => [ - 'available' => 'Allocazioni Disponibili', - 'header' => 'Allocazioni Server', - 'header_sub' => "Controlla l'IP e le porte disponibili di questo server.", - 'help' => 'Aiuto Allocazione', - 'help_text' => "L'elenco a sinistra include tutti gli IP disponibili e le porte che sono aperte per il tuo server da usare per le connessioni in arrivo.", - ], - 'database' => [ - 'add_db' => 'Aggiungi un nuovo database.', - 'header' => 'Database', - 'header_sub' => 'Tutti i database disponibili per questo server.', - 'host' => 'Host MySQL', - 'no_dbs' => 'Non ci sono database disponibili per questo server.', - 'reset_password' => 'Ripristina Password', - 'your_dbs' => 'I tuoi Database', - ], - 'sftp' => [ - 'change_pass' => 'Cambia la Password SFTP', - 'conn_addr' => 'Indirizzo Connessione', - 'details' => 'Dettagli SFTP', - 'header' => 'Configurazione SFTP', - 'header_sub' => 'Dettagli account per le connessioni SFTP.', - 'warning' => "Assicurati che il client è settato per collegarti via SFTP e non il FTP o FTPS, c'è una differenza tra i protocolli.", - ], - 'startup' => [ - 'command' => 'Commando di Avvio', - 'edited' => 'Le variabili di avvio sono state modificate con successo. Saranno applicate al prossimo avvio del server.', - 'edit_params' => 'Modifica Parametri', - 'header' => 'Avvia Configurazione', - 'header_sub' => 'Controllo gli argomenti del avvio server.', - 'startup_regex' => 'Verifica Regex', - 'startup_var' => 'Commando di avvio variabile', - 'update' => 'Aggiorna Parametri di Avvio', - ], - ], - 'files' => [ - 'add' => [ - 'create' => 'Crea File', - 'header' => 'Nuovo File', - 'header_sub' => 'Crea un nuovo file nel tuo server.', - 'name' => 'Nome File', - ], - 'add_folder' => 'Crea Nuova Cartella', - 'add_new' => 'Aggiungi un nuovo File', - 'back' => 'Torna nella Gestione File', - 'delete' => 'Elimina', - 'edit' => [ - 'header' => 'Modifica File', - 'header_sub' => 'Modifica il file dal browser.', - 'return' => 'Ritorna alla Gestione File', - 'save' => 'Salva File', - ], - 'exceptions' => [ - 'invalid_mime' => "Questo tipo di file non può essere editato con l'editore del Panello.", - 'list_directory' => "Un errore è accaduto durante l'analisi del contenuto di questa cartella. Riprova.", - 'max_size' => "Questo file è troppo grande per essere modificato con l'editore del Panello.", - ], - 'file_name' => 'Nome File', - 'header' => 'Gestione File', - 'header_sub' => 'Controlla tutti i tuoi file direttamente dal browser.', - 'last_modified' => 'Ultima Modifica', - 'loading' => 'Carico la struttura iniziale dei file, può richiedere qualche secondo.', - 'mass_actions' => 'Azioni di massa', - 'seconds_ago' => 'secondi fa', - 'size' => 'Dimensione', - 'yaml_notice' => 'Stai modificando un file YAML. Questi files non accettano i tabs, devono usare i spazi. Abbiamo fatto in modo che premendo tab inserirà :dropdown spazi.', - ], - 'index' => [ - 'add_new' => 'Aggiungi Server', - 'allocation' => 'Allocazione', - 'command' => 'Inserisci un Commando Console', - 'connection' => 'Connessione default', - 'control' => 'Controlla server', - 'cpu_use' => 'Uso CPU', - 'disk_space' => 'Spazio Disco', - 'header' => 'Console Server', - 'header_sub' => 'Controlla il tuo server in tempo reale.', - 'memory_use' => 'Uso della Memoria', - 'mem_limit' => 'Limite Memoria', - 'server_info' => 'Informazioni Server', - 'title' => 'Server :name', - 'usage' => 'Uso', - 'xaxis' => 'Tempo (2 secondi di incremento)', - ], - 'schedule' => [ - 'actions' => [ - 'command' => 'Invia Commando', - 'power' => 'Cambia Accensione', - ], - 'current' => 'Programmi Correnti', - 'day_of_month' => 'Giorno del Mese', - 'day_of_week' => 'Giorno della Settimana', - 'header' => 'Programmazioni', - 'hour' => 'Ore del Giorno', - 'manage' => [ - 'delete' => 'Elimina Programmazione', - 'header' => 'Gestione Programmazioni', - 'submit' => 'Aggiorna Programmazioni -', - ], - 'minute' => 'Minuti del Ora', - 'new' => [ - 'header' => 'Nuovo Programma', - ], - 'task' => [ - 'action' => 'Esegui Azione', - 'add_more' => 'Esegui un altro Programma', - 'payload' => 'Con Carico', - 'time' => 'Dopo', - ], - 'toggle' => 'Cambia Stato', - ], - 'tasks' => [ - 'actions' => [ - 'command' => 'Invia Comando', - ], - 'header' => 'Operazioni Programmate', - 'new' => [ - 'chain_do' => 'Fai', - 'custom' => 'Valore unico', - 'fri' => 'Venerdì', - 'header_sub' => 'Crea una nuova operazione pianificata in questo server.', - 'hour' => 'Ora', - 'type' => 'Tipo di pianificazione', - 'wed' => "Mercoledi'", - ], - 'new_task' => 'Aggiungi nuova operazione', - ], - 'users' => [ - 'add' => 'Aggiungi nuovo sub-utente', - 'configure' => 'Configura permessi', - 'edit' => [ - 'header' => 'Modifica sub-utente', - ], - 'new' => [ - 'copy_files' => [ - 'title' => 'Copia Files', - ], - 'db_header' => 'Controlla Database', - 'decompress_files' => [ - 'description' => "Permetti all'utente di decomprimere archivi .zip e .tar(.gz).", - ], - 'delete_files' => [ - 'title' => 'Elimina File', - ], - 'delete_schedule' => [ - 'title' => 'Elimina Programmazione', - ], - 'delete_subuser' => [ - 'title' => 'Elimina Sottoutente', - ], - 'delete_task' => [ - 'description' => 'Permette un utente di eliminare un programmazione.', - 'title' => 'Elimina Programmazione', - ], - 'download_files' => [ - 'title' => 'Scarica Files', - ], - 'edit_subuser' => [ - 'description' => "Permetti ad un utente di modifcare i permessi assegnati ad un'altro sub-utente.", - ], - 'email_help' => "Immetti l'email dell'utente che desideri di invitare a controllare questo server.", - 'header_sub' => 'Aggiungi un nuovo utente con dei permessi su questo server.', - 'kill' => [ - 'title' => 'Termina Server', - ], - 'list_files' => [ - 'description' => "Permetti all'utente di vedere tutti i files e le cartelle in questo server ma non poterne vedere il contenuto.", - ], - 'restart' => [ - 'title' => 'Restarta Server', - ], - 'stop' => [ - 'title' => 'Termina server', - ], - 'subuser_header' => 'Controlla i sub-utenti', - 'task_header' => 'Controlla le operazioni pianificate', - 'view_allocations' => [ - 'description' => "Permette l'utente di vedere tutti gli IP e le porte assegnate a quel server.", - 'title' => 'Mostra Sedi', - ], - 'view_subuser' => [ - 'description' => 'Permetti agli utenti di vedere i permessi assegnati ai sub-utenti.', - ], - ], - ], -]; diff --git a/resources/lang/it/strings.php b/resources/lang/it/strings.php deleted file mode 100644 index 33a6af8e..00000000 --- a/resources/lang/it/strings.php +++ /dev/null @@ -1,97 +0,0 @@ - '2FA', - '2fa_token' => "Token d'Autenticazione", - 'account' => 'Account', - 'action' => 'Azione', - 'admin' => 'Amministratore', - 'admin_control' => 'Controllo Amministratore', - 'admin_cp' => 'Panello Controllo Amministratore', - 'again' => 'Di nuovo', - 'alias' => 'Alias', - 'api_access' => 'Accesso API', - 'cancel' => 'Annulla', - 'captcha_invalid' => 'Il captcha inserito è invalido.', - 'child_tasks' => 'Programmazioni Figli', - 'close' => 'Chiudi', - 'configuration' => 'Configurazione', - 'confirm_password' => 'Conferma Password', - 'connection' => 'Connessione', - 'cpu' => 'CPU', - 'create' => 'Crea', - 'created' => 'Creato', - 'created_at' => 'Creato il', - 'current_password' => 'Password corrente', - 'danger' => 'Pericolo', - 'data' => 'Dati', - 'database' => 'Database', - 'databases' => 'Database', - 'days' => [ - 'fri' => 'Venerdì', - 'mon' => 'Lunedì', - 'sat' => 'Sabato', - 'sun' => 'Domenica', - 'thurs' => 'Giovedì', - 'tues' => 'Martedì', - 'wed' => 'Mercoledì', - ], - 'delete' => 'Elimina', - 'disabled' => 'Disabilitare', - 'email' => 'La :attribute deve essere un indirizzo email valido.', - 'enabled' => 'Abilitato', - 'expires' => 'Scadenza', - 'home' => 'Home', - 'id' => 'ID', - 'ip' => ":attribute deve essere un'IP valido.", - 'language' => 'Lingua', - 'last_activity' => 'Ultima Attività', - 'last_run' => 'Ultima Esecuzione', - 'location' => 'Locazione', - 'login' => 'Login', - 'logout' => 'Esci', - 'make_primary' => 'Fallo Primario', - 'memo' => 'Nota', - 'memory' => 'Memoria', - 'minutes' => 'Minuti', - 'name' => 'Nomi', - 'never' => 'mai', - 'new' => 'Nuovo', - 'next_run' => 'Prossimo Avvio', - 'no' => 'No', - 'node' => 'Nodo', - 'none' => 'Nessuno', - 'not_run_yet' => 'Non ancora avviato', - 'optional' => 'Opzionale', - 'owner' => 'Proprietario', - 'password' => 'Password', - 'players' => 'Giocatori', - 'port' => 'Porta', - 'primary' => 'Primario', - 'public_key' => 'Chiave pubblica', - 'queued' => 'In coda', - 'read_only' => 'Solo Lettura', - 'registered' => 'Registrato', - 'relation' => 'Relazione', - 'required' => ':attribute è richiesto.', - 'restart' => 'Restart', - 'revoke' => 'Revoca', - 'root_administrator' => 'Amministratore Root', - 'save' => 'Salva', - 'search' => 'Cerca', - 'seconds' => 'Secondi', - 'security' => 'Sicurezza', - 'select_all' => 'Seleziona Tutto', - 'select_none' => 'Deseleziona Tutto', - 'servers' => 'Server', - 'sftp' => 'SFTP', - 'start' => 'Accendere', - 'status' => 'Stato', - 'stop' => 'Ferma', - 'submit' => 'Inserisci', - 'subuser' => 'Sottoutente', - 'success' => 'Successo', - 'suspended' => 'Sospeso', - 'tasks' => 'Programmazioni', - 'whoops' => 'Whoops', -]; diff --git a/resources/lang/it/validation.php b/resources/lang/it/validation.php deleted file mode 100644 index 63875c44..00000000 --- a/resources/lang/it/validation.php +++ /dev/null @@ -1,70 +0,0 @@ - ':attribute deve essere accettato.', - 'active_url' => 'Questo attributo non è un URL valido.', - 'after' => ':attrubute deve essere una data dopo :date.', - 'after_or_equal' => 'Il :attribute deve essere una data successiva o uguale a :date.', - 'alpha' => 'Il :attribute può contenere solo lettere.', - 'alpha_dash' => 'Il :attribute può contenere solo lettere, numeri, e trattini.', - 'alpha_num' => 'Il :attribute può contenere solo lettere e numeri.', - 'array' => 'Il :attribute deve essere un array.', - 'before' => ':attribute deve essere una data prima di :date.', - 'before_or_equal' => ':attribute deve essere una data prima o equivalente a :date.', - 'between' => [ - 'array' => 'Il :attribute deve essere tra :min e :max oggetti.', - 'file' => ':attribute deve essere dai :min al :max kilobytes.', - 'numeric' => 'Il :attribute deve essere tra :min e :max.', - 'string' => 'Il :attribute deve essere tra :min e :max caratteri.', - ], - 'boolean' => ':attribute deve essere true(vero) o false(falso).', - 'confirmed' => 'La :attribute di conferma non corrisponde.', - 'custom' => [ - 'attribute-name' => [ - 'rule-name' => 'messaggio personalizzato', - ], - ], - 'date' => ':attribute non è una data valida.', - 'date_format' => 'Il :attribute non corrisponde al formato :format.', - 'different' => ':attribute deve essere differente da :other .', - 'digits' => 'Il :attribute deve essere lungo :digits.', - 'digits_between' => 'Il :attribute deve essere lungo tra :min e :max.', - 'dimensions' => 'Il :attribute ha dimensioni immagini invalide.', - 'distinct' => ':attribute ha un valore duplicato.', - 'email' => 'La :attribute deve essere un indirizzo email valido.', - 'exists' => ':attribute è invalido.', - 'file' => 'Il :attribute deve essere un file.', - 'image' => ":attribute deve essere un'immagine.", - 'in' => 'Fil :attribute selezionato è invalido.', - 'integer' => 'Il :attribute deve essere un numero.', - 'internal' => [ - 'variable_value' => 'variabile :env ', - ], - 'in_array' => 'Il campo :attribute non esiste in :other.', - 'ip' => ":attribute deve essere un'IP valido.", - 'json' => 'Il :attribute deve essere una stringa JSON valida.', - 'max' => [ - 'array' => 'Il :attribute non può avere più di :max oggetti.', - 'file' => 'Il :attribute non può essere più grande di :max kilobytes.', - 'numeric' => ':attribute non deve essere maggiore di :max.', - 'string' => ":attribute non dovrebbe essere piu' lungo di :max caratteri", - ], - 'mimes' => 'Il :attribute deve essere un file del tipo: :values.', - 'mimetypes' => 'Il :attribute deve essere un file del tipo: :values.', - 'min' => [ - 'array' => 'Il :attribute deve avere almeno :min oggetti.', - 'numeric' => ':attribute non deve essere minore di :min.', - 'string' => ':attribute deve essere almeno lungo :min caratteri', - ], - 'not_in' => ':attribute è invalido', - 'numeric' => 'Il :attribute deve essere un numero.', - 'required' => ':attribute è richiesto.', - 'required_with' => ':attribute è richiesto quando :values è presente.', - 'required_without_all' => ':attribute è richieste quando nessuno di :values è presente.', - 'size' => [ - 'numeric' => ':attribute deve essere :size.', - 'string' => ':attribute deve essere almeno lungo :size caratteri.', - ], - 'totp' => 'Il token TOTP è invalido. Sarà scaduto?', - 'unique' => ':attribute è già stato preso.', -]; diff --git a/resources/lang/nl/auth.php b/resources/lang/nl/auth.php deleted file mode 100644 index f15ea9e8..00000000 --- a/resources/lang/nl/auth.php +++ /dev/null @@ -1,18 +0,0 @@ - 'De gegeven 2FA token is niet geldig.', - '2fa_must_be_enabled' => 'De administrator heeft ingesteld dat je 2-factor authenticatie moet gebruiken voor je acount voordat je het paneel kan gebruiken', - '2fa_required' => '2-factor authenticatie', - 'confirmpassword' => 'Bevestig wachtwoord', - 'emailsent' => 'Je wachtwoord herstel e-mail is onderweg.', - 'forgot_password' => 'Ik ben mijn wachtwoord vergeten!', - 'not_authorized' => 'U heeft geen toestemming om deze actie uit te voeren.', - 'password_requirements' => 'Wachtwoorden moeten minstens één kleine letter, één hoofdletter en één numeriek karakter bevatten, het wachtwoord moet ook minstens 8 karakters lang zijn.', - 'remember_me' => 'Onthoud mij', - 'resetpassword' => 'Herstel wachtwoord', - 'reset_password' => 'Herstel account wachtwoord', - 'reset_password_text' => 'Herstel uw account wachtwoord.', - 'sendlink' => 'Stuur wachtwoord herstel link', - 'throttle' => 'Teveel inlog pogingen. Probeer het opnieuw in :seconds seconden.', -]; diff --git a/resources/lang/nl/base.php b/resources/lang/nl/base.php deleted file mode 100644 index e600525b..00000000 --- a/resources/lang/nl/base.php +++ /dev/null @@ -1,129 +0,0 @@ - [ - 'current_password' => 'Huidig Wachtwoord', - 'delete_user' => 'Gebruiker Verwijderen', - 'details_updated' => 'Je account details zijn succesvol veranderd', - 'email_password' => 'E-mail wachtwoord', - 'exception' => 'Er is een fout opgetreden tijdens het veranderen van je account.', - 'first_name' => 'Voornaam', - 'header' => 'Account Beheer', - 'header_sub' => 'Beheer uw account details', - 'invalid_pass' => 'Het opgegeven wachtwoord is ongeldig voor dit account.', - 'invalid_password' => 'Het opgegeven wachtwoord voor je account is ongeldig.', - 'last_name' => 'Naam', - 'new_email' => 'Nieuw e-mail adres', - 'new_password' => 'Nieuw Wachtwoord', - 'new_password_again' => 'Herhaal nieuw wachtwoord', - 'totp_apps' => 'U moet een TOTP ondersteunde applicatie hebben (bijv. Google Authenticator, DUO Mobile, Auth, Enpass) om van deze optie gebruik te kunnen maken.', - 'totp_checkpoint_help' => 'Bevestig a.u.b. uw TOTP instellingen door de QR code rechts te scannen met de authenticator applicatie op uw smartphone. Vul vervolgens de 6-delige code, die de applicatie genereerde, in het onderstaand veld in. Druk op enter wanneer u klaar bent.', - 'totp_disable' => 'Schakel 2-delige authenticatie uit.', - 'totp_disable_help' => 'Om TOTP uit te schakelen op dit account moet je een geldige TOTP token geven. TOTP bescherming zal uitgeschakeld worden als de token geldig is.', - 'totp_enable' => 'Schakel Two-Factor Authentication in', - 'totp_enabled' => 'TOTP is nu ingeschakeld op dit account. Klik op de sluit knop om te beëindigen.', - 'totp_enabled_error' => 'De opgegeven TOTP token kon niet gevalideerd worden. Probeer het aub nogmaals.', - 'totp_header' => 'Two-Factor Authenticatie', - 'totp_qr' => 'TOTP QR Code', - 'totp_token' => 'TOTP Token', - 'update_email' => 'E-mail Adres Bijwerken', - 'update_identitity' => 'Identiteit Bijwerken', - 'update_pass' => 'Wachtwoord Bijwerken', - 'update_user' => 'Gebruiker Bijwerken', - 'username_help' => 'Uw gebruikersnaam moet uniek zijn en mag enkel de volgende characters bevatten: :requirements.', - ], - 'api' => [ - 'index' => [ - 'create_new' => 'Creëer nieuwe API sleutel', - 'header' => 'API toegang', - 'header_sub' => 'Beheer jouw API sleutels.', - 'keypair_created' => 'Een API Key-Pair is gegenereerd. Jouw API geheime token is :token. Noteer deze code, hij wordt later niet nog een keer weergegeven.', - 'list' => 'API sleutels', - ], - 'new' => [ - 'allowed_ips' => [ - 'title' => 'Toegestane IP\'s', - ], - 'base' => [ - 'title' => 'Basis Informatie', - ], - 'descriptive_memo' => [ - 'description' => 'Voeg een korte beschrijving over waarvoor deze API sleutel gebruikt zal worden toe.', - 'title' => 'Beschrijvende notitie', - ], - 'form_title' => 'Details', - 'header' => 'Nieuwe API Sleutel', - 'header_sub' => 'Maak een nieuwe API toegangs sleutel', - 'node_management' => [ - 'delete' => [ - 'title' => 'Verwijder node', - ], - 'list' => [ - 'title' => 'Toon nodes', - ], - ], - 'server_management' => [ - 'create' => [ - 'title' => 'Maak een server', - ], - 'delete' => [ - 'description' => 'Geeft toegang tot het verwijderen van een server', - ], - 'list' => [ - 'title' => 'Toon servers', - ], - 'power' => [ - 'description' => 'Geeft toegang om de aan/uit status van een server te beheren.', - ], - 'title' => 'Server beheer', - 'unsuspend' => [ - 'title' => 'Hef schorsing op', - ], - ], - 'service_management' => [ - 'list' => [ - 'title' => 'Toon diensten', - ], - 'view' => [ - 'title' => 'Toon dienst', - ], - ], - 'user_management' => [ - 'create' => [ - 'title' => 'Gebruiker Aanmaken', - ], - 'delete' => [ - 'description' => 'Geeft toegang tot het verwijderen van een gebruiker', - ], - 'list' => [ - 'title' => 'Toon gebruikers', - ], - 'title' => 'Gebruikersbeheer', - 'update' => [ - 'title' => 'Gebruiker bijwerken', - ], - 'view' => [ - 'title' => 'Toon gebruiker', - ], - ], - ], - ], - 'errors' => [ - '403' => [ - 'header' => 'Geen toegang', - ], - '404' => [ - 'header' => 'Bestand niet gevonden.', - ], - 'return' => 'Keer terug naar de vorige pagina', - ], - 'form_error' => 'De volgende fouten werden gevonden bij het verwerken van dit verzoek.', - 'security' => [ - '2fa_header' => '2-factor authenticatie', - '2fa_qr' => 'Configureer 2FA op uw toestel', - 'enable_2fa' => 'Zet 2-Factor authenticatie aan', - ], - 'server_name' => 'Server naam', - 'validation_error' => 'Er is een fout opgetreden tijdens het valideren van de data die u heeft opgegeven.', - 'view_as_admin' => 'U bekijkt de server lijst als een administrator. Hierdoor zijn alle servers die geïnstalleerd zijn op het systeem zichtbaar. Alle servers waarvan u de eigenaar bent zijn gemarkeerd met een blauwe bol links van hun naam.', -]; diff --git a/resources/lang/nl/pagination.php b/resources/lang/nl/pagination.php deleted file mode 100644 index b80265a0..00000000 --- a/resources/lang/nl/pagination.php +++ /dev/null @@ -1,12 +0,0 @@ - '« Vorige', - 'sidebar' => [ - 'account_settings' => 'Account instellingen', - 'files' => 'Bestandsbeheer', - 'manage' => 'Beheer server', - 'overview' => 'Server overzicht', - 'server_controls' => 'Server beheer', - ], -]; diff --git a/resources/lang/nl/passwords.php b/resources/lang/nl/passwords.php deleted file mode 100644 index 7a04355d..00000000 --- a/resources/lang/nl/passwords.php +++ /dev/null @@ -1,7 +0,0 @@ - 'Wachtwoord', - 'sent' => 'We hebben u een wachtwoord herstel link gestuurd!', - 'user' => 'We kunnen geen gebruiker vinden met dat e-mail adres.', -]; diff --git a/resources/lang/nl/server.php b/resources/lang/nl/server.php deleted file mode 100644 index e231b3d2..00000000 --- a/resources/lang/nl/server.php +++ /dev/null @@ -1,174 +0,0 @@ - [ - 'socket_error' => 'We konden niet verbinden met de hoofd Socket.IO server. Er kunnen momenteel netwerk problemen zijn. Het paneel zal niet werken zoals verwacht.', - 'socket_status' => 'De status van deze server is veranderd naar', - 'socket_status_crashed' => 'De server is gedetecteerd als: GECRASHT.', - ], - 'config' => [ - 'allocation' => [ - 'available' => 'Beschikbare locaties', - 'header' => 'Server toewijzingen', - ], - 'database' => [ - 'header' => 'Databases', - 'your_dbs' => 'Jouw databases', - ], - 'sftp' => [ - 'change_pass' => 'Verander SFTP wachtwoord', - 'conn_addr' => 'Connectie adres', - 'header_sub' => 'Account details voor SFTP connecties', - ], - 'startup' => [ - 'command' => 'Opstart commando', - 'header_sub' => 'Beheer server opstart argumenten', - ], - ], - 'files' => [ - 'add' => [ - 'create' => 'Maak bestand', - 'header_sub' => 'Creëer een nieuw bestand op je server', - ], - 'back' => 'Terug naar bestandsbeheer', - 'edit' => [ - 'header' => 'Bewerk bestand', - 'return' => 'Keer terug naar de File Manager', - 'save' => 'Bewaar bestand', - ], - 'last_modified' => 'Laatst gewijzigd', - 'seconds_ago' => 'seconden geleden', - ], - 'index' => [ - 'command' => 'Voeg console commando in', - 'cpu_use' => 'CPU verbruik', - 'memory_use' => 'Geheugen in gebruik', - 'mem_limit' => 'Geheugen limiet', - 'server_info' => 'Server informatie', - 'usage' => 'Gebruik', - ], - 'tasks' => [ - 'current' => 'Huidig Geplande Taken.', - 'header' => 'Geplande taken', - 'new' => [ - 'day_of_month' => 'Dag van de maand', - 'day_of_week' => 'Dag van de week', - 'fri' => 'vrijdag', - 'header' => 'Nieuwe taak', - 'hour' => 'Uur', - 'submit' => 'Creëer taak ', - 'sun' => 'zondag', - 'tues' => 'Dinsdag', - 'wed' => 'Woensdag', - ], - 'new_task' => 'Voeg nieuwe taak toe', - ], - 'users' => [ - 'add' => 'Voeg nieuwe subuser toe', - 'edit' => [ - 'header' => 'Bewerk Subuser', - ], - 'header_sub' => 'Beheer wie je server kan gebruiken', - 'list' => 'Accounts met toegang', - 'new' => [ - 'command' => [ - 'title' => 'Stuur Console Commando', - ], - 'compress_files' => [ - 'title' => 'Bestanden Comprimeren.', - ], - 'copy_files' => [ - 'title' => 'Kopieer bestanden', - ], - 'create_files' => [ - 'description' => 'Staat gebruikers toe om een nieuw bestand aan te maken binnen het paneel.', - ], - 'create_task' => [ - 'title' => 'Taak aanmaken', - ], - 'db_header' => 'Database beheer', - 'delete_files' => [ - 'description' => 'Sta gebruiker toe om bestanden van het systeem te verwijderen.', - ], - 'delete_subuser' => [ - 'description' => 'Sta gebruiker toe om andere sub gebruikers van de server te verwijderen.', - ], - 'download_files' => [ - 'title' => 'Download bestanden', - ], - 'edit_files' => [ - 'description' => 'Staat gebruikers toe om een bestand enkel te lezen.', - ], - 'edit_subuser' => [ - 'title' => 'Bewerk sub gebruiker', - ], - 'email' => 'Email adres', - 'file_header' => 'Bestands beheer', - 'header' => 'Voeg nieuwe gebruiker toe', - 'kill' => [ - 'title' => 'Dood server', - ], - 'list_files' => [ - 'title' => 'Toon bestanden', - ], - 'list_tasks' => [ - 'title' => 'Toon taken', - ], - 'queue_task' => [ - 'title' => 'Plan taak', - ], - 'reset_db_password' => [ - 'title' => 'Herstel database wachtwoord', - ], - 'reset_sftp' => [ - 'title' => 'Herstel SFTP wachtwoord', - ], - 'restart' => [ - 'title' => 'Herstart server', - ], - 'server_header' => 'Server Beheer', - 'sftp_header' => 'SFTP beheer', - 'start' => [ - 'description' => 'Staat gebruikers toe om de server te herstarten.', - ], - 'stop' => [ - 'description' => 'Sta gebruiker toe om de server te stoppen.', - ], - 'toggle_task' => [ - 'description' => 'Sta gebruiker toe om een taak aan/uit te zetten.', - ], - 'upload_files' => [ - 'description' => 'Staat gebruikers toe om bestanden te uploaden via het bestandsbeheer.', - ], - 'view_databases' => [ - 'title' => 'Geef database details weer', - ], - 'view_schedule' => [ - 'title' => 'Bekijk Schema', - ], - 'view_sftp' => [ - 'description' => 'Sta gebruiker toe om de SFTP informatie te zien, maar niet het wachtwoord.', - 'title' => 'Bekijk SFTP Details', - ], - 'view_sftp_password' => [ - 'description' => 'Staat gebruikers toe het SFTP wachtwoord te bekijken voor deze server.', - 'title' => 'Geef SFTP wachtwoord weer', - ], - 'view_startup' => [ - 'description' => 'Staat een gebruiker toe de startup command en de bijbehorende variabelen voor een server te bekijken.', - 'title' => 'Bekijk Startup Command', - ], - 'view_subuser' => [ - 'description' => 'Staat een gebruiker toe permissies van subusers te bekijken.', - 'title' => 'Toon sub gebruiker', - ], - 'view_task' => [ - 'description' => 'Staat een gebruiker toe details van een taak te bekijken.', - 'title' => 'Bekijk Taak', - ], - ], - 'update' => 'Werk sub gebruiker bij', - 'user_assigned' => 'De subuser is succesvol toegevoegd aan deze server.', - 'user_updated' => 'Rechten zijn bijgewerkt.', - ], -]; diff --git a/resources/lang/nl/strings.php b/resources/lang/nl/strings.php deleted file mode 100644 index 629c6329..00000000 --- a/resources/lang/nl/strings.php +++ /dev/null @@ -1,49 +0,0 @@ - '2FA', - '2fa_token' => 'Authenticatie Token', - 'account' => 'account', - 'action' => 'Actie', - 'admin' => 'Admin', - 'admin_control' => 'Admin controle', - 'admin_cp' => 'Admin Besturings Paneel', - 'again' => 'Opnieuw', - 'alias' => 'Alias', - 'api_access' => 'API toegang', - 'close' => 'Sluit', - 'configuration' => 'Configuratie', - 'confirm_password' => 'Bevestig wachtwoord', - 'created_at' => 'Gecreëerd op', - 'data' => 'Bestanden', - 'database' => 'Database', - 'email' => 'E-mail', - 'enabled' => 'Ingeschakeld', - 'expires' => 'Verloopt', - 'home' => 'Home', - 'language' => 'Taal', - 'memo' => 'Memo', - 'memory' => 'Geheugen', - 'name' => 'Naam', - 'new' => 'nieuw', - 'next_run' => 'Volgende ronde', - 'node' => 'Node', - 'none' => 'Geen', - 'password' => 'Wachtwoord', - 'players' => 'Spelers', - 'primary' => 'Primair', - 'queued' => 'Wachtrij', - 'root_administrator' => 'Root administrator', - 'save' => 'Opslaan', - 'security' => 'Beveiliging', - 'select_all' => 'Selecteer alles', - 'sftp' => 'SFTP', - 'status' => 'Status', - 'stop' => 'Stop', - 'submit' => 'Bevestig', - 'success' => 'Geslaagd', - 'suspended' => 'Geschorst', - 'username' => 'gebruikersnaam', - 'whoops' => 'Oeps', - 'yes' => 'Ja', -]; diff --git a/resources/lang/nl/validation.php b/resources/lang/nl/validation.php deleted file mode 100644 index 6f4ebca1..00000000 --- a/resources/lang/nl/validation.php +++ /dev/null @@ -1,15 +0,0 @@ - 'geaccepteerd', - 'active_url' => 'De :attribute is niet een geldige URL.', - 'after' => 'De :attribute datum moet later zijn dan :date.', - 'after_or_equal' => 'De :attribute datum moet later zijn of gelijk aan :date.', - 'alpha' => 'Het :attribute mag enkel letters bevatten.', - 'alpha_dash' => 'De :attribute mag alleen letters, nummers en streepjes bevatten.', - 'alpha_num' => 'De :attribute mag alleen letters en nummers bevatten.', - 'email' => 'E-mail', - 'in' => 'Het geselecteerde :attribute is ongeldig.', - 'timezone' => 'Het :attribute moet een geldige tijdszone zijn.', - 'totp' => 'De totp token is ongeldig. Is het verlopen?', -]; diff --git a/resources/lang/ro/auth.php b/resources/lang/ro/auth.php deleted file mode 100644 index 048e0181..00000000 --- a/resources/lang/ro/auth.php +++ /dev/null @@ -1,27 +0,0 @@ - 'Token-ul 2FA primit este invalid. ', - '2fa_required' => 'Autentificare 2-Factori', - 'authentication_required' => 'Autentificarea este necesară.', - 'auth_error' => 'Eroare la logare.', - 'confirmpassword' => 'Confirmă Parola', - 'emailsent' => 'Email-ul pentru resetare parolă a fost trimis.', - 'email_sent' => 'Un email a fost trimis către tine cu instrucțiuni pentru a-ți reseta parola.', - 'errorencountered' => 'A apărut o eroare în încercarea de a procesa cererea ta.', - 'failed' => 'Datele de logare introduse nu se potrivesc cu cele din baza noastră de date.', - 'forgot_password' => 'Am uitat parola', - 'not_authorized' => 'Nu ai permisiunea să faci această acțiune.', - 'password_requirements' => 'Parola trebuie să conțină cel puțin o literă mare, o literă mică, o cifră și trebuie să fie de cel puțin 8 caractere în lungime.', - 'remeberme' => 'Memorează', - 'remember_me' => 'Memorează', - 'request_reset' => 'Caută contul', - 'request_reset_text' => 'Ai pierdut parola contului tău? Nu e capăt de lume, doar scrie email-ul tau mai jos.', - 'resetpassword' => 'Resetează Parola', - 'reset_password' => 'Restează Parola Contului', - 'reset_password_text' => 'Resetează parola contului tău.', - 'sendlink' => 'Link Resetare Parolă', - 'sign_in' => 'Login', - 'throttle' => 'Prea multe încercări de logare. Te rog încearcă iar în :seconds secunde.', - 'totp_failed' => 'Token-ul TOTP introdus este invalid. Te rog asigură-te că token-ul generat de device-ul tău este valid.', -]; diff --git a/resources/lang/ro/base.php b/resources/lang/ro/base.php deleted file mode 100644 index c0ccb9ac..00000000 --- a/resources/lang/ro/base.php +++ /dev/null @@ -1,282 +0,0 @@ - [ - 'current_password' => 'Parolă curentă', - 'delete_user' => 'Șterge Utilizator', - 'email_password' => 'Parola Email', - 'exception' => 'A aparut o eroare in incercarea de a-ti actualiza contul.', - 'first_name' => 'Prenume', - 'header' => 'Management Cont', - 'header_sub' => 'Modifică detaliile contului tău.', - 'invalid_pass' => 'Parola introdusă nu este validă pentru acest cont.', - 'last_name' => 'Nume', - 'new_email' => 'Adresă Email Nouă', - 'new_password' => 'Parolă Nouă', - 'new_password_again' => 'Repetă Noua Parolă', - 'totp_apps' => 'Trebuie să ai o aplicație TOTP(ex. Authenticator,DUO Mobile,Authy,Enpass) pentru a folosi această opțiune.', - 'totp_checkpoint_help' => 'Te rog verifică setările tale TOTP prin scanarea Codului QR din dreapta folosind aplicatia de autentificare din telefonul tău și scrie codul de 6 cifre generat de aplicație în spațiul de mai jos. Apasă tasta enter când ai terminat.', - 'totp_disable' => 'Dezactivează Autentificarea 2-Factori.', - 'totp_disable_help' => 'Pentru a dezactiva TOTP pe acest cont trebuie să introduci token-ul TOTP valid. O dată validat, protecția TOTP din acest cont va fi dezactivată.', - 'totp_enable' => 'Activează Autentificarea 2-Factori.', - 'totp_enabled' => 'Contul tău a fost activat cu verificarea TOTP. Te rog apasă butonul de close din spațiul ăsta pentru a termina.', - 'totp_enabled_error' => 'Token-ul TOTP intodus nu a putut fi verificat. Te rog incearcă iar.', - 'totp_enable_help' => 'Aparent nu ai Autentificarea 2-Factori activată. Această metodă de autentificare adaugă o barieră adițională ce previne accesul neautorizat în contul tău. Dacă o activezi, o să fie necesar să scrii un cod generat pe telefonul tău sau alt device ce suportă TOTP înainte de a termina login-ul.', - 'totp_header' => 'Autentificarea Doi-Factori', - 'totp_qr' => 'Cod QR TOTP', - 'totp_token' => 'Token TOTP', - 'update_email' => 'Actualizează Adresa Email', - 'update_identitity' => 'Actualizează Identitatea', - 'update_pass' => 'Actualizează Parola', - 'update_user' => 'Actualizează Utilizator', - 'username_help' => 'Numele tău de utilizator trebuie să fie unic contului tau, el poate conține următoarele caractere: -:requirements.', - ], - 'api' => [ - 'index' => [ - 'create_new' => 'Creează o nouă cheie API.', - 'header' => 'Acces API', - 'header_sub' => 'Modifică cheile de acces API.', - 'list' => 'Chei API', - ], - 'new' => [ - 'allowed_ips' => [ - 'description' => 'Scrie o listă delimitată de linii cu IP-urile care au acces la API folosind această cheie. Notația CIDR nu e permisă. Lasă gol pentru a permite orice adresă IP.', - 'title' => 'IP-uri Permise', - ], - 'base' => [ - 'information' => [ - 'description' => 'Întoarce o listă cu toate serverele la care acest cont are acces.', - 'title' => 'Informații de Bază', - ], - 'title' => 'Informații Bază', - ], - 'descriptive_memo' => [ - 'description' => 'Scrie o descriere scurtă despre ce o să faci cu acest API Key.', - 'title' => 'Memo Descriptiv', - ], - 'form_title' => 'Detalii', - 'header' => 'Cheie API Nouă', - 'header_sub' => 'Creează o nouă cheie de acces API', - 'location_management' => [ - 'list' => [ - 'description' => 'Permite listarea tuturor locațiilor și a node-urilor asociate.', - 'title' => 'Lista Locațiilor', - ], - 'title' => 'Management Locații', - ], - 'node_management' => [ - 'allocations' => [ - 'description' => 'Permite vederea tuturor alocațiilor în panou și a node-urilor.', - 'title' => 'Listă Alocații', - ], - 'create' => [ - 'description' => 'Permite creerea unui node nou în sistem.', - 'title' => 'Creează Node', - ], - 'delete' => [ - 'description' => 'Permite ștergerea unui node.', - 'title' => 'Șterge Node', - ], - 'list' => [ - 'description' => 'Permite listarea tuturor node-urilor prezente în sistem.', - 'title' => 'Listă Node-uri', - ], - 'title' => 'Management Node', - 'view' => [ - 'description' => 'Permite vederea detalilor unui node, inclusiv serviciile active.', - 'title' => 'Listează Node-urile Singure', - ], - ], - 'server_management' => [ - 'build' => [ - 'description' => 'Permite modificarea parametriilor unui server precum memoria, CPU, spațiul disk si IP-urile alocate sau default.', - 'title' => 'Actualizează Build', - ], - 'command' => [ - 'description' => 'Permite utilizatorului să trimită comenzi serverului specificat.', - 'title' => 'Trimite Comandă', - ], - 'config' => [ - 'description' => 'Permite modificarea configuraratiei server-ului(nume, detinator si token acces)', - 'title' => 'Actualizează Configurația', - ], - 'create' => [ - 'description' => 'Permite creerea unui nou server în sistem.', - 'title' => 'Creează Server', - ], - 'delete' => [ - 'description' => 'Permite ștergerea unui server.', - 'title' => 'Șterge Server', - ], - 'list' => [ - 'description' => 'Permite listarea tuturor serverelor din sistem.', - 'title' => 'Listează Serverele', - ], - 'power' => [ - 'description' => 'Permite accesul la starea server-ului.', - 'title' => 'Stare Server', - ], - 'server' => [ - 'description' => 'Permite accesul la informațiile despre un singur server, inclusiv statusul curent si alocarea.', - 'title' => 'Info Server', - ], - 'suspend' => [ - 'description' => 'Permite suspendarea unui server.', - 'title' => 'Suspendă Server', - ], - 'title' => 'Management Server', - 'unsuspend' => [ - 'description' => 'Permite reluarea unui server.', - 'title' => 'Reluare Server', - ], - 'view' => [ - 'description' => 'Permite accesul la detaliile unui server, inclusiv daemon_token si informații despre procesul curent.', - 'title' => 'Arată un Singur Server', - ], - ], - 'service_management' => [ - 'list' => [ - 'description' => 'Permite listarea tuturor serviciilor configurate în sistem.', - 'title' => 'Listă Servicii', - ], - 'title' => 'Management Servicii', - 'view' => [ - 'description' => 'Permite listarea detalilor despre fiecare serviciu în sistem, include opțiuniile serviciului si variabilele.', - 'title' => 'Listează un Singur Serviciu', - ], - ], - 'user_management' => [ - 'create' => [ - 'description' => 'Permite creearea unui nou user în sistem.', - 'title' => 'Creează Utilizator', - ], - 'delete' => [ - 'description' => 'Permite ștergerea unui utilizator.', - 'title' => 'Șterge Utilizator', - ], - 'list' => [ - 'description' => 'Permite listarea tuturor utilizator prezenți în sistem.', - 'title' => 'Listează Utilizatorii', - ], - 'title' => 'Management Utilizatori', - 'update' => [ - 'description' => 'Permite modificarea detalii utilizatori(email, parolă, informații TOTP).', - 'title' => 'Actualizează Utilizator', - ], - 'view' => [ - 'description' => 'Permite vederea detaliilor unui specific utilizator, inclusiv serviciilor active.', - 'title' => 'Listează Utilizator Singur', - ], - ], - ], - 'permissions' => [ - 'admin' => [ - 'location' => [ - 'list' => [ - 'desc' => 'Permite vizualizarea tuturor locațiilor și nodurilor.', - 'title' => 'Vizualizează Locațiile', - ], - ], - 'location_header' => 'Controlează Locația', - 'node' => [ - 'create' => [ - 'desc' => 'Permite crearea unui nou nod în sistem.', - 'title' => 'Crează Nod', - ], - 'delete' => [ - 'desc' => 'Permite ștergerea unui nod din sistem.', - 'title' => 'Șterge Nod', - ], - 'list' => [ - 'desc' => 'Permite vizualizarea tuturor nodurilor din sistem.', - 'title' => 'Vezi Nodurile', - ], - 'view-config' => [ - 'desc' => 'Pericol! Asta permite vizualizarea configurației nodului folosită de daemon și expune vizualizarea chei secrete a daemonului!', - 'title' => 'Vezi Configurația Nodului', - ], - 'view' => [ - 'desc' => 'Permite vizualizarea detaliilor despre un anumit nod incluzând și serverele lui active.', - 'title' => 'Vezi Nodul', - ], - ], - 'node_header' => 'Controlează Nodul', - 'pack' => [ - 'view' => [ - 'title' => 'Vezi Pachetul', - ], - ], - 'server' => [ - 'edit-container' => [ - 'title' => 'Editează Containerul Serverului', - ], - 'edit-details' => [ - 'desc' => 'Permite editarea detaliilor serverului, precum numele, proprietarul, descrierea sau cheia secretă.', - 'title' => 'Editează Detaliile Serverului', - ], - 'edit-startup' => [ - 'desc' => 'Permite modificarea comenzilor și parametrilor de start ai serverului.', - 'title' => 'Editează Startup-ul Serverului', - ], - 'install' => [ - 'title' => 'Comută Starea Instalării', - ], - 'list' => [ - 'desc' => 'Permite vizualizarea tuturor serverelor din sistem.', - 'title' => 'Listează Serverele', - ], - 'rebuild' => [ - 'title' => 'Reconstruiește Serverul', - ], - 'view' => [ - 'title' => 'Vezi Serverul', - ], - ], - 'user' => [ - 'edit' => [ - 'desc' => 'Permite modificarea detaliilor utilizatorului', - 'title' => 'Actualizează Utilizatorul', - ], - ], - ], - ], - ], - 'confirm' => 'Ești sigur?', - 'errors' => [ - '403' => [ - 'desc' => 'Nu ai permisiunea să accesezi această resursă pe server.', - 'header' => 'Interzis', - ], - '404' => [ - 'desc' => 'Nu am putut găsi această resursă pe server.', - 'header' => 'Fișierul nu a fost găsit', - ], - 'home' => 'Mergi acasă', - 'return' => 'Întoarce-te la Pagina Precedentă', - ], - 'form_error' => 'Următoarele erori au apărut în timpul încercării de a procesa această cerere.', - 'index' => [ - 'header' => 'Consolă Server', - 'header_sub' => 'Controlează serverele în timp real.', - 'list' => 'Listă Servere', - ], - 'no_servers' => 'Nu ai nici un server prezent în contul tău.', - 'password_req' => 'Parola trebuie să îndeplinească următoarele cerințe: cel puțin o literă mare, o literă mică, o cifră și să fie de minim 8 caractere în lungime.', - 'security' => [ - '2fa_checkpoint_help' => 'Folosește aplicația 2FA de pe telefonul tău și fă poză la codul QR din stânga sau introdu manual codul de sub el. După ce ai făcut asta, generează un token și scrie-l mai jos.', - '2fa_disabled' => 'Autentificarea 2-Factori este dezactivată în contul tău! Ar trebui să activezi 2FA pentru a avea un nivel suplimentar de protecție în contul tău.', - '2fa_enabled' => 'Autentificarea 2-Facotri este activată pe acest cont și este necesară pentru a te loga în panou. Dacă vrei să dezactivezi 2FA, scrie un token valid mai jos și trimite.', - '2fa_header' => 'Autentificare 2-Factori', - '2fa_qr' => 'Configurează 2FA în device-ul tău', - '2fa_token_help' => 'Scrie Token-ul 2FA generat de aplicația ta (Google Authenticator,Authy,etc.).', - 'disable_2fa' => 'Dezactivează Autentificarea 2-Factori', - 'enable_2fa' => 'Activează Autentificarea 2-Factori', - 'header' => 'Securitate Cont', - 'header_sub' => 'Controlează sesiunile active și Autentificarea 2-Factori', - 'sessions' => 'Sesiuni Active', - ], - 'server_name' => 'Nume Server', - 'validation_error' => 'A apărut o eroare cu unul sau mai multe câmpuri.', - 'view_as_admin' => 'Vezi acest server listat ca admin. Ca atare, toate serverele din sistem sunt afișate. Toate serverele la care tu ești deținător sunt afișate cu un punct albastru în fața numelui.', -]; diff --git a/resources/lang/ro/pagination.php b/resources/lang/ro/pagination.php deleted file mode 100644 index 764fd7e5..00000000 --- a/resources/lang/ro/pagination.php +++ /dev/null @@ -1,17 +0,0 @@ - 'Următorul »', - 'previous' => '« Anterior', - 'sidebar' => [ - 'account_controls' => 'Control Cont', - 'account_security' => 'Securitate Cont', - 'account_settings' => 'Setări Cont', - 'files' => 'Manager de Fișiere', - 'manage' => 'Gestionează Server', - 'overview' => 'Afișare Server', - 'servers' => 'Serverele Tale', - 'server_controls' => 'Controlează Server', - 'subusers' => 'Gestionează Sub-Useri', - ], -]; diff --git a/resources/lang/ro/passwords.php b/resources/lang/ro/passwords.php deleted file mode 100644 index ecd2c586..00000000 --- a/resources/lang/ro/passwords.php +++ /dev/null @@ -1,9 +0,0 @@ - 'Parolă', - 'reset' => 'Parola ta a fost resetată!', - 'sent' => 'Ți-am trimis pe e-mail link-ul de resetare al parolei!', - 'token' => 'Token-ul de resetare parolă este invalid.', - 'user' => 'Nu am putut găsi un utilizator cu acest e-mail.', -]; diff --git a/resources/lang/ro/server.php b/resources/lang/ro/server.php deleted file mode 100644 index 8c6c765b..00000000 --- a/resources/lang/ro/server.php +++ /dev/null @@ -1,284 +0,0 @@ - [ - 'socket_error' => 'Nu s-a putut conecta la server-ul Socket.IO principal, poate fi o problemă cu rețeaua. Panoul poate să nu funcționeze normal.', - 'socket_status' => 'Statusul server-ului s-a schimbat în', - 'socket_status_crashed' => 'Server-ul a fost detectat ca CRASHED.', - ], - 'config' => [ - 'allocation' => [ - 'available' => 'Alocații Disponibile', - 'header' => 'Alocații Server', - 'header_sub' => 'Controlează IP-urile și port-urile disponibile pe acest server.', - 'help' => 'Ajutor Alocare', - 'help_text' => 'Lista din stânga include toate IP-urile și port-urile deschise pentru server-ul tău și disponibile să le folosești pentru conexiunile care vin.', - ], - 'database' => [ - 'add_db' => 'Adaugă o nouă bază de date.', - 'header' => 'Baze de date', - 'header_sub' => 'Toate bazele de date disponibile pentru acest server.', - 'host' => 'Host MySQL', - 'no_dbs' => 'Nu sunt baze de date listate pentru acest server.', - 'reset_password' => 'Resetează Parola', - 'your_dbs' => 'Bazele tale de date', - ], - 'sftp' => [ - 'change_pass' => 'Schimbă Parola SFTP', - 'conn_addr' => 'Adresă Conexiune', - 'details' => 'Detalii SFTP', - 'header' => 'Configurare SFTP', - 'header_sub' => 'Detalii cont pentru conexiuni SFTP.', - 'warning' => 'Asigură-te că clientul tău este setat să folosească SFTP și nu FTP/FTPS pentru conexiuni, există o diferență între protocoale.', - ], - 'startup' => [ - 'command' => 'Comenzi Start', - 'edit_params' => 'Modifică Parametrii', - 'header' => 'Configurație Start', - 'header_sub' => 'Controlează argumentele de start ale server-ului.', - 'startup_regex' => 'Regex de Verificare', - 'startup_var' => 'Variabilă Comandă de Start', - 'update' => 'Actualizează Parametrii de Start', - ], - ], - 'files' => [ - 'add' => [ - 'create' => 'Creează Fișier', - 'header' => 'Fișier Nou', - 'header_sub' => 'Creează un nou fișier pe server-ul tău.', - 'name' => 'Nume Filă', - ], - 'add_folder' => 'Adaugă Folder Nou', - 'add_new' => 'Adaugă Filă Nouă', - 'back' => 'Înapoi la Manager Fișiere', - 'edit' => [ - 'header' => 'Modifcă Filă', - 'header_sub' => 'Fă modificări la un fișier de pe web.', - 'return' => 'Întoarce-te la Manager Fișiere', - 'save' => 'Salvează Fișier', - ], - 'file_name' => 'Nume Fișier', - 'header' => 'Manager Fișiere', - 'header_sub' => 'Modifică toate fișiere tale direct de pe web.', - 'last_modified' => 'Ultima Modificare', - 'loading' => 'Încarc structura fișierelor, poate dura câteva secunde.', - 'path' => 'Când configurezi o cale a unui fișier în plugin-urile sau setările server-ului tău, trebuie să folosești :path ca și cale de bază. Mărimea maximă pentru fisiere încarcate pe web este :size.', - 'saved' => 'Fișierul a fost salvat cu succes.', - 'seconds_ago' => 'acum câteva secunde', - 'size' => 'Mărime', - 'yaml_notice' => 'Acum editezi un fișier YAML. Aceste fișiere nu acceptă tab-uri, trebuie să folosești spații. Noi am modificat ca atunci când apeși tab se scriu :dropdown spații.', - ], - 'index' => [ - 'add_new' => 'Adaugă Server Nou', - 'allocation' => 'Alocații', - 'command' => 'Scrie Comandă Consolă', - 'connection' => 'Conexiune Implicită', - 'control' => 'Controlează Server', - 'cpu_use' => 'CPU Utilizat', - 'disk_space' => 'Spațiu Disk', - 'header' => 'Consolă Server', - 'header_sub' => 'Controlează serverele în timp real.', - 'memory_use' => 'Utilizare Memorie', - 'mem_limit' => 'Limită Memorie', - 'server_info' => 'Informații Server', - 'title' => 'Afișare Server :name', - 'usage' => 'Uz', - 'xaxis' => 'Timp (Creștere 2s)', - ], - 'tasks' => [ - 'actions' => [ - 'command' => 'Trimite Comandă', - 'power' => 'Trimite Puterea de Comutare', - ], - 'current' => 'Sarcinile Programate Curent', - 'header' => 'Activițăți Programate', - 'header_sub' => 'Automatizează server-ul.', - 'new' => [ - 'custom' => 'Valoare Personalizată', - 'day_of_month' => 'Ziua din Lună', - 'day_of_week' => 'Ziua din Saptămână', - 'fri' => 'Vineri', - 'header' => 'Sarcină Nouă', - 'header_sub' => 'Creează o nouă sarcină programată pentru acest server.', - 'hour' => 'Oră', - 'minute' => 'Minut', - 'mon' => 'Luni', - 'payload' => 'Payload Sarcină', - 'payload_help' => 'Ca exemplu, dacă ai selectat Send Command scrie comanda aici. Dacă ai selectat Send Power Option pune acțiunea de pornire aici (ex. restart).', - 'sat' => 'Sâmbătă', - 'submit' => 'Creează Sarcină', - 'sun' => 'Duminică', - 'thurs' => 'Joi', - 'tues' => 'Marți', - 'type' => 'Tip Sarcină', - 'wed' => 'Miercuri', - ], - 'new_task' => 'Adaugă Activitate Nouă', - 'toggle' => 'Schimbă Status', - ], - 'users' => [ - 'add' => 'Adaugă Subuser Nou', - 'configure' => 'Configurează Permisiuni', - 'edit' => [ - 'header' => 'Modifică Subuser', - 'header_sub' => 'Schimbă accesul utilizatorului la server.', - ], - 'header' => 'Gestionează Utilizatorii', - 'header_sub' => 'Controlează cine îți poate accesa server-ul.', - 'list' => 'Conturi cu Acces', - 'new' => [ - 'command' => [ - 'description' => 'Permite trimiterea unei comenzi din consolă. Dacă user-ul nu are permisiunea de stop sau restart, ei nu pot trimite comanda de stop către aplicație.', - 'title' => 'Trimite Comandă Consolă', - ], - 'compress_files' => [ - 'description' => 'Permite unui user să creeze arhive ale fișierelor și a directoarelor din sistem.', - 'title' => 'Comprimă Fișiere', - ], - 'copy_files' => [ - 'description' => 'Permite unui user să copieze fișierele și directoarele din sistemul de fișiere.', - 'title' => 'Copiază Fișiere', - ], - 'create_files' => [ - 'description' => 'Permite unui user să creeze un fișier nou folosind panoul.', - 'title' => 'Creează Fișiere', - ], - 'create_subuser' => [ - 'description' => 'Permite unui user să creeze subuseri pe server.', - 'title' => 'Creează Subuser', - ], - 'create_task' => [ - 'description' => 'Permite unui user să creeze sarcini noi.', - 'title' => 'Creează Sarcină', - ], - 'db_header' => 'Gestionează Bază de Date', - 'decompress_files' => [ - 'description' => 'Permite unui user să decomprime arhive .zip și .tar(.gz). ', - 'title' => 'Decomprimă Fișiere', - ], - 'delete_files' => [ - 'description' => 'Permite unui user să șteargă toate fișiere din sistem.', - 'title' => 'Șterge Fișiere', - ], - 'delete_subuser' => [ - 'description' => 'Permite unui user să șteargă alți subuseri din server.', - 'title' => 'Șterge Subuser', - ], - 'delete_task' => [ - 'description' => 'Permite unui user să șteargă o sarcină.', - 'title' => 'Șterge Sarcină', - ], - 'download_files' => [ - 'description' => 'Permite utilizatorului să descarce fișiere. Dacă un user primește această permisiune el poate descărca și vedea fișiere chiar dacă permisiunea nu este atribuită în panou.', - 'title' => 'Descarcă Fișiere', - ], - 'edit_files' => [ - 'description' => 'Permite unui user să deschidă un fișier doar pentru a-l vedea.', - 'title' => 'Editează Fișiere', - ], - 'edit_startup' => [ - 'description' => 'Permite unui user să modifice variabilele de start.', - 'title' => 'Modifică Comanda de Start', - ], - 'edit_subuser' => [ - 'description' => 'Permite unui user să modifice permisiunile atribuite altor subuseri.', - 'title' => 'Modifică Subuser', - ], - 'email' => 'Adresă de Email', - 'email_help' => 'Scrie adresa email a utilizatorului pe care vrei să-l înviți să modifice acest server.', - 'file_header' => 'Gestionează Fișiere', - 'header' => 'Adaugă User Nou', - 'header_sub' => 'Adaugă un nou user cu permisiuni la acest server.', - 'kill' => [ - 'description' => 'Permite unui user să omoare procesul unui server.', - 'title' => 'Omoară Server', - ], - 'list_files' => [ - 'description' => 'Permite unui user să listeze toate fișierele și directoarele de pe server dar nu și vizionarea conținutului fișierelor.', - 'title' => 'Listează Fișiere', - ], - 'list_subusers' => [ - 'description' => 'Permite unui user să vadă lista tuturor subuserilor atribuiți server-ului.', - 'title' => 'Listează Subuserii', - ], - 'list_tasks' => [ - 'description' => 'Permite unui user să listeze toate sarcinile(activate și dezactivate) de pe un server.', - 'title' => 'Listează Sarcinile', - ], - 'move_files' => [ - 'description' => 'Permite unui user să mute și să redenumească fișierele și directoarele din sistem.', - 'title' => 'Redenumește & Mută Fișiere', - ], - 'power_header' => 'Gestionează Status', - 'queue_task' => [ - 'description' => 'Permite unui user să programeze o sarcină pentru urmatăroul ciclu de sarcini.', - 'title' => 'Programează Sarcina', - ], - 'reset_db_password' => [ - 'description' => 'Permite unui user să reseteze parolele pentru baza de date.', - 'title' => 'Restează Parola Bazei de Date', - ], - 'reset_sftp' => [ - 'description' => 'Permite unui user să schimbe parola SFTP a server-ului.', - 'title' => 'Restează Parola SFTP', - ], - 'restart' => [ - 'description' => 'Permite user-ului să dea restart server-ului.', - 'title' => 'Restart Server', - ], - 'save_files' => [ - 'description' => 'Permite user-ului să salveze conținutul fișierelor modificate.', - 'title' => 'Salvează Fișiere', - ], - 'server_header' => 'Gestionează Server', - 'set_connection' => [ - 'description' => 'Permite unui user să stabilească conexiunea prestabilită și să vadă port-urile disponibile.', - 'title' => 'Setează Conexiunea Prestabilită', - ], - 'sftp_header' => 'Gestionează SFTP', - 'start' => [ - 'description' => 'Permite user-ului să pornească server-ul.', - 'title' => 'Start Server', - ], - 'stop' => [ - 'description' => 'Permite user-ului să oprească server-ul.', - 'title' => 'Stop Server', - ], - 'subuser_header' => 'Gestionează Subuser', - 'task_header' => 'Management Activități', - 'toggle_task' => [ - 'description' => 'Permite unui user să pornească sau să inchidă o sarcină.', - 'title' => 'Comută Sarcina', - ], - 'upload_files' => [ - 'description' => 'Permite utilizatorilor să încarce fișiere folosind manager-ul de fișiere.', - 'title' => 'Încarcă Fișiere', - ], - 'view_databases' => [ - 'description' => 'Permite unui user să vadă toate bazele de date asociate cu server-ul ăsta, inclusiv numele și parolele bazei de date.', - 'title' => 'Vezi Detalile Bazei de Date', - ], - 'view_sftp' => [ - 'description' => 'Permite user-ului să vadă informațiile SFTP ale server-ului dar nu și parola.', - 'title' => 'Vezi Detaliile SFTP', - ], - 'view_sftp_password' => [ - 'description' => 'Permite user-ului să vadă parolele SFTP ale acestui server.', - 'title' => 'Vezi Parola SFTP', - ], - 'view_startup' => [ - 'description' => 'Permite unui user să vadă comenzile de start și variabilele asociate server-ului.', - 'title' => 'Vezi Comanda de Start', - ], - 'view_subuser' => [ - 'description' => 'Permite unui user să vadă permisiunile atribuite subuserilor.', - 'title' => 'Vezi Subuser', - ], - 'view_task' => [ - 'description' => 'Permite user-ului să vadă detaliile unei sarcini.', - 'title' => 'Vezi Sarcină', - ], - ], - 'update' => 'Actualizează Subuser', - ], -]; diff --git a/resources/lang/ro/strings.php b/resources/lang/ro/strings.php deleted file mode 100644 index 763c626d..00000000 --- a/resources/lang/ro/strings.php +++ /dev/null @@ -1,88 +0,0 @@ - '2FA', - '2fa_token' => 'Token Autentificare', - 'account' => 'Cont', - 'action' => 'Actiune', - 'admin' => 'Administrator', - 'admin_control' => 'Control Admin', - 'admin_cp' => 'Panou Control Admin', - 'again' => 'Din nou', - 'alias' => 'Alias', - 'api_access' => 'Acces API', - 'cancel' => 'Anulează', - 'captcha_invalid' => 'Codul Captcha introdus este invalid.', - 'close' => 'Închide', - 'configuration' => 'Configurație', - 'confirm_password' => 'Confirmă Parola', - 'connection' => 'Conexiune', - 'cpu' => 'CPU', - 'create' => 'Creează', - 'created' => 'Creat', - 'created_at' => 'Creat La', - 'current_password' => 'Parolă Curentă', - 'danger' => 'Pericol', - 'data' => 'Dată', - 'database' => 'Bază de date', - 'databases' => 'Baze de date', - 'delete' => 'Șterge', - 'disabled' => 'Dezactivat', - 'email' => ':attribute trebuie să fie o adresă de mail validă.', - 'enabled' => 'Activat', - 'expires' => 'Expiră', - 'home' => 'Acasă', - 'id' => 'ID', - 'ip' => ':attribute trebuie să fie o adresă IP validă.', - 'language' => 'Limbă', - 'last_activity' => 'Ultima Activitate', - 'last_run' => 'Ultima Executare', - 'location' => 'Locație', - 'login' => 'Login', - 'logout' => 'Logout', - 'make_primary' => 'Modifică în Primar', - 'memo' => 'Memo', - 'memory' => 'Memorie', - 'name' => 'Nume', - 'never' => 'niciodată', - 'new' => 'Nou', - 'next_run' => 'Următoarea Pornire', - 'no' => 'Nu', - 'node' => 'Node', - 'none' => 'Nici unul', - 'optional' => 'Opțional', - 'owner' => 'Proprietar', - 'password' => 'Parolă', - 'players' => 'Jucători', - 'port' => 'Port', - 'primary' => 'Primar', - 'public_key' => 'Cheie Publică', - 'queued' => 'În așteptare', - 'read_only' => 'Doar Citire', - 'registered' => 'Înregistrat', - 'relation' => 'Relație', - 'required' => 'Câmpul :attribute este necesar.', - 'restart' => 'Restart', - 'revoke' => 'Revocă', - 'root_administrator' => 'Administrator Root', - 'save' => 'Salvează', - 'search' => 'Caută', - 'security' => 'Securitate', - 'select_all' => 'Selectează Toate', - 'select_none' => 'Selectează Node', - 'servers' => 'Servere', - 'settings' => 'Setări', - 'sftp' => 'SFTP', - 'sign_out' => 'Sign out', - 'start' => 'Start', - 'status' => 'Status', - 'stop' => 'Stop', - 'submit' => 'Trimite', - 'subuser' => 'Subutilizator', - 'success' => 'Succes', - 'suspended' => 'Suspendat', - 'username' => 'Username', - 'user_identifier' => 'Username sau Email', - 'whoops' => 'Oops', - 'yes' => 'Da', -]; diff --git a/resources/lang/ro/validation.php b/resources/lang/ro/validation.php deleted file mode 100644 index cb49ed4b..00000000 --- a/resources/lang/ro/validation.php +++ /dev/null @@ -1,82 +0,0 @@ - ':attribute trebuie sa fie acceptat.', - 'active_url' => ':attribute nu e un URL valid.', - 'after' => ':attribute trebuie sa fie o dată după :date.', - 'after_or_equal' => ':attribute trebuie sa fie o dată după sau egală cu :date.', - 'alpha' => ':attribute poate conține doar litere.', - 'alpha_dash' => ':attribute poate conține doar litere, numere si cratime.', - 'alpha_num' => ':attribute poate conține doar litere si numere.', - 'array' => ':attribute trebuie să fie o grilă.', - 'before' => ':attribute trebuie să fie o dată înainte de :date.', - 'before_or_equal' => ':attribute trebuie să fie o dată înainte sau egală cu :date.', - 'between' => [ - 'array' => ':attribute trebuie să aibă valori între :min și :max.', - 'file' => ':attribute trebuie să fie între :min și :max kilobytes.', - 'numeric' => ':attribute trebuie să fie între :min și :max.', - 'string' => ':attribute trebuie să fie între :min și :max caractere.', - ], - 'boolean' => 'Câmpul :attribute trebuie să fie adevărat sau fals.', - 'confirmed' => 'Confirmarea :attribute nu se potrivește.', - 'custom' => [ - 'attribute-name' => [ - 'rule-name' => 'mesaj-personalizat', - ], - ], - 'date' => ':attribute nu este o dată validă.', - 'date_format' => ':attribute nu se potrivește cu formatul :format.', - 'different' => ':attribute și :other trebuie să fie diferite.', - 'digits' => ':attribute trebuie să fie din :digits cifre.', - 'digits_between' => ':attribute trebuie să fie între :min și :max cifre.', - 'dimensions' => ':attribute are dimensiunile imaginii invalide.', - 'distinct' => 'Câmpul :attribute are o valoare duplicată.', - 'email' => ':attribute trebuie să fie o adresă de mail validă.', - 'exists' => ':attribute selectat nu este valid.', - 'file' => ':attribute trebuie să fie un fișier.', - 'filled' => 'Câmpul :attribute este necesar.', - 'image' => ':attribute trebuie să fie o imagine.', - 'in' => ':attribute selectat este invalid.', - 'integer' => ':attribute trebuie să fie un întreg.', - 'in_array' => 'Câmpul :attribute nu există în :other.', - 'ip' => ':attribute trebuie să fie o adresă IP validă.', - 'json' => ':attribute trebuie să fie un string JSON valid.', - 'max' => [ - 'array' => ':attribute nu trebuie să aibă mai mult de :max valori.', - 'file' => ':attribute nu trebuie să fie mai mare decât :max kilobytes.', - 'numeric' => ':attribute nu poate fi mai mare decât :max.', - 'string' => ':attribute nu poate fi mai mare de :max caractere.', - ], - 'mimes' => ':attribute trebuie să fie un fișier de tipul: :values.', - 'mimetypes' => ':attribute trebuie să fie un fișier de tipul: :values.', - 'min' => [ - 'array' => ':attribute trebuie să aibă cel puțin :min valori.', - 'file' => ':attribute trebuie să fie cel puțin :min kilobytes.', - 'numeric' => ':attribute trebuie să fie de cel puțin :min.', - 'string' => ':attribute trebuie să fie de cel puțin :min caractere.', - ], - 'not_in' => ':attribute selectat este invalid.', - 'numeric' => ':attribute trebuie să fie un număr.', - 'present' => 'Câmpul :attribute trebuie să fie prezent.', - 'regex' => 'Formatul :attribute este invalid.', - 'required' => 'Câmpul :attribute este necesar.', - 'required_if' => 'Câmpul :attribute este necesar atunci când :other este :value.', - 'required_unless' => 'Câmpul :attribute este necesar daca nu :other este în :values.', - 'required_with' => 'Câmpul :attribute este necesar atunci când :values este prezent.', - 'required_without' => 'Câmpul :attribute este necesar când :values nu este prezent.', - 'required_without_all' => 'Câmpul :attribute este necesar atunci când nici una din :values sunt prezente.', - 'required_with_all' => 'Câmpul :attribute este necesar atunci când :values este prezent.', - 'same' => ':attribute și :other trebuie să se potrivească.', - 'size' => [ - 'array' => ':attribute trebuie să conțină minim :size valori.', - 'file' => ':attribute trebuie să fie de :size kilobytes.', - 'numeric' => ':attribute trebuie să fie :size.', - 'string' => ':attribute trebuie să fie de :size caractere.', - ], - 'string' => ':attribute trebuie să fie un string.', - 'timezone' => ':attribute trebuie să fie o zonă validă.', - 'totp' => 'Token-ul TOTP este invalid. A expirat?', - 'unique' => ':attribute a fost deja luat.', - 'uploaded' => ':attribute a eșuat la încărcare.', - 'url' => 'Formatul :attribute este invalid.', -]; diff --git a/resources/lang/ru/admin/nests.php b/resources/lang/ru/admin/nests.php deleted file mode 100644 index b07441b1..00000000 --- a/resources/lang/ru/admin/nests.php +++ /dev/null @@ -1,33 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -return [ - 'notices' => [ - 'created' => 'Новое гнездо :name успешно создано.', - 'deleted' => 'Успешно удалили запрошенное гнездо с панели.', - 'updated' => 'Успешно обновлены параметры конфигурации гнезда.', - ], - 'eggs' => [ - 'notices' => [ - 'imported' => 'Успешно импортировано это яйцо и связанные с ним переменные.', - 'updated_via_import' => 'Это яйцо было обновлено с использованием предоставленного файла.', - 'deleted' => 'Успешно удалили запрошенное яйцо из панели.', - 'updated' => 'Конфигурация яйца успешно обновлена.', - 'script_updated' => 'Сценарий установки Egg обновлен и будет запускаться всякий раз, когда будут установлены серверы.', - 'egg_created' => 'Новое яйцо было добавлено успешно. Вам нужно будет перезапустить все работающие демоны, чтобы применить это новое яйцо.', - ], - ], - 'variables' => [ - 'notices' => [ - 'variable_deleted' => 'Переменная ":variable" была удалена и больше не будет доступна серверам после восстановления.', - 'variable_updated' => 'Переменная ":variable" была обновлена. Вам нужно будет перестроить все серверы, использующие эту переменную, чтобы применить изменения.', - 'variable_created' => 'Новая переменная была успешно создана и назначена этому яйцу.', - ], - ], -]; diff --git a/resources/lang/ru/admin/node.php b/resources/lang/ru/admin/node.php deleted file mode 100644 index b4abb573..00000000 --- a/resources/lang/ru/admin/node.php +++ /dev/null @@ -1,23 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -return [ - 'validation' => [ - 'fqdn_not_resolvable' => 'Указанное полное доменное имя или IP-адрес не преобразуется в действительный IP-адрес.', - 'fqdn_required_for_ssl' => 'Для использования SSL для этого узла требуется полное доменное имя, которое разрешается в публичный IP-адрес.', - ], - 'notices' => [ - 'allocations_added' => 'Локации успешно добавлены в этот узел.', - 'node_deleted' => 'Узел успешно удален с панели.', - 'location_required' => 'У вас должно быть настроено хотя бы одно местоположение, прежде чем вы сможете добавить узел на эту панель.', - 'node_created' => 'Успешно создан новый узел. Вы можете автоматически настроить демон на этом компьютере, посетив вкладку \'Configuration\'. Прежде чем вы сможете добавить какие-либо серверы, вы должны сначала выделить как минимум один IP-адрес и порт.', - 'node_updated' => 'Информация об узле обновлена. Если какие-либо настройки демона были изменены, вам нужно будет перезагрузить их, чтобы эти изменения вступили в силу.', - 'unallocated_deleted' => 'Удалил все нераспределенные порты для :ip.', - ], -]; diff --git a/resources/lang/ru/admin/pack.php b/resources/lang/ru/admin/pack.php deleted file mode 100644 index 0ff9911d..00000000 --- a/resources/lang/ru/admin/pack.php +++ /dev/null @@ -1,16 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -return [ - 'notices' => [ - 'pack_updated' => 'Пак успешно обновлен.', - 'pack_deleted' => 'Успешно удален пакет ":name" из системы.', - 'pack_created' => 'Новый пакет был успешно создан в системе и теперь доступен для развертывания на серверах.', - ], -]; diff --git a/resources/lang/ru/admin/server.php b/resources/lang/ru/admin/server.php deleted file mode 100644 index 59fb1882..00000000 --- a/resources/lang/ru/admin/server.php +++ /dev/null @@ -1,31 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -return [ - 'exceptions' => [ - 'no_new_default_allocation' => 'Вы пытаетесь удалить выделение по умолчанию для этого сервера, но резервное выделение для использования отсутствует.', - 'marked_as_failed' => 'Этот сервер был помечен как Проблемный предыдущей установки. Текущий статус не может быть переключен в этом состоянии.', - 'bad_variable' => 'Произошла ошибка проверки с переменной :name.', - 'daemon_exception' => 'При попытке установить связь с демоном возникла исключительная ситуация, в результате которой был получен код ответа HTTP / :code. Это исключение было зарегистрировано.', - 'default_allocation_not_found' => 'Запрошенное распределение по умолчанию не было найдено в выделениях этого сервера.', - ], - 'alerts' => [ - 'startup_changed' => 'Начальная конфигурация для этого сервера была обновлена. Если гнездо или яйцо этого сервера было изменено, то теперь будет происходить переустановка.', - 'server_deleted' => 'Сервер успешно удален из системы.', - 'server_created' => 'Сервер буспешно создан из панели. Пожалуйста, дайте демону несколько минут, чтобы полностью установить этот сервер.', - 'build_updated' => 'Детали сборки для этого сервера были обновлены. Некоторые изменения могут потребовать перезагрузки для вступления в силу.', - 'suspension_toggled' => 'Состояние приостановки сервера изменено на :status.', - 'rebuild_on_boot' => 'Этот сервер был помечен как требующий перестройки Docker Container. Это произойдет при следующем запуске сервера.', - 'install_toggled' => 'Состояние установки для этого сервера переключено.', - 'server_reinstalled' => 'Этот сервер поставлен в очередь для начала переустановки.', - 'details_updated' => 'Данные сервера успешно обновлены.', - 'docker_image_updated' => 'Успешно изменен образ Docker по умолчанию для использования на этом сервере. Чтобы применить это изменение, требуется перезагрузка.', - 'node_required' => 'У вас должен быть настроен хотя бы один узел, прежде чем вы сможете добавить сервер на эту панель.', - ], -]; diff --git a/resources/lang/ru/admin/user.php b/resources/lang/ru/admin/user.php deleted file mode 100644 index 6273da77..00000000 --- a/resources/lang/ru/admin/user.php +++ /dev/null @@ -1,18 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -return [ - 'exceptions' => [ - 'user_has_servers' => 'Невозможно удалить пользователя с активными серверами, подключенными к его учетной записи. Пожалуйста, удалите их серверы, прежде чем продолжить.', - ], - 'notices' => [ - 'account_created' => 'Аккаунт успешно создан.', - 'account_updated' => 'Аккаунт успешно обновлен.', - ], -]; diff --git a/resources/lang/ru/auth.php b/resources/lang/ru/auth.php deleted file mode 100644 index d14bd8cc..00000000 --- a/resources/lang/ru/auth.php +++ /dev/null @@ -1,22 +0,0 @@ - 'Вы не авторизованы для выполнения этого действия.', - 'auth_error' => 'Произошла ошибка при попытке войти.', - 'authentication_required' => 'Для продолжения требуется Аутентификация.', - 'remember_me' => 'Запомнить меня', - 'sign_in' => 'Войти в систему', - 'forgot_password' => 'Я забыл свой пароль!', - 'request_reset_text' => 'Забыли пароль учетной записи? Это не конец света, просто укажите свой адрес электронной почты.', - 'reset_password_text' => 'Сбросить пароль вашей учетной записи.', - 'reset_password' => 'Сбросить пароль учетной записи', - 'email_sent' => 'Вам отправлено электронное письмо с дальнейшими инструкциями по восстановлению пароля.', - 'failed' => 'Предоставленные учетные данные не совпадают с теми, которые есть у нас в записи, или предоставленный токен 2FA недействителен.', - 'throttle' => 'Слишком много попыток входа в систему. Пожалуйста, повторите попытку через :seconds секунд.', - 'password_requirements' => 'Пароли должны содержать как минимум одну заглавную, строчную букву, цифры и содержать не менее 8 символов.', - 'request_reset' => 'Найти аккаунт', - '2fa_required' => 'Двухфакторная аутентификация', - '2fa_failed' => 'Указанный токен 2FA недействителен.', - 'totp_failed' => 'Произошла ошибка при попытке проверить TOTP.', - '2fa_must_be_enabled' => 'Администратор потребовал, чтобы для вашей учетной записи была включена двухфакторная аутентификация, чтобы использовать панель.', -]; diff --git a/resources/lang/ru/base.php b/resources/lang/ru/base.php deleted file mode 100644 index f73f8f42..00000000 --- a/resources/lang/ru/base.php +++ /dev/null @@ -1,89 +0,0 @@ - 'Произошла ошибка с одним или несколькими полями в запросе.', - 'errors' => [ - 'return' => 'Вернуться на предыдущую страницу', - 'home' => 'Домой', - '403' => [ - 'header' => 'Запрещено', - 'desc' => 'У вас нет прав доступа к ресурсам на этом сервере.', - ], - '404' => [ - 'header' => 'Файл не найден', - 'desc' => 'Нам не удалось найти запрошенный ресурс на сервере.', - ], - 'installing' => [ - 'header' => 'Установка сервера', - 'desc' => 'Запрошенный сервер все еще завершает процесс установки. Пожалуйста, зайдите через несколько минут, вы должны получить электронное письмо, как только этот процесс будет завершен.', - ], - 'suspended' => [ - 'header' => 'Сервер приостановлен', - 'desc' => 'Этот сервер был приостановлен и недоступен.', - ], - 'maintenance' => [ - 'header' => 'Узел в обслуживании', - 'title' => 'Временно недоступен', - 'desc' => 'Этот узел находится на обслуживании, поэтому ваш сервер может быть временно недоступен.', - ], - ], - 'index' => [ - 'header' => 'Ваши серверы', - 'header_sub' => 'Серверы, к которым у вас есть доступ.', - 'list' => 'Список серверов', - ], - 'api' => [ - 'index' => [ - 'list' => 'Ваши ключи', - 'header' => 'API аккаунта', - 'header_sub' => 'Управляйте клавишами доступа, которые позволяют вам выполнять действия с панелью.', - 'create_new' => 'Создать новый ключ API', - 'keypair_created' => 'Ключ API был успешно сгенерирован и показан ниже.', - ], - 'new' => [ - 'header' => 'овый ключ API', - 'header_sub' => 'Создайте новый ключ доступа к учетной записи.', - 'form_title' => 'Подробности', - 'descriptive_memo' => [ - 'title' => 'Описание', - 'description' => 'Введите краткое описание этого ключа, которое будет полезно для справки.', - ], - 'allowed_ips' => [ - 'title' => 'Разрешенные IP-адреса', - 'description' => 'Введите разделенный строкой список IP-адресов, которым разрешен доступ к API с помощью этого ключа. Нотация CIDR разрешена. Оставьте пустым, чтобы разрешить любой IP.', - ], - ], - ], - 'account' => [ - 'details_updated' => 'Данные вашей учетной записи успешно обновлены.', - 'invalid_password' => 'Пароль для вашей учетной записи недействителен.', - 'header' => 'Ваш аккаунт', - 'header_sub' => 'Управляйте данными своей учетной записи.', - 'update_pass' => 'Обновить пароль', - 'update_email' => 'Обновить адрес электронной почты', - 'current_password' => 'Текущий пароль', - 'new_password' => 'Новый пароль', - 'new_password_again' => 'Повторите новый пароль', - 'new_email' => 'Новый E-mail адрес', - 'first_name' => 'Имя', - 'last_name' => 'Фамилия', - 'update_identity' => 'Обновить данные', - 'username_help' => 'Ваше имя пользователя должно быть уникальным для вашей учетной записи и может содержать только следующие символы: :requirements.', - 'language' => 'Язык', - ], - 'security' => [ - 'session_mgmt_disabled' => 'Ваш хост не включил возможность управлять сеансами аккаунта через этот интерфейс.', - 'header' => 'Безопасность аккаунта', - 'header_sub' => 'Контроль активных сессий и 2-х факторной аутентификации.', - 'sessions' => 'Активная сессия', - '2fa_header' => '2-х факторная аутентификация', - '2fa_token_help' => 'Введите токен 2FA, созданный вашим приложением (Google Authenticator, Authy, и так далее.).', - 'disable_2fa' => 'Отключить двухфакторную аутентификацию', - '2fa_enabled' => 'В этой учетной записи включена двухфакторная аутентификация, которая потребуется для входа в панель. Если вы хотите отключить 2FA, просто введите действительный токен ниже и отправьте форму.', - '2fa_disabled' => 'Двухфакторная аутентификация отключена на вашем аккаунте! Вы должны включить 2FA, чтобы добавить дополнительный уровень защиты для вашей учетной записи.', - 'enable_2fa' => 'Включить двухфакторную аутентификацию', - '2fa_qr' => 'Настройте 2FA на вашем устройстве', - '2fa_checkpoint_help' => 'Используйте приложение 2FA на своем телефоне, чтобы сделать снимок QR-кода, или вручную введите код под ним. После этого сгенерируйте токен и введите его ниже.', - '2fa_disable_error' => 'Указанный токен 2FA недействителен. Защита не была отключена для этой учетной записи.', - ], -]; diff --git a/resources/lang/ru/command/messages.php b/resources/lang/ru/command/messages.php deleted file mode 100644 index b4aab647..00000000 --- a/resources/lang/ru/command/messages.php +++ /dev/null @@ -1,97 +0,0 @@ - [ - 'warning' => 'Похоже, вы уже настроили ключ шифрования приложения. Продолжая этот процесс с перезаписать этот ключ и вызвать повреждение данных для любых существующих зашифрованных данных. НЕ ПРОДОЛЖАЙТЕ, ЕСЛИ ВЫ НЕ ЗНАЕТЕ, ЧТО ВЫ ДЕЛАЕТЕ.', - 'confirm' => 'Я понимаю последствия выполнения этой команды и принимаю на себя всю ответственность за потерю зашифрованных данных.', - 'final_confirm' => 'Вы уверены, что хотите продолжить? Изменение ключа шифрования приложения приведет к потере данных.', - ], - 'location' => [ - 'no_location_found' => 'Не удалось найти запись, соответствующую предоставленному короткому коду.', - 'ask_short' => 'Короткий код местоположения', - 'ask_long' => 'Описание местоположения', - 'created' => 'Успешно создано новое местоположение (:name) с идентификатором :id.', - 'deleted' => 'Успешно удалено запрошенное местоположение.', - ], - 'user' => [ - 'search_users' => 'Введите имя пользователя, UUID или адрес электронной почты', - 'select_search_user' => 'ID пользователя для удаления (Enter \'0\' to re-search)', - 'deleted' => 'Пользователь успешно удален из Панели.', - 'confirm_delete' => 'Вы уверены, что хотите удалить этого пользователя с панели?', - 'no_users_found' => 'По данному запросу не найдено ни одного пользователя.', - 'multiple_found' => 'Для предоставленного пользователя было найдено несколько учетных записей, не удалось удалить пользователя из-за --no-interaction flag.', - 'ask_admin' => 'Является ли этот пользователь администратором?', - 'ask_email' => 'Адрес электронной почты', - 'ask_username' => 'Ник пользователя', - 'ask_name_first' => 'Имя', - 'ask_name_last' => 'Фамилия', - 'ask_password' => 'Пароль', - 'ask_password_tip' => 'Если вы хотите создать учетную запись со случайным паролем, отправленным пользователю по электронной почте, повторите команду (CTRL + C) и передайте `--no-password` flag.', - 'ask_password_help' => 'Пароли должны быть длиной не менее 8 символов и содержать как минимум одну заглавную букву и цифру.', - '2fa_help_text' => [ - 'Эта команда отключит двухфакторную аутентификацию для учетной записи пользователя, если она включена. Это следует использовать только в качестве команды восстановления учетной записи, если пользователь заблокирован в своей учетной записи.', - 'Если это не то, что вы хотели сделать, нажмите CTRL + C, чтобы выйти из этого процесса.', - ], - '2fa_disabled' => 'Двухфакторная аутентификация была отключена для :email.', - ], - 'schedule' => [ - 'output_line' => 'Диспетчерская работа для первой задачи в `:schedule` (:hash).', - ], - 'maintenance' => [ - 'deleting_service_backup' => 'Удаление файла резервной копии службы :file.', - ], - 'server' => [ - 'rebuild_failed' => 'Запрос на перестроение для ":name" (#:id) на узле ":node" завершился ошибкой: :message', - 'power' => [ - 'confirm' => 'Вы собираетесь выполнить :action против :count серверов. Вы хотите продолжить?', - 'action_failed' => 'Запрос на действие питания для ":name" (#:id) на узле ":node" завершился ошибкой: :message', - ], - ], - 'environment' => [ - 'mail' => [ - 'ask_smtp_host' => 'SMTP Хост (e.g. smtp.gmail.com)', - 'ask_smtp_port' => 'SMTP Порт', - 'ask_smtp_username' => 'SMTP Пользователь', - 'ask_smtp_password' => 'SMTP Password', - 'ask_mailgun_domain' => 'Почтовый Домен', - 'ask_mailgun_secret' => 'Mailgun Secret', - 'ask_mandrill_secret' => 'Mandrill Secret', - 'ask_postmark_username' => 'Postmark API Key', - 'ask_driver' => 'Какой драйвер следует использовать для отправки писем?', - 'ask_mail_from' => 'Адреса электронной почты должны исходить от', - 'ask_mail_name' => 'Имя, с которого должны начинаться электронные письма', - 'ask_encryption' => 'Метод шифрования для использования', - ], - 'database' => [ - 'host_warning' => 'Настоятельно рекомендуется не использовать "localhost" в качестве хоста базы данных, поскольку мы часто сталкиваемся с проблемами подключения к сокету. Если вы хотите использовать локальное соединение, вы должны использовать "127.0.0.1".', - 'host' => 'Хост базы данных', - 'port' => 'Порт базы данных', - 'database' => 'Название базы данных', - 'username_warning' => 'Использование учетной записи "root" для подключений MySQL не только не одобряется, но и не допускается этим приложением. Вам нужно создать пользователя MySQL для этого программного обеспечения.', - 'username' => 'Имя пользователя базы данных', - 'password_defined' => 'Похоже, вы уже определили пароль для подключения к MySQL, хотите изменить его?', - 'password' => 'Пароль базы данных', - 'connection_error' => 'Невозможно подключиться к серверу MySQL с использованием предоставленных учетных данных. Возвращенная ошибка ":error".', - 'creds_not_saved' => 'Ваши учетные данные НЕ были сохранены. Вам нужно будет предоставить действительную информацию о соединении, прежде чем продолжить.', - 'try_again' => 'Вернуться и попробуйте снова?', - ], - 'app' => [ - 'settings' => 'Включить редактор настроек на основе интерфейса?', - 'author' => 'E-mail автора яйца', - 'author_help' => 'Укажите адрес электронной почты, с которого должны быть отправлены яйца, экспортируемые этой панелью. Это должен быть действительный адрес электронной почты.', - 'app_url_help' => 'URL приложения ДОЛЖЕН начинаться с https:// или http:// в зависимости от того, используете ли вы SSL или нет. Если вы не включите схему, ваши электронные письма и другой контент будут ссылаться на неправильное местоположение.', - 'app_url' => 'URL приложения', - 'timezone_help' => 'Часовой пояс должен соответствовать одному из поддерживаемых часовых поясов PHP. Если вы не уверены, пожалуйста перейдите по ссылке http://php.net/manual/en/timezones.php.', - 'timezone' => 'Часовой пояс приложения', - 'cache_driver' => 'Драйвер кеша', - 'session_driver' => 'Драйвер сеанса', - 'queue_driver' => 'Драйвер очереди ', - 'using_redis' => 'Вы выбрали драйвер Redis для одного или нескольких параметров. Пожалуйста, предоставьте действительную информацию о подключении ниже. В большинстве случаев вы можете использовать предоставленные значения по умолчанию, если только вы не изменили настройки.', - 'redis_host' => 'Ност Redis', - 'redis_password' => 'Пароль Redis', - 'redis_pass_help' => 'По умолчанию экземпляр сервера Redis не имеет пароля, поскольку он работает локально и недоступен для внешнего мира. Если это так, просто нажмите Enter, не вводя значение.', - 'redis_port' => 'Порт Redis', - 'redis_pass_defined' => 'Кажется, пароль для Redis уже определен, вы хотите его изменить?', - ], - ], -]; diff --git a/resources/lang/ru/exceptions.php b/resources/lang/ru/exceptions.php deleted file mode 100644 index 6c3afbb4..00000000 --- a/resources/lang/ru/exceptions.php +++ /dev/null @@ -1,68 +0,0 @@ - 'При попытке установить связь с демоном возникла исключительная ситуация, в результате которой был получен код ответа HTTP /:code. Это исключение было зарегистрировано.', - 'node' => [ - 'servers_attached' => 'Узел не должен иметь серверов, связанных с ним, для удаления.', - 'daemon_off_config_updated' => 'Конфигурация демона была обновлена, однако при попытке автоматического обновления файла конфигурации в демоне произошла ошибка. Вам нужно будет вручную обновить файл конфигурации (core.json), чтобы демон применил эти изменения.', - ], - 'allocations' => [ - 'server_using' => 'Сервер в настоящее время назначен на это распределение. Распределение может быть удалено, только если в данный момент не назначен сервер.', - 'too_many_ports' => 'Добавление более 1000 портов в одном диапазоне одновременно не поддерживается.', - 'invalid_mapping' => 'Отображение, предусмотренное для :port, было недействительным и не могло быть обработано.', - 'cidr_out_of_range' => 'Нотация CIDR допускает маски только от / 25 до / 32.', - 'port_out_of_range' => 'Порты в распределении должны быть больше 1024 и меньше или равны 65535.', - ], - 'nest' => [ - 'delete_has_servers' => 'Гнездо с подключенными к нему активными серверами нельзя удалить с панели.', - 'egg' => [ - 'delete_has_servers' => 'Яйцо с подключенными к нему активными серверами нельзя удалить с панели.', - 'invalid_copy_id' => 'Яйцо, выбранное для копирования скрипта, либо не существует, либо копирует сам скрипт.', - 'must_be_child' => 'Директива "Copy Settings From" для этого яйца должна быть дочерней для выбранного гнезда.', - 'has_children' => 'Это яйцо является родителем одного или нескольких других яиц. Пожалуйста, удалите эти яйца перед удалением этого яйца.', - ], - 'variables' => [ - 'env_not_unique' => 'Переменная окружения :name должно быть уникальным для этого яйца.', - 'reserved_name' => 'Переменная среды :name защищено и не может быть присвоено переменной.', - 'bad_validation_rule' => 'Правило проверки ":rule" не является допустимым правилом для этого приложения.', - ], - 'importer' => [ - 'json_error' => 'Произошла ошибка при попытке проанализировать файл JSON: :error.', - 'file_error' => 'Предоставленный файл JSON недействителен.', - 'invalid_json_provided' => 'Предоставленный файл JSON не в формате, который может быть распознан.', - ], - ], - 'packs' => [ - 'delete_has_servers' => 'Невозможно удалить пакет, прикрепленный к активным серверам.', - 'update_has_servers' => 'Невозможно изменить идентификатор связанной опции, когда серверы в данный момент подключены к пакету.', - 'invalid_upload' => 'Предоставленный файл не является действительным.', - 'invalid_mime' => 'Предоставленный файл не соответствует требуемому типу :type', - 'unreadable' => 'Предоставленный архив не может быть открыт сервером.', - 'zip_extraction' => 'Возникла исключительная ситуация при попытке извлечь архив, предоставленный на сервер.', - 'invalid_archive_exception' => 'В архиве пакета отсутствует требуемый файл archive.tar.gz или import.json в базовом каталоге.', - ], - 'subusers' => [ - 'editing_self' => 'Редактирование вашей учетной записи подпользователя запрещено.', - 'user_is_owner' => 'Вы не можете добавить владельца сервера в качестве подпользователя для этого сервера.', - 'subuser_exists' => 'Пользователь с таким адресом электронной почты уже назначен в качестве подпользователя для этого сервера.', - ], - 'databases' => [ - 'delete_has_databases' => 'Невозможно удалить хост-сервер базы данных, с которым связаны активные базы данных.', - ], - 'tasks' => [ - 'chain_interval_too_long' => 'Максимальный интервал времени для связанной задачи составляет 15 минут.', - ], - 'locations' => [ - 'has_nodes' => 'Невозможно удалить местоположение, к которому прикреплены активные узлы.', - ], - 'users' => [ - 'node_revocation_failed' => 'Не удалось отозвать ключи на Node #:node. :error', - ], - 'deployment' => [ - 'no_viable_nodes' => 'Узлы, удовлетворяющие требованиям, указанным для автоматического развертывания, не найдены.', - 'no_viable_allocations' => 'Местоположений, удовлетворяющих требованиям для автоматического развертывания, обнаружено не было.', - ], - 'api' => [ - 'resource_not_found' => 'Запрашиваемый ресурс не существует на этом сервере.', - ], -]; diff --git a/resources/lang/ru/navigation.php b/resources/lang/ru/navigation.php deleted file mode 100644 index a924c218..00000000 --- a/resources/lang/ru/navigation.php +++ /dev/null @@ -1,32 +0,0 @@ - 'Главная', - 'account' => [ - 'header' => 'УПРАВЛЕНИЕ АККАУНТОМ', - 'my_account' => 'Мой аккаунт', - 'security_controls' => 'Контроль безопасности', - 'api_access' => 'API аккаунта', - 'my_servers' => 'Мои серверы', - ], - 'server' => [ - 'header' => 'УПРАВЛЕНИЕ СЕРВЕРАМИ', - 'console' => 'Консоль', - 'console-pop' => 'Полноэкранная консоль', - 'file_management' => 'Управление файлами', - 'file_browser' => 'Браузер файлов', - 'create_file' => 'Создать файл', - 'upload_files' => 'Загрузить файлы', - 'subusers' => 'Подпользователь', - 'schedules' => 'Расписание', - 'configuration' => 'Конфигурация', - 'port_allocations' => 'Настройки портов', - 'sftp_settings' => 'Настройки SFTP', - 'startup_parameters' => 'Параметры запуска', - 'databases' => 'Базы данных', - 'edit_file' => 'Редактировать файл', - 'admin_header' => 'АДМИНИСТРАТИВНЫЕ', - 'admin' => 'Конфигурация сервера', - 'server_name' => 'Название сервера', - ], -]; diff --git a/resources/lang/ru/pagination.php b/resources/lang/ru/pagination.php deleted file mode 100644 index bae6004c..00000000 --- a/resources/lang/ru/pagination.php +++ /dev/null @@ -1,17 +0,0 @@ - '« Предыдущий', - 'next' => 'Следующий »', -]; diff --git a/resources/lang/ru/passwords.php b/resources/lang/ru/passwords.php deleted file mode 100644 index 129e46a6..00000000 --- a/resources/lang/ru/passwords.php +++ /dev/null @@ -1,19 +0,0 @@ - 'Пароли должны содержать не менее шести символов и соответствовать подтверждению.', - 'reset' => 'Ваш пароль сброшен!', - 'sent' => 'Мы отправили вам ссылку для сброса пароля по электронной почте!', - 'token' => 'Этот токен сброса пароля недействителен.', - 'user' => 'Мы не можем найти пользователя с таким адресом электронной почты.', -]; diff --git a/resources/lang/ru/server.php b/resources/lang/ru/server.php deleted file mode 100644 index d37bc589..00000000 --- a/resources/lang/ru/server.php +++ /dev/null @@ -1,334 +0,0 @@ - [ - 'title' => 'Просмотр сервера :name', - 'header' => 'Консоль сервера', - 'header_sub' => 'Контролируйте свой сервер в режиме реального времени.', - ], - 'schedule' => [ - 'header' => 'Диспетчер расписаний', - 'header_sub' => 'Управляйте всеми расписаниями этого сервера в одном месте.', - 'current' => 'Текущие расписания', - 'new' => [ - 'header' => 'Создать новое расписание', - 'header_sub' => 'Создайте новый набор запланированных задач для этого сервера.', - 'submit' => 'Создать расписание', - ], - 'manage' => [ - 'header' => 'Управление расписанием', - 'submit' => 'Обновить расписание', - 'delete' => 'Удалить расписание', - ], - 'task' => [ - 'time' => 'После', - 'action' => 'Выполнить действие', - 'payload' => 'С полезной нагрузкой', - 'add_more' => 'Добавить еще одну задачу', - ], - 'actions' => [ - 'command' => 'Отправить команду', - 'power' => 'Управление питанием', - ], - 'toggle' => 'Переключить статус', - 'run_now' => 'Расписание запуска', - 'schedule_created' => 'Успешно создано новое расписание для этого сервера.', - 'schedule_updated' => 'Расписание обновлено.', - 'unnamed' => 'Расписание без имени', - 'setup' => 'Настройка расписания', - 'day_of_week' => 'День недели', - 'day_of_month' => 'День месяца', - 'hour' => 'Час дня', - 'minute' => 'Минута часа', - 'time_help' => 'Система расписаний поддерживает использование синтаксиса Cronjob при определении момента начала выполнения задач. Используйте поля выше, чтобы указать, когда эти задачи должны начать выполняться, или выберите параметры в меню множественного выбора.', - 'task_help' => 'Время выполнения заданий относительно ранее определенного задания. Каждому расписанию может быть назначено не более 5 задач, а задачи не могут быть запланированы с интервалом более 15 минут.', - ], - 'tasks' => [ - 'task_created' => 'Успешно создано новое задание на панели.', - 'task_updated' => 'Задача успешно обновлена. Любые действия задачи, поставленные в очередь, будут отменены и запущены снова в следующее определенное время.', - 'header' => 'Запланированные задачи', - 'header_sub' => 'Автоматизируйте свой сервер.', - 'current' => 'Текущие запланированные задачи', - 'actions' => [ - 'command' => 'Отправить команду', - 'power' => 'Опция управленя питанием ', - ], - 'new_task' => 'Добавить новую задачу', - 'toggle' => 'Переключить статус', - 'new' => [ - 'header' => 'Новое задание', - 'header_sub' => 'Создайте новое запланированное задание для этого сервера.', - 'task_name' => 'Название задачи', - 'day_of_week' => 'День недели', - 'custom' => 'Пользовательское значение', - 'day_of_month' => 'День месяца', - 'hour' => 'Час', - 'minute' => 'Минута', - 'sun' => 'Воскресенье', - 'mon' => 'Понедельник', - 'tues' => 'Вторник', - 'wed' => 'Среда', - 'thurs' => 'Четверг', - 'fri' => 'Пятница', - 'sat' => 'Суббота', - 'submit' => 'Создать задачу', - 'type' => 'Тип задачи', - 'chain_then' => 'Затем после', - 'chain_do' => 'Выполнять', - 'chain_arguments' => 'С аргументами', - 'payload' => 'Задача Полезная нагрузка', - 'payload_help' => 'Например, если вы выбрали Send Command, введите команду здесь. Если вы выбрали Send Power Option, укажите здесь действие power (например, restart).', - ], - 'edit' => [ - 'header' => 'Управление задачей', - 'submit' => 'Обновить задачу', - ], - ], - 'users' => [ - 'header' => 'Управление пользователями', - 'header_sub' => 'Контроль, кто может получить доступ к вашему серверу.', - 'configure' => 'Настроить разрешения', - 'list' => 'Аккаунты с доступом', - 'add' => 'Добавить нового пользователя', - 'update' => 'Обновить пользователя', - 'user_assigned' => 'Успешно назначен новый пользователь на этот сервер.', - 'user_updated' => 'Успешно обновлены разрешения.', - 'edit' => [ - 'header' => 'Редактировать пользователя', - 'header_sub' => 'Изменить доступ пользователя к серверу.', - ], - 'new' => [ - 'header' => 'Добавить нового пользователя', - 'header_sub' => 'Добавьте нового пользователя с разрешениями на этот сервер.', - 'email' => 'Адрес электронной почты', - 'email_help' => 'Введите адрес электронной почты для пользователя, которого вы хотите пригласить для управления этим сервером.', - 'power_header' => 'Управление питанием', - 'file_header' => 'Управление файлами', - 'subuser_header' => 'Управление пользователями', - 'server_header' => 'Управление сервером', - 'task_header' => 'Управление расписанием', - 'database_header' => 'Управление базой данных', - 'power_start' => [ - 'title' => 'Запустить сервер', - 'description' => 'Позволяет пользователю запустить сервер.', - ], - 'power_stop' => [ - 'title' => 'Остановить сервер.', - 'description' => 'Позволяет пользователю остановить сервер.', - ], - 'power_restart' => [ - 'title' => 'Перезагрузить сервер', - 'description' => 'Позволяет пользователю перезапустить сервер.', - ], - 'power_kill' => [ - 'title' => 'Убить Сервер', - 'description' => 'Позволяет пользователю убить процесс сервера.', - ], - 'send_command' => [ - 'title' => 'Отправить консольную команду', - 'description' => 'Позволяет отправить команду из консоли. Если у пользователя нет разрешений на остановку или перезапуск, он не может отправить команду stop', - ], - 'access_sftp' => [ - 'title' => 'SFTP разрешения', - 'description' => 'Позволяет пользователю подключаться к SFTP-серверу, предоставленному демоном.', - ], - 'list_files' => [ - 'title' => 'Список файлов', - 'description' => 'Позволяет пользователю перечислять все файлы и папки на сервере, но не просматривать содержимое файла.', - ], - 'edit_files' => [ - 'title' => 'Редактировать файлы', - 'description' => 'Позволяет пользователю открыть файл только для просмотра. SFTP не влияет на это разрешение.', - ], - 'save_files' => [ - 'title' => 'Сохранить файлы', - 'description' => 'По', - ], - 'move_files' => [ - 'title' => 'Переименовать и переместить файлы', - 'description' => 'Позволяет пользователю перемещать и переименовывать файлы и папки в файловой системе.', - ], - 'copy_files' => [ - 'title' => 'Копировать файлы', - 'description' => 'Позволяет пользователю копировать файлы и папки в файловой системе.', - ], - 'compress_files' => [ - 'title' => 'Сжатие файлов', - 'description' => 'Позволяет пользователю создавать архивы файлов и папок в системе.', - ], - 'decompress_files' => [ - 'title' => 'Распаковать файлы', - 'description' => 'Позволяет пользователю распаковать архивы .zip и .tar (.gz).', - ], - 'create_files' => [ - 'title' => 'Создать файлы', - 'description' => 'Позволяет пользователю создавать новый файл в панели.', - ], - 'upload_files' => [ - 'title' => 'Загрузить файлы', - 'description' => 'Позволяет пользователю загружать файлы через файловый менеджер.', - ], - 'delete_files' => [ - 'title' => 'Удалить файлы', - 'description' => 'Позволяет пользователю удалять файлы из системы.', - ], - 'download_files' => [ - 'title' => 'Скачать файлы', - 'description' => 'Позволяет пользователю загружать файлы. Если пользователю дано это разрешение, он может загружать и просматривать содержимое файла, даже если это разрешение не назначено на панели.', - ], - 'list_subusers' => [ - 'title' => 'Список пользователей', - 'description' => 'Позволяет пользователю просматривать список всех суб-пользователей, назначенных серверу.', - ], - 'view_subuser' => [ - 'title' => 'Просмотр пользователей', - 'description' => 'Позволяет пользователю просматривать разрешения, назначенные для пользователей.', - ], - 'edit_subuser' => [ - 'title' => 'Редактировать пользователей', - 'description' => 'Позволяет пользователю редактировать разрешения, назначенные другим пользователям.', - ], - 'create_subuser' => [ - 'title' => 'Создать пользователя', - 'description' => 'Позволяет пользователю создавать дополнительных пользователей на сервере.', - ], - 'delete_subuser' => [ - 'title' => 'Удалить пользователя', - 'description' => 'Позволяет пользователю удалять других пользователей на сервере.', - ], - 'view_allocations' => [ - 'title' => 'Посмотреть распределение', - 'description' => 'Позволяет пользователю просматривать все IP-адреса и порты, назначенные серверу.', - ], - 'edit_allocation' => [ - 'title' => 'Изменить подключение по умолчанию', - 'description' => 'Позволяет пользователю изменять распределение соединения по умолчанию для использования на сервере.', - ], - 'view_startup' => [ - 'title' => 'Просмотреть команду запуска', - 'description' => 'Позволяет пользователю просматривать команду запуска и связанные переменные для сервера.', - ], - 'edit_startup' => [ - 'title' => 'Редактировать команду запуска', - 'description' => 'Позволяет пользователю изменять переменные запуска для сервера.', - ], - 'list_schedules' => [ - 'title' => 'Список расписаний', - 'description' => 'Позволяет пользователю посмотреть все расписания (включенные и отключенные) для этого сервера.', - ], - 'view_schedule' => [ - 'title' => 'Посмотреть расписание', - 'description' => 'Позволяет пользователю просматривать детали конкретного расписания, включая все назначенные задачи.', - ], - 'toggle_schedule' => [ - 'title' => 'Переключить Расписание', - 'description' => 'Позволяет пользователю переключать расписание на активные или неактивные.', - ], - 'queue_schedule' => [ - 'title' => 'Очередь Расписаний', - 'description' => 'Позволяет пользователю ставить в очередь расписание для выполнения его задач в следующем цикле процесса.', - ], - 'edit_schedule' => [ - 'title' => 'Изменить расписание', - 'description' => 'Позволяет пользователю редактировать расписание, включая все задачи расписания. Это позволит пользователю удалять отдельные задачи, но не удалять само расписание.', - ], - 'create_schedule' => [ - 'title' => 'Создать расписание', - 'description' => 'Позволяет пользователю создавать новое расписание.', - ], - 'delete_schedule' => [ - 'title' => 'Удалить расписание', - 'description' => 'Позволяет пользователю удалить расписание с сервера.', - ], - 'view_databases' => [ - 'title' => 'Просмотр базы данных', - 'description' => 'Позволяет пользователю просматривать все базы данных, связанные с этим сервером, включая имена пользователей и пароли для баз данных.', - ], - 'reset_db_password' => [ - 'title' => 'Сбросить пароль базы данных', - 'description' => 'Позволяет пользователю сбросить пароли для баз данных.', - ], - 'delete_database' => [ - 'title' => 'Удалить базы данных', - 'description' => 'Позволяет пользователю удалять базы данных для этого сервера.', - ], - 'create_database' => [ - 'title' => 'Создать базу данных', - 'description' => 'Позволяет пользователю создавать дополнительные базы данных для этого сервера.', - ], - ], - ], - 'allocations' => [ - 'mass_actions' => 'Массовые акции', - 'delete' => 'Удалить распределение', - ], - 'files' => [ - 'exceptions' => [ - 'invalid_mime' => 'Этот тип файла не может быть отредактирован через встроенный редактор.', - 'max_size' => 'Этот файл слишком велик для редактирования через встроенный редактор.', - ], - 'header' => 'Файловый менеджер', - 'header_sub' => 'Управляйте всеми своими файлами прямо из Интернета.', - 'loading' => 'Загрузка исходной файловой структуры, это может занять несколько секунд.', - 'path' => 'При настройке любых путей к файлам в плагинах или настройках вашего сервера вы должны использовать :path в качестве базового пути. Максимальный размер загрузки файлов через Интернет на этот узел: :size.', - 'seconds_ago' => 'секунд назад', - 'file_name' => 'Имя файла', - 'size' => 'Размер', - 'last_modified' => 'Последнее изменение', - 'add_new' => 'Добавить новый файл', - 'add_folder' => 'Добавить новую папку', - 'mass_actions' => 'Массовые акции', - 'delete' => 'Удалить файлы', - 'edit' => [ - 'header' => 'Редактировать файл', - 'header_sub' => 'Внесите изменения в файл из Интернета.', - 'save' => 'Сохранить файл', - 'return' => 'Вернуться в файловый менеджер', - ], - 'add' => [ - 'header' => 'Новый файл', - 'header_sub' => 'Создайте новый файл на вашем сервере.', - 'name' => 'Имя файла', - 'create' => 'Создать файл', - ], - ], - 'config' => [ - 'name' => [ - 'header' => 'Название сервера', - 'header_sub' => 'Измените имя этого сервера.', - 'details' => 'Имя сервера является только ссылкой на этот сервер на панели и не влияет на какие-либо конкретные конфигурации серверов, которые могут отображаться для пользователей в играх.', - ], - 'startup' => [ - 'header' => 'Начать настройку', - 'header_sub' => 'Управляйте аргументами запуска сервера.', - 'command' => 'Команда запуска', - 'edit_params' => 'Изменить параметры', - 'update' => 'Обновить параметры запуска', - 'startup_regex' => 'Правила ввода', - 'edited' => 'Переменные запуска были успешно отредактированы. Они вступят в силу при следующем запуске этого сервера.', - ], - 'sftp' => [ - 'header' => 'Конфигурация SFTP', - 'header_sub' => 'Данные учетной записи для SFTP-соединений.', - 'details' => 'Подробности SFTP', - 'conn_addr' => 'Адрес подключения', - 'warning' => 'Пароль SFTP - это пароль вашей учетной записи. Убедитесь, что ваш клиент настроен на использование SFTP, а не FTP или FTPS для соединений, между протоколами есть разница.', - ], - 'database' => [ - 'header' => 'Базы данных', - 'header_sub' => 'Все базы данных доступные для этого сервера.', - 'your_dbs' => 'Настройки базы данных', - 'host' => 'MySQL Хост', - 'reset_password' => 'Сброс пароля', - 'no_dbs' => 'Для этого сервера нет баз данных.', - 'add_db' => 'Добавить новую базу данных.', - ], - 'allocation' => [ - 'header' => 'Порты сервера', - 'header_sub' => 'Управляйте IP-адресами и портами, доступными на этом сервере.', - 'available' => 'Доступные Распределения', - 'help' => 'Справка по Распределению портов.', - 'help_text' => 'Список включает в себя все доступные IP-адреса и порты, которые открыты для вашего сервера, чтобы использовать для входящих подключений.', - ], - ], -]; diff --git a/resources/lang/ru/strings.php b/resources/lang/ru/strings.php deleted file mode 100644 index 0dc9ff55..00000000 --- a/resources/lang/ru/strings.php +++ /dev/null @@ -1,88 +0,0 @@ - 'Эл. адрес', - 'user_identifier' => 'Имя пользователя или адрес электронной почты', - 'password' => 'Пароль', - 'confirm_password' => 'Подтвердите Пароль', - 'login' => 'Войти', - 'home' => 'Домой', - 'servers' => 'Серверы', - 'id' => 'ID', - 'name' => 'Название', - 'node' => 'Узел', - 'connection' => 'Подключение', - 'memory' => 'Память', - 'cpu' => 'Процессор', - 'status' => 'Статус', - 'search' => 'Поиск', - 'suspended' => 'Приостановленный', - 'account' => 'Аккаунт', - 'security' => 'Безопасность', - 'ip' => 'IP адрес', - 'last_activity' => 'Последняя активность', - 'revoke' => 'Отменить', - '2fa_token' => 'Токен аутентификации', - 'submit' => 'Отправить', - 'close' => 'Закрыть', - 'settings' => 'Настройки', - 'configuration' => 'Конфигурация', - 'sftp' => 'SFTP', - 'databases' => 'База данных', - 'memo' => 'Напоминание', - 'created' => 'Созданный', - 'expires' => 'Истекает', - 'public_key' => 'Токен', - 'api_access' => 'Доступ к API', - 'never' => 'никогда', - 'sign_out' => 'Выход', - 'admin_control' => 'Админ панель', - 'required' => 'Необходимо', - 'port' => 'Порт', - 'username' => 'Имя пользователя', - 'database' => 'База данных', - 'new' => 'Новый', - 'danger' => 'Опасность', - 'create' => 'Создать', - 'select_all' => 'Выбрать все', - 'select_none' => 'Выберат ни одного', - 'alias' => 'Псевдоним', - 'primary' => 'Основной', - 'make_primary' => 'Сделать основным', - 'none' => 'Никто', - 'cancel' => 'Отмена', - 'created_at' => 'Создан в', - 'action' => 'Действие', - 'data' => 'Дата', - 'queued' => 'Очередь', - 'last_run' => 'Последний запуск', - 'next_run' => 'Следующий запуск', - 'not_run_yet' => 'Еще не работает', - 'yes' => 'Да', - 'no' => 'Нет', - 'delete' => 'Удалить', - '2fa' => '2FA', - 'logout' => 'Выйти', - 'admin_cp' => 'Панель управления администратора', - 'optional' => 'Опциональный', - 'read_only' => 'Только чтение', - 'relation' => 'Зависимость', - 'owner' => 'Владелец', - 'admin' => 'Администратор', - 'subuser' => 'Доп-пользователь', - 'captcha_invalid' => 'Капча недействительна.', - 'tasks' => 'Задачи', - 'seconds' => 'Секунд', - 'minutes' => 'Минут', - 'under_maintenance' => 'На техобслуживании', - 'days' => [ - 'sun' => 'Воскресенье', - 'mon' => 'Понедельник', - 'tues' => 'Вторник', - 'wed' => 'Среда', - 'thurs' => 'Черверг', - 'fri' => 'Пятница', - 'sat' => 'Суббота', - ], - 'last_used' => 'Последний раз был использован', -]; diff --git a/resources/lang/ru/validation.php b/resources/lang/ru/validation.php deleted file mode 100644 index 9baf6c96..00000000 --- a/resources/lang/ru/validation.php +++ /dev/null @@ -1,105 +0,0 @@ - ':attribute должен быть принят.', - 'active_url' => ':attribute не является допустимым URL.', - 'after' => ':attribute должен быть датой после :date.', - 'after_or_equal' => ':attribute должен быть датой после или равен :date.', - 'alpha' => ':attribute может содержать только буквы.', - 'alpha_dash' => ':attribute может содержать только буквы, цифры и тире.', - 'alpha_num' => ':attribute может содержать только буквы и цифры.', - 'array' => ':attribute должен быть массивом.', - 'before' => ':attribute должен быть датой до :date.', - 'before_or_equal' => ':attribute должен быть датой до или равен :date.', - 'between' => [ - 'numeric' => ':attribute должен быть между :min и :max.', - 'file' => ':attribute должен быть между :min и :max килобайт.', - 'string' => ':attribute должен быть между :min и :max символами.', - 'array' => ':attribute должен содержать от :min до :max элементов.', - ], - 'boolean' => ':attribute должно быть true или false.', - 'confirmed' => ':attribute Подтверждение не совпадает.', - 'date' => ':attribute не является допустимой датой.', - 'date_format' => ':attribute не соответствует формату :format.', - 'different' => ':attribute и :other должен быть другим.', - 'digits' => ':attribute должен быть :digits цифровым.', - 'digits_between' => ':attribute должен быть между :min и :max цифрами.', - 'dimensions' => ':attribute имеет недопустимые размеры изображения.', - 'distinct' => ':attribute имеет повторяющееся значение.', - 'email' => ':attribute должен быть действительным адресом электронной почты.', - 'exists' => 'выбранный :attribute недействителен.', - 'file' => ':attribute должен быть файлом.', - 'filled' => 'поле :attribute обязательно для заполнения.', - 'image' => ':attribute должен быть изображением.', - 'in' => 'выбранный :attribute недействителен.', - 'in_array' => 'поле :attribute не существует в :other.', - 'integer' => ':attribute должен быть целым числом.', - 'ip' => ':attribute должен быть действительным IP-адресом.', - 'json' => ':attribute должен быть допустимой строкой JSON.', - 'max' => [ - 'numeric' => ':attribute не может быть больше чем :max.', - 'file' => ':attribute не может быть больше, чем :max килобайт.', - 'string' => ':attribute не может быть больше, чем :max символов.', - 'array' => ':attribute может содержать не более :max предметов.', - ], - 'mimes' => ':attribute должен быть файл типа: :values.', - 'mimetypes' => ':attribute должен быть файл типа: :values.', - 'min' => [ - 'numeric' => ':attribute должен быть не менее :min.', - 'file' => ':attribute должно быть не менее :min килобайт.', - 'string' => ':attribute должно быть не менее :min символов.', - 'array' => ':attribute должно иметь как минимум :min предметов.', - ], - 'not_in' => 'выбранный :attribute недействителен.', - 'numeric' => ':attribute должен быть числом.', - 'present' => ':attribute поле должно присутствовать.', - 'regex' => ':attribute Формат неверен.', - 'required' => ':attribute Поле, обязательное для заполнения.', - 'required_if' => ':attribute Поле обязательно для заполнения, когда :other является :value.', - 'required_unless' => ':attribute Поле обязательно для заполнения, если :other не находится в :values.', - 'required_with' => ':attribute Поле обязательно для заполнения, когда :values присутствуют.', - 'required_with_all' => ':attribute Поле обязательно для заполнения, когда :values присутствуют.', - 'required_without' => ':attribute Поле обязательно для заполнения, когда :values отсутствуют.', - 'required_without_all' => ':attribute поле обязательно для заполнения, когда нет ни одного из :values.', - 'same' => ':attribute и :other должены совпадать.', - 'size' => [ - 'numeric' => ':attribute должно быть :size.', - 'file' => ':attribute должно быть :size килобайт.', - 'string' => ':attribute должно быть :size символов.', - 'array' => ':attribute должен содержать :size предметов.', - ], - 'string' => ':attribute должен быть строкой.', - 'timezone' => ':attribute должна быть действительной зоной.', - 'unique' => ':attribute уже занят.', - 'uploaded' => ':attribute не удалось загрузить.', - 'url' => ':attribute Формат неверен.', - - /* - |-------------------------------------------------------------------------- - | Custom Validation Attributes - |-------------------------------------------------------------------------- - | - | following language lines are used to swap attribute place-holders - | with something more reader friendly such as E-Mail Address instead - | of "email". This simply helps us make messages a little cleaner. - | - */ - - 'attributes' => [], - - // Internal validation logic for Pterodactyl - 'internal' => [ - 'variable_value' => ':env переменная', - ], -]; diff --git a/resources/lang/tr/auth.php b/resources/lang/tr/auth.php deleted file mode 100644 index 70771d37..00000000 --- a/resources/lang/tr/auth.php +++ /dev/null @@ -1,23 +0,0 @@ - '2-Aşamalı Doğrulama kodu yanlış.', - '2fa_must_be_enabled' => 'Paneli kullanabilmeniz için hesabınızda 2-Aşamalı Doğrulama aktif olmalıdır.', - '2fa_required' => '2-Aşamalı Doğrulama', - 'authentication_required' => 'Devam etmek için doğrulama gerekli.', - 'auth_error' => 'Giriş yaparken bir hata meydana geldi.', - 'email_sent' => 'Hesap şifrenizi sıfırlamayla alakalı detaylar eposta adresinize gönderildi.', - 'failed' => 'Girdiğiniz bilgiler hatalı ya da 2-Aşamalı Doğrulama kodunuz yanlış.', - 'forgot_password' => 'Şifremi unuttum!', - 'not_authorized' => 'Bunu yapmak için yeterli yetkiniz yok.', - 'password_requirements' => 'Şifreniz en az 8 karakter uzunlukta olmalı ve içerisinde en az 1 büyük, küçük, nümerik karakter bulundurmak zorunda.', - 'remember_me' => 'Beni Hatırla', - 'request_reset' => 'Hesabımı Bul', - 'request_reset_text' => 'Hesabınızın şifresini mi unuttunuz? Dünyanın sonu değil, eposta adresinizi girin yeter.', - 'reset_password' => 'Hesap Şifresi Sıfırla', - 'reset_password_text' => 'Hesap şifrenizi sıfırlayın.', - 'sendlink' => 'Şifre Sıfırlama Linki Gönder', - 'sign_in' => 'GİRİŞ', - 'throttle' => 'Çok fazla giriş denemesi. Lütfen :seconds saniye içerisinde tekrar deneyin.', - 'totp_failed' => 'There was an error while attempting to validate TOTP.', -]; diff --git a/resources/lang/tr/base.php b/resources/lang/tr/base.php deleted file mode 100644 index c0ac8fd8..00000000 --- a/resources/lang/tr/base.php +++ /dev/null @@ -1,145 +0,0 @@ - [ - 'current_password' => 'Mevcut Şifre', - 'delete_user' => 'Kullanıcıyı Sil', - 'details_updated' => 'Hesap ayarlarınız başarıyla güncellendi.', - 'exception' => 'Hesabınız güncellenirken bir hata oluştu.', - 'first_name' => 'İsim', - 'header' => 'HESAP AYARLARI', - 'header_sub' => 'Hesap ayarlarınızı düzenleyin.', - 'invalid_password' => 'Girdiğiniz şifre doğru değil.', - 'last_name' => 'Soy İsim', - 'new_email' => 'Yeni Eposta Adresi', - 'new_password' => 'Yeni Şifre', - 'new_password_again' => 'Tekrar Yeni Şifre', - 'totp_disable' => '2-Aşamalı Doğrulamayı Devre Dışı Bırak', - 'totp_enable' => '2-Aşamalı Doğrulamayı Etkinleştir', - 'totp_enabled_error' => 'Tek seferlik doğrulama kodunuz yanlış. Lütfen sonra tekrar deneyin.', - 'totp_enable_help' => 'Görünüşe göre 2-Aşamalı doğrulama devre dışı. Bu doğrulama metodu, hesabınıza yetkisiz girişleri engellemek için ek bir önlem oluşturur. Eğer etkinleştirirseniz, hesabınıza bağlanırken telefonunuzda veya tek seferlik doğrulama kodu destekleyen bir cihazda oluşturulan kodu girmeniz gerekecektir.', - 'update_email' => 'Güncelle', - 'update_identitity' => 'Güncelle', - 'update_pass' => 'Güncelle', - 'update_user' => 'Güncelle', - 'username_help' => 'Kullanıcı adınız hesabınıza özgün olmalı ve belirtilen karakterleri barındırmalıdır. :requirements.', - ], - 'api' => [ - 'index' => [ - 'create_new' => 'Yeni Yetki Oluştur', - 'header' => 'Yetki Paylaşımı', - 'header_sub' => 'Yetki anahtarlarınızı düzenleyin', - 'keypair_created' => 'Yetki Anahtarı başarıyla oluşturuldu ve listelendi.', - 'list' => 'Yetki Anahtarlarınız', - ], - 'new' => [ - 'allowed_ips' => [ - 'description' => 'Enter a line delimitated list of IPs that are allowed to access the API using this key. CIDR notation is allowed. Leave blank to allow any IP.', - 'title' => 'Allowed IPs', - ], - 'base' => [ - 'information' => [ - 'title' => 'Ana Bilgileri Görüntüleme', - ], - 'title' => 'Ana Bilgiler', - ], - 'descriptive_memo' => [ - 'description' => 'Enter a brief description of this key that will be useful for reference.', - 'title' => 'Description', - ], - 'form_title' => 'Details', - 'header' => 'Yeni Yetki Anahtarı', - 'header_sub' => 'Create a new account access key.', - 'location_management' => [ - 'list' => [ - 'title' => 'Lokasyonları listele', - ], - 'title' => 'Lokasyon yönetimi', - ], - 'server_management' => [ - 'config' => [ - 'title' => 'Konfigürasyon Güncelleme', - ], - 'create' => [ - 'title' => 'Sunucu Oluşturma', - ], - 'delete' => [ - 'title' => 'Sunucuyu Silme', - ], - 'list' => [ - 'title' => 'Sunucuları Listeleme', - ], - 'server' => [ - 'title' => 'Sunucu Bilgisi', - ], - 'title' => 'Sunucu Yönetimi', - ], - 'service_management' => [ - 'title' => 'Servis Yönetimi', - ], - 'user_management' => [ - 'create' => [ - 'description' => 'Sistemde yeni bir kullanıcı oluşturulmasına izin verir.', - 'title' => 'Kullanıcı Oluşturma', - ], - 'delete' => [ - 'title' => 'Kullanıcı Silme', - ], - 'list' => [ - 'description' => 'Sistemdeki kullanıcıların listelenmesine izin verir.', - 'title' => 'Kullanıcıları Listeleme', - ], - 'title' => 'Kullanıcı Yönetimi', - 'update' => [ - 'description' => 'Kullanıcı detaylarının değiştirilmesini sağlar. (E-posta, şifre)', - 'title' => 'Kullanıcı Güncelleme', - ], - ], - ], - ], - 'confirm' => 'Emin misin?', - 'errors' => [ - '403' => [ - 'desc' => 'Bu sunucudaki kaynaklara ulaşma yetkiniz yok.', - 'header' => 'Yasak', - ], - '404' => [ - 'desc' => 'We were unable to locate the requested resource on the server.', - 'header' => 'Dosya Bulunamadı', - ], - 'home' => 'Anasayfaya Dön', - 'installing' => [ - 'desc' => 'The requested server is still completing the install process. Please check back in a few minutes, you should receive an email as soon as this process is completed.', - 'header' => 'Sunucu Yükleniyor', - ], - 'return' => 'Önceki Sayfaya Dön', - 'suspended' => [ - 'desc' => 'This server has been suspended and cannot be accessed.', - 'header' => 'Sunucu Askıya Alındı', - ], - ], - 'form_error' => 'Bu isteği işlerken aşağıdaki hatayi karşılaşıldı.', - 'index' => [ - 'header' => 'Server Console', - 'header_sub' => 'Control your server in real time.', - 'list' => 'Sunucu Listesi', - ], - 'security' => [ - '2fa_checkpoint_help' => 'Use the 2FA application on your phone to take a picture of the QR code to the left, or manually enter the code under it. Once you have done so, generate a token and enter it below.', - '2fa_disabled' => '2-Factor Authentication is disabled on your account! You should enable 2FA in order to add an extra level of protection on your account.', - '2fa_disable_error' => 'The 2FA token provided was not valid. Protection has not been disabled for this account.', - '2fa_enabled' => '2-Factor Authentication is enabled on this account and will be required in order to login to the panel. If you would like to disable 2FA, simply enter a valid token below and submit the form.', - '2fa_header' => '2-Aşamalı Doğrulama', - '2fa_qr' => 'Confgure 2FA on Your Device', - '2fa_token_help' => 'Uygulamanız tarafından oluşturulan 2AD kodunuzu girin (Google Doğrulama, Authy, vs.).', - 'disable_2fa' => 'Disable 2-Factor Authentication', - 'enable_2fa' => 'Enable 2-Factor Authentication', - 'header' => 'Hesap Güvenliği', - 'header_sub' => 'Control active sessions and 2-Factor Authentication.', - 'sessions' => 'Aktif Oturumlar', - 'session_mgmt_disabled' => 'Your host has not enabled the ability to manage account sessions via this interface.', - ], - 'server_name' => 'Sunucu Adı', - 'validation_error' => 'There was an error with one or more fields in the request.', - 'view_as_admin' => 'Sunucu listesini yönetici olarak görüyorsunuz. Bu sebeple, sistemde kurulu bütün sunucular gösteriliyor. Size ait olarak belirlenmiş sunucular, isimlerinin solunda mavi bir nokta ile işaretlendi.', -]; diff --git a/resources/lang/tr/pagination.php b/resources/lang/tr/pagination.php deleted file mode 100644 index 8e20419e..00000000 --- a/resources/lang/tr/pagination.php +++ /dev/null @@ -1,6 +0,0 @@ - 'Next »', - 'previous' => '« Previous', -]; diff --git a/resources/lang/tr/passwords.php b/resources/lang/tr/passwords.php deleted file mode 100644 index 3acb4f6d..00000000 --- a/resources/lang/tr/passwords.php +++ /dev/null @@ -1,9 +0,0 @@ - 'Şifre', - 'reset' => 'Your password has been reset!', - 'sent' => 'We have e-mailed your password reset link!', - 'token' => 'This password reset token is invalid.', - 'user' => "We can't find a user with that e-mail address.", -]; diff --git a/resources/lang/tr/server.php b/resources/lang/tr/server.php deleted file mode 100644 index a9f6fed3..00000000 --- a/resources/lang/tr/server.php +++ /dev/null @@ -1,346 +0,0 @@ - [ - 'allocation' => [ - 'available' => 'Available Allocations', - 'header' => 'Server Allocations', - 'header_sub' => 'Control the IPs and ports available on this server.', - 'help' => 'Allocation Help', - 'help_text' => 'The list to the left includes all available IPs and ports that are open for your server to use for incoming connections.', - ], - 'database' => [ - 'add_db' => 'Add a new database.', - 'header' => 'Databases', - 'header_sub' => 'All databases available for this server.', - 'host' => 'MySQL Host', - 'no_dbs' => 'There are no databases listed for this server.', - 'reset_password' => 'Reset Password', - 'your_dbs' => 'Configured Databases', - ], - 'name' => [ - 'details' => 'The server name is only a reference to this server on the panel, and will not affect any server specific configurations that may display to users in games.', - 'header' => 'Server Name', - 'header_sub' => "Change this server's name.", - ], - 'sftp' => [ - 'conn_addr' => 'Connection Address', - 'details' => 'SFTP Details', - 'header' => 'SFTP Configuration', - 'header_sub' => 'Account details for SFTP connections.', - 'warning' => 'The SFTP password is your account password. Ensure that your client is set to use SFTP and not FTP or FTPS for connections, there is a difference between the protocols.', - ], - 'startup' => [ - 'command' => 'Startup Command', - 'edited' => 'Startup variables have been successfully edited. They will take effect the next time this server is started.', - 'edit_params' => 'Edit Parameters', - 'header' => 'Start Configuration', - 'header_sub' => 'Control server startup arguments.', - 'startup_regex' => 'Input Rules', - 'update' => 'Update Startup Parameters', - ], - ], - 'files' => [ - 'add' => [ - 'create' => 'Create File', - 'header' => 'New File', - 'header_sub' => 'Create a new file on your server.', - 'name' => 'File Name', - ], - 'add_folder' => 'Add New Folder', - 'add_new' => 'Add New File', - 'delete' => 'Delete Files', - 'edit' => [ - 'header' => 'Edit File', - 'header_sub' => 'Make modifications to a file from the web.', - 'return' => 'Return to File Manager', - 'save' => 'Save File', - ], - 'exceptions' => [ - 'invalid_mime' => "This type of file cannot be edited via the Panel's built-in editor.", - 'max_size' => "This file is too large to edit via the Panel's built-in editor.", - ], - 'file_name' => 'File Name', - 'header' => 'File Manager', - 'header_sub' => 'Manage all of your files directly from the web.', - 'last_modified' => 'Last Modified', - 'loading' => 'Loading initial file structure, this could take a few seconds.', - 'mass_actions' => 'Mass Actions', - 'path' => 'When configuring any file paths in your server plugins or settings you should use :path as your base path. The maximum size for web-based file uploads to this node is :size.', - 'saved' => 'Dosya basarli kaydedildi.', - 'seconds_ago' => 'seconds ago', - 'size' => 'Size', - ], - 'index' => [ - 'add_new' => 'Yeni Sunucu Ekli.', - 'disk_space' => 'Disk Alani', - 'header' => 'Server Console', - 'header_sub' => 'Control your server in real time.', - 'title' => 'Viewing Server :name', - ], - 'schedule' => [ - 'actions' => [ - 'command' => 'Send Command', - 'power' => 'Power Action', - ], - 'current' => 'Current Schedules', - 'day_of_month' => 'Day of Month', - 'day_of_week' => 'Day of Week', - 'header' => 'Schedule Manager', - 'header_sub' => "Manage all of this server's schedules in one place.", - 'hour' => 'Hour of Day', - 'manage' => [ - 'delete' => 'Delete Schedule', - 'header' => 'Manage Schedule', - 'submit' => 'Update Schedule', - ], - 'minute' => 'Minute of Hour', - 'new' => [ - 'header' => 'Create New Schedule', - 'header_sub' => 'Create a new set of scheduled tasks for this server.', - 'submit' => 'Create Schedule', - ], - 'run_now' => 'Trigger Schedule', - 'schedule_created' => 'Successfully created a new schedule for this server.', - 'schedule_updated' => 'Schedule has been updated.', - 'setup' => 'Schedule Setup', - 'task' => [ - 'action' => 'Perform Action', - 'add_more' => 'Add Another Task', - 'payload' => 'With Payload', - 'time' => 'After', - ], - 'task_help' => 'Times for tasks are relative to the previously defined task. Each schedule may have no more than 5 tasks assigned to it and tasks may not be scheduled more than 15 minutes apart.', - 'time_help' => 'The schedule system supports the use of Cronjob syntax when defining when tasks should begin running. Use the fields above to specify when these tasks should begin running or select options from the multiple select menus.', - 'toggle' => 'Toggle Status', - 'unnamed' => 'Unnamed Schedule', - ], - 'tasks' => [ - 'actions' => [ - 'command' => 'Send Command', - 'power' => 'Send Power Option', - ], - 'current' => 'Current Scheduled Tasks', - 'edit' => [ - 'header' => 'Manage Task', - 'submit' => 'Update Task', - ], - 'header' => 'Scheduled Tasks', - 'header_sub' => 'Automate your server.', - 'new' => [ - 'chain_arguments' => 'With Arguments', - 'chain_do' => 'Do', - 'chain_then' => 'Then, After', - 'custom' => 'Custom Value', - 'day_of_month' => 'Day of Month', - 'day_of_week' => 'Day of Week', - 'fri' => 'Friday', - 'header' => 'New Task', - 'header_sub' => 'Create a new scheduled task for this server.', - 'hour' => 'Hour', - 'minute' => 'Minute', - 'mon' => 'Monday', - 'payload' => 'Task Payload', - 'payload_help' => 'For example, if you selected Send Command enter the command here. If you selected Send Power Option put the power action here (e.g. restart).', - 'sat' => 'Saturday', - 'submit' => 'Create Task', - 'sun' => 'Sunday', - 'task_name' => 'Task Name', - 'thurs' => 'Thursday', - 'tues' => 'Tuesday', - 'type' => 'Task Type', - 'wed' => 'Wednesday', - ], - 'new_task' => 'Add New Task', - 'task_created' => 'Successfully created a new task on the Panel.', - 'task_updated' => 'Task has successfully been updated. Any currently queued task actions will be cancelled and run again at the next defined time.', - 'toggle' => 'Toggle Status', - ], - 'users' => [ - 'add' => 'Add New Subuser', - 'configure' => 'Configure Permissions', - 'edit' => [ - 'header' => 'Edit Subuser', - 'header_sub' => "Modify user's access to server.", - ], - 'header' => 'Manage Users', - 'header_sub' => 'Control who can access your server.', - 'list' => 'Accounts with Access', - 'new' => [ - 'access_sftp' => [ - 'description' => 'Allows user to connect to the SFTP server provided by the daemon.', - 'title' => 'SFTP Allowed', - ], - 'compress_files' => [ - 'description' => 'Allows user to make archives of files and folders on the system.', - 'title' => 'Compress Files', - ], - 'copy_files' => [ - 'description' => 'Allows user to copy files and folders on the filesystem.', - 'title' => 'Copy Files', - ], - 'create_database' => [ - 'description' => 'Allows a user to create additional databases for this server.', - 'title' => 'Create Database', - ], - 'create_files' => [ - 'description' => 'Allows user to create a new file within the panel.', - 'title' => 'Create Files', - ], - 'create_schedule' => [ - 'description' => 'Allows a user to create a new schedule.', - 'title' => 'Create Schedule', - ], - 'create_subuser' => [ - 'description' => 'Allows user to create additional subusers on the server.', - 'title' => 'Create Subuser', - ], - 'database_header' => 'Database Management', - 'db_header' => 'Veritabanı Yönetimi', - 'decompress_files' => [ - 'description' => 'Allows user to decompress .zip and .tar(.gz) archives.', - 'title' => 'Decompress Files', - ], - 'delete_database' => [ - 'description' => 'Allows a user to delete databases for this server from the Panel.', - 'title' => 'Delete Databases', - ], - 'delete_files' => [ - 'description' => 'Allows user to delete files from the system.', - 'title' => 'Delete Files', - ], - 'delete_schedule' => [ - 'description' => 'Allows a user to delete a schedule from the server.', - 'title' => 'Delete Schedule', - ], - 'delete_subuser' => [ - 'description' => 'Allows a user to delete other subusers on the server.', - 'title' => 'Delete Subuser', - ], - 'download_files' => [ - 'description' => 'Allows user to download files. If a user is given this permission they can download and view file contents even if that permission is not assigned on the panel.', - 'title' => 'Download Files', - ], - 'edit_allocation' => [ - 'description' => 'Allows user to change the default connection allocation to use for a server.', - 'title' => 'Edit Default Connection', - ], - 'edit_files' => [ - 'description' => 'Allows user to open a file for viewing only. SFTP is not effected by this permission.', - 'title' => 'Edit Files', - ], - 'edit_schedule' => [ - 'description' => "Allows a user to edit a schedule including all of the schedule's tasks. This will allow the user to remove individual tasks, but not delete the schedule itself.", - 'title' => 'Edit Schedule', - ], - 'edit_startup' => [ - 'description' => 'Allows a user to modify startup variables for a server.', - 'title' => 'Edit Startup Command', - ], - 'edit_subuser' => [ - 'description' => 'Allows a user to edit permissions assigned to other subusers.', - 'title' => 'Edit Subuser', - ], - 'email' => 'Email Address', - 'email_help' => 'Enter the email address for the user you wish to invite to manage this server.', - 'file_header' => 'File Management', - 'header' => 'Add New User', - 'header_sub' => 'Add a new user with permissions to this server.', - 'list_files' => [ - 'description' => 'Allows user to list all files and folders on the server but not view file contents.', - 'title' => 'List Files', - ], - 'list_schedules' => [ - 'description' => 'Allows a user to list all schedules (enabled and disabled) for this server.', - 'title' => 'List Schedules', - ], - 'list_subusers' => [ - 'description' => 'Allows user to view a listing of all subusers assigned to the server.', - 'title' => 'List Subusers', - ], - 'move_files' => [ - 'description' => 'Allows user to move and rename files and folders on the filesystem.', - 'title' => 'Rename & Move Files', - ], - 'power_header' => 'Power Management', - 'power_kill' => [ - 'description' => 'Allows user to kill the server process.', - 'title' => 'Kill Server', - ], - 'power_restart' => [ - 'description' => 'Allows user to restart the server.', - 'title' => 'Restart Server', - ], - 'power_start' => [ - 'description' => 'Allows user to start the server.', - 'title' => 'Start Server', - ], - 'power_stop' => [ - 'description' => 'Allows user to stop the server.', - 'title' => 'Stop Server', - ], - 'queue_schedule' => [ - 'description' => "Allows a user to queue a schedule to run it's tasks on the next process cycle.", - 'title' => 'Queue Schedule', - ], - 'reset_db_password' => [ - 'description' => 'Allows a user to reset passwords for databases.', - 'title' => 'Reset Database Password', - ], - 'reset_sftp' => [ - 'title' => 'SFTP Şifresini Sıfırla', - ], - 'restart' => [ - 'title' => 'Sunucuyu Yeniden Başlat', - ], - 'save_files' => [ - 'description' => 'Allows user to save modified file contents. SFTP is not effected by this permission.', - 'title' => 'Save Files', - ], - 'send_command' => [ - 'description' => "Allows sending a command from the console. If the user does not have stop or restart permissions they cannot send the application's stop command.", - 'title' => 'Send Console Command', - ], - 'server_header' => 'Server Management', - 'subuser_header' => 'Subuser Management', - 'task_header' => 'Schedule Management', - 'toggle_schedule' => [ - 'description' => 'Allows a user to toggle a schedule to be active or inactive.', - 'title' => 'Toggle Schedule', - ], - 'upload_files' => [ - 'description' => 'Allows user to upload files through the file manager.', - 'title' => 'Upload Files', - ], - 'view_allocations' => [ - 'description' => 'Allows user to view all of the IPs and ports assigned to a server.', - 'title' => 'View Allocations', - ], - 'view_databases' => [ - 'description' => 'Allows user to view all databases associated with this server including the usernames and passwords for the databases.', - 'title' => 'View Database Details', - ], - 'view_schedule' => [ - 'description' => "Allows a user to view a specific schedule's details including all of the assigned tasks.", - 'title' => 'View Schedule', - ], - 'view_sftp' => [ - 'title' => 'SFTP Detaylarını Göster', - ], - 'view_sftp_password' => [ - 'title' => 'SFTP Şifresini Göster', - ], - 'view_startup' => [ - 'description' => 'Allows user to view the startup command and associated variables for a server.', - 'title' => 'View Startup Command', - ], - 'view_subuser' => [ - 'description' => 'Allows user to view permissions assigned to subusers.', - 'title' => 'View Subuser', - ], - ], - 'update' => 'Update Subuser', - 'user_assigned' => 'Successfully assigned a new subuser to this server.', - 'user_updated' => 'Successfully updated permissions.', - ], -]; diff --git a/resources/lang/tr/strings.php b/resources/lang/tr/strings.php deleted file mode 100644 index 95ab7d6a..00000000 --- a/resources/lang/tr/strings.php +++ /dev/null @@ -1,91 +0,0 @@ - '2AD', - '2fa_token' => 'Doğrulama Kodu', - 'account' => 'Hesap', - 'action' => 'Action', - 'admin' => 'Yönetici', - 'admin_control' => 'Yönetici Kontrol', - 'admin_cp' => 'Yönetici Kontrol Paneli', - 'alias' => 'Alias', - 'api_access' => 'Api Access', - 'cancel' => 'Cancel', - 'captcha_invalid' => 'The provided captcha is invalid.', - 'close' => 'Kapat', - 'configuration' => 'Konfigürasyon', - 'confirm_password' => 'Confirm Password', - 'connection' => 'Bağlantı Adresi', - 'cpu' => 'CPU', - 'create' => 'Oluştur', - 'created' => 'Oluşturuldu', - 'created_at' => 'Created At', - 'current_password' => 'Mevcut Şifre', - 'danger' => 'Danger', - 'data' => 'Data', - 'database' => 'Veritabanı', - 'databases' => 'Veritabanları', - 'days' => [ - 'fri' => 'Friday', - 'mon' => 'Monday', - 'sat' => 'Saturday', - 'sun' => 'Sunday', - 'thurs' => 'Thursday', - 'tues' => 'Tuesday', - 'wed' => 'Wednesday', - ], - 'delete' => 'Sil', - 'email' => 'The :attribute must be a valid email address.', - 'expires' => 'Expires', - 'home' => 'Anasayfa', - 'id' => 'ID', - 'ip' => 'The :attribute must be a valid IP address.', - 'last_activity' => 'Son Hareket', - 'last_run' => 'Last Run', - 'last_used' => 'Last Used', - 'location' => 'Yer', - 'login' => 'Giriş', - 'logout' => 'Çıkış', - 'make_primary' => 'Make Primary', - 'memo' => 'Memo', - 'memory' => 'Bellek', - 'minutes' => 'Minutes', - 'name' => 'İsim', - 'never' => 'never', - 'new' => 'Yeni', - 'next_run' => 'Next Run', - 'no' => 'Hayır', - 'node' => 'Node', - 'none' => 'None', - 'not_run_yet' => 'Not Run Yet', - 'optional' => 'Optional', - 'owner' => 'Sahip', - 'password' => 'Şifre', - 'port' => 'Port', - 'primary' => 'Primary', - 'public_key' => 'Token', - 'queued' => 'Queued', - 'read_only' => 'Read Only', - 'registered' => 'Kayitli', - 'relation' => 'Relation', - 'required' => 'The :attribute field is required.', - 'restart' => 'Teklar baslat', - 'revoke' => 'Geri Al', - 'search' => 'Ara', - 'seconds' => 'Seconds', - 'security' => 'Güvenlik', - 'select_all' => 'Hepsini Seç', - 'select_none' => 'Select None', - 'servers' => 'Sunucular', - 'settings' => 'Ayarlar', - 'sftp' => 'SFTP', - 'sign_out' => 'Çıkış', - 'status' => 'Durum', - 'submit' => 'Gönder', - 'subuser' => 'Subuser', - 'suspended' => 'Askıya Alındı', - 'tasks' => 'Tasks', - 'username' => 'Kullanıcı adı', - 'user_identifier' => 'Kullanıcı adı veya Eposta', - 'yes' => 'Evet', -]; diff --git a/resources/lang/tr/validation.php b/resources/lang/tr/validation.php deleted file mode 100644 index e0c4df2b..00000000 --- a/resources/lang/tr/validation.php +++ /dev/null @@ -1,79 +0,0 @@ - 'The :attribute must be accepted.', - 'active_url' => 'Girdiğiniz :attribute geçerli bir URL değil.', - 'after' => 'The :attribute must be a date after :date.', - 'after_or_equal' => 'The :attribute must be a date after or equal to :date.', - 'alpha' => 'The :attribute may only contain letters.', - 'alpha_dash' => 'The :attribute may only contain letters, numbers, and dashes.', - 'alpha_num' => 'The :attribute may only contain letters and numbers.', - 'array' => 'The :attribute must be an array.', - 'before' => 'The :attribute must be a date before :date.', - 'before_or_equal' => 'The :attribute must be a date before or equal to :date.', - 'between' => [ - 'array' => 'The :attribute must have between :min and :max items.', - 'file' => 'The :attribute must be between :min and :max kilobytes.', - 'numeric' => 'The :attribute must be between :min and :max.', - 'string' => 'The :attribute must be between :min and :max characters.', - ], - 'boolean' => 'The :attribute field must be true or false.', - 'confirmed' => 'The :attribute confirmation does not match.', - 'date' => 'The :attribute is not a valid date.', - 'date_format' => 'The :attribute does not match the format :format.', - 'different' => 'The :attribute and :other must be different.', - 'digits' => 'The :attribute must be :digits digits.', - 'digits_between' => 'The :attribute must be between :min and :max digits.', - 'dimensions' => 'The :attribute has invalid image dimensions.', - 'distinct' => 'The :attribute field has a duplicate value.', - 'email' => 'The :attribute must be a valid email address.', - 'exists' => 'The selected :attribute is invalid.', - 'file' => 'The :attribute must be a file.', - 'filled' => 'The :attribute field is required.', - 'image' => 'The :attribute must be an image.', - 'in' => 'The selected :attribute is invalid.', - 'integer' => 'The :attribute must be an integer.', - 'internal' => [ - 'variable_value' => ':env variable', - ], - 'in_array' => 'The :attribute field does not exist in :other.', - 'ip' => 'The :attribute must be a valid IP address.', - 'json' => 'The :attribute must be a valid JSON string.', - 'max' => [ - 'array' => 'The :attribute may not have more than :max items.', - 'file' => 'The :attribute may not be greater than :max kilobytes.', - 'numeric' => 'The :attribute may not be greater than :max.', - 'string' => 'The :attribute may not be greater than :max characters.', - ], - 'mimes' => 'The :attribute must be a file of type: :values.', - 'mimetypes' => 'The :attribute must be a file of type: :values.', - 'min' => [ - 'array' => 'The :attribute must have at least :min items.', - 'file' => 'The :attribute must be at least :min kilobytes.', - 'numeric' => 'The :attribute must be at least :min.', - 'string' => 'The :attribute must be at least :min characters.', - ], - 'not_in' => 'The selected :attribute is invalid.', - 'numeric' => 'The :attribute must be a number.', - 'present' => 'The :attribute field must be present.', - 'regex' => 'The :attribute format is invalid.', - 'required' => 'The :attribute field is required.', - 'required_if' => 'The :attribute field is required when :other is :value.', - 'required_unless' => 'The :attribute field is required unless :other is in :values.', - 'required_with' => 'The :attribute field is required when :values is present.', - 'required_without' => 'The :attribute field is required when :values is not present.', - 'required_without_all' => 'The :attribute field is required when none of :values are present.', - 'required_with_all' => 'The :attribute field is required when :values is present.', - 'same' => 'The :attribute and :other must match.', - 'size' => [ - 'array' => 'The :attribute must contain :size items.', - 'file' => 'The :attribute must be :size kilobytes.', - 'numeric' => 'The :attribute must be :size.', - 'string' => 'The :attribute must be :size characters.', - ], - 'string' => 'The :attribute must be a string.', - 'timezone' => 'The :attribute must be a valid zone.', - 'unique' => 'The :attribute has already been taken.', - 'uploaded' => 'The :attribute failed to upload.', - 'url' => 'The :attribute format is invalid.', -]; diff --git a/resources/lang/zh/admin/nests.php b/resources/lang/zh/admin/nests.php deleted file mode 100644 index 388c3269..00000000 --- a/resources/lang/zh/admin/nests.php +++ /dev/null @@ -1,33 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -return [ - 'notices' => [ - 'created' => '已成功创建 :name 。', - 'deleted' => '已成功从面板删除指定的管理模块。', - 'updated' => '已成功更新管理模块选项。', - ], - 'eggs' => [ - 'notices' => [ - 'imported' => '已成功导入管理模板。', - 'updated_via_import' => '此管理模板已按照上传的文件完成更新。', - 'deleted' => '已成功删除指定的管路模板。', - 'updated' => '已成功更新管理模板的配置。', - 'script_updated' => '已成功更新孵化蛋安装脚本且将于服务器安装时自动执行。', - 'egg_created' => '一个管理模板已经成功创建. 你需要重启所有正在运行的节点受控端来使该模板生效。', - ], - ], - 'variables' => [ - 'notices' => [ - 'variable_deleted' => '已移除变量 ":variable" 且其在重构服务器镜像后将会失效。 ', - 'variable_updated' => '已更新变量 ":variable" 。您需要重构使用此变量的服务器以应用更改。', - 'variable_created' => '已成功创建新变量并分配给此孵化蛋。', - ], - ], -]; diff --git a/resources/lang/zh/admin/node.php b/resources/lang/zh/admin/node.php deleted file mode 100644 index ad5a40a0..00000000 --- a/resources/lang/zh/admin/node.php +++ /dev/null @@ -1,23 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -return [ - 'validation' => [ - 'fqdn_not_resolvable' => '提供的正式域名(FQDN)或 IP 地址未解析到有效的 IP 地址。', - 'fqdn_required_for_ssl' => '此节点需要解析到公网 IP 地址的正式域名才能使用 SSL。', - ], - 'notices' => [ - 'allocations_added' => '已成功为此节点分配地址。', - 'node_deleted' => '已成功从面板中移除节点。', - 'location_required' => '您必须至少配置一个区域才能添加节点至面板。', - 'node_created' => '已成功新建节点!您可通过\'配置\'选项卡已自动配置此机器上的守护程序。在您添加服务器前,您必须先分配一个 IP 地址及端口。', - 'node_updated' => '已更新节点信息。若守护程序设置更改,您需要重启守护程序才能生效。', - 'unallocated_deleted' => '已为 :ip 删除所有未分配的端口。', - ], -]; diff --git a/resources/lang/zh/admin/pack.php b/resources/lang/zh/admin/pack.php deleted file mode 100644 index e8494ef2..00000000 --- a/resources/lang/zh/admin/pack.php +++ /dev/null @@ -1,16 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -return [ - 'notices' => [ - 'pack_updated' => '已成功更新整合包。', - 'pack_deleted' => '已成功从系统中删除整合包 “:name”。', - 'pack_created' => '已成功在系统上创建整合包,您现在可以使用它来部署服务器了。', - ], -]; diff --git a/resources/lang/zh/admin/server.php b/resources/lang/zh/admin/server.php deleted file mode 100644 index 5973034e..00000000 --- a/resources/lang/zh/admin/server.php +++ /dev/null @@ -1,31 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -return [ - 'exceptions' => [ - 'no_new_default_allocation' => '您正在尝试删除此服务器的默认分配地址,但此服务器可用的备选分配地址。', - 'marked_as_failed' => '此服务器被标记为安装失败。当前状态无法在面板中被改变。', - 'bad_variable' => '变量 :name 有验证错误。', - 'daemon_exception' => '连接守护程序时返回 HTTP/:code 反馈码。此错误已被记录。', - 'default_allocation_not_found' => '未在此服务器上找到请求的默认分配地址。', - ], - 'alerts' => [ - 'startup_changed' => '已更新此服务器的启动配置。若此服务器的启动模板被更改,其将被重新安装。', - 'server_deleted' => '已成功从系统中删除服务器。', - 'server_created' => '已成功在面板中创建服务器。请稍等面板完全安装服务器完毕。', - 'build_updated' => '已更新此服务器的构建参数。部分更改可能需要重启才能生效。启动参数已更改。', - 'suspension_toggled' => '服务器停用状态已更改为 :status.', - 'rebuild_on_boot' => '此服务器已被标记为需要重新构建 Docker 容器。此操作会在下次启动服务器后生效。', - 'install_toggled' => '此服务器的安装状态已被更改。', - 'server_reinstalled' => '此服务器已置于即将开始的重装队列中。', - 'details_updated' => '已成功更新服务器信息。', - 'docker_image_updated' => '已成功更改此服务器使用的默认 Docker 镜像。此操作需要重启以应用更改。', - 'node_required' => '您需要配置至少一个节点以添加服务器至面板。', - ], -]; diff --git a/resources/lang/zh/admin/user.php b/resources/lang/zh/admin/user.php deleted file mode 100644 index 27c2cc61..00000000 --- a/resources/lang/zh/admin/user.php +++ /dev/null @@ -1,18 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -return [ - 'exceptions' => [ - 'user_has_servers' => '无法删除已绑定活跃服务器的账户。请删除服务器后继续。', - ], - 'notices' => [ - 'account_created' => '已成功创建用户。', - 'account_updated' => '已成功更新用户。', - ], -]; diff --git a/resources/lang/zh/auth.php b/resources/lang/zh/auth.php deleted file mode 100644 index df59bb48..00000000 --- a/resources/lang/zh/auth.php +++ /dev/null @@ -1,22 +0,0 @@ - '您无权执行此操作。', - 'auth_error' => '登录时发生错误。', - 'authentication_required' => '需要认证以继续', - 'remember_me' => '记住我', - 'sign_in' => '登陆', - 'forgot_password' => '忘记密码', - 'request_reset_text' => '忘记密码了吗?请在下方填入您的电子邮件地址。', - 'reset_password_text' => '重设账户密码', - 'reset_password' => '重设密码', - 'email_sent' => '一封含有重置密码指引的邮件已发送至您的电子邮箱地址。', - 'failed' => '所提供的凭证与我们所记录的不符,或可能两步验证失败。', - 'throttle' => '登录尝试次数过多。请 :seconds 秒后重试。', - 'password_requirements' => '密码必须含有一位大写字母、小写字母及数字且长度至少为八位。', - 'request_reset' => '查找账户', - '2fa_required' => '两步验证', - '2fa_failed' => '两步验证密码错误', - 'totp_failed' => '尝试进行两步验证时发生错误。', - '2fa_must_be_enabled' => '管理员要求您启用两步验证才能使用面板。', -]; diff --git a/resources/lang/zh/base.php b/resources/lang/zh/base.php deleted file mode 100644 index 5613750b..00000000 --- a/resources/lang/zh/base.php +++ /dev/null @@ -1,89 +0,0 @@ - '请求中有一个或多个字段出错', - 'errors' => [ - 'return' => '返回上页', - 'home' => '返回主页', - '403' => [ - 'header' => '禁止访问', - 'desc' => '您没有访问此服务器上的资源的权限。', - ], - '404' => [ - 'header' => '文件未找到', - 'desc' => '我们无法在此服务器上找到所请求的资源。', - ], - 'installing' => [ - 'header' => '服务器安装中', - 'desc' => '请求的服务器正在完成安装进程。请几分钟后再来查看,您将在此过程完成后收到电子邮件提醒。', - ], - 'suspended' => [ - 'header' => '服务器已停用', - 'desc' => '此服务器已停用且无法访问。', - ], - 'maintenance' => [ - 'header' => '节点维护中', - 'title' => '暂时不可用', - 'desc' => '此节点正在维护,当前无法访问。', - ], - ], - 'index' => [ - 'header' => '您的服务器', - 'header_sub' => '您有权限访问的服务器。', - 'list' => '服务器列表', - ], - 'api' => [ - 'index' => [ - 'list' => '您的密钥', - 'header' => '账户 API', - 'header_sub' => '管理允许您对面板执行操作的 API 密钥。', - 'create_new' => '新建 API 密钥', - 'keypair_created' => '已成功生成 API 密钥并列于下方。', - ], - 'new' => [ - 'header' => '新建 API 密钥', - 'header_sub' => '新建账户访问密钥。', - 'form_title' => '详细信息', - 'descriptive_memo' => [ - 'title' => '描述', - 'description' => '请输入便于分辨此密钥的描述信息。', - ], - 'allowed_ips' => [ - 'title' => '许可 IP', - 'description' => '输入允许使用此密钥的 IP 地址列表。此功能支持无类别域间路由。留空将允许所有 IP 使用。', - ], - ], - ], - 'account' => [ - 'details_updated' => '已成功更新您的账户信息。', - 'invalid_password' => '您提供的密码无效。', - 'header' => '您的账户', - 'header_sub' => '管理您的账户信息.', - 'update_pass' => '修改密码', - 'update_email' => '修改电子邮件地址', - 'current_password' => '当前密码', - 'new_password' => '新密码', - 'new_password_again' => '重复密码', - 'new_email' => '新电子邮件地址', - 'first_name' => '姓氏', - 'last_name' => '名称', - 'update_identity' => '更新个人信息', - 'username_help' => '您的用户名必须未被他人使用,且仅包含下列字符::requirements。', - 'language' => '语言', - ], - 'security' => [ - 'session_mgmt_disabled' => '您的托管商未启用此界面来管理账户会话。', - 'header' => '账户安全', - 'header_sub' => '管理活跃中的会话与两步验证。', - 'sessions' => '活跃会话', - '2fa_header' => '两步验证', - '2fa_token_help' => '请填入由应用程序所生成的两步验证密钥(Google 身份验证器、Authy 等)。', - 'disable_2fa' => '关闭两步验证', - '2fa_enabled' => '已为此账户启用两步验证,您将需要验证以登录至此账户。若您想关闭两步验证,您只需在下方输入密钥并提交即可。', - '2fa_disabled' => '已关闭两步验证!您应启用此功能来作为此账户的附加防护手段。', - 'enable_2fa' => '启用两步验证', - '2fa_qr' => '在您的设备上配置两步验证', - '2fa_checkpoint_help' => '在您的手机上使用两步验证应用程序扫描左侧的二维码或直接输入下方的代码。录入后,请在下方输入应用程序生成的密码。', - '2fa_disable_error' => '提供的两步验证密钥无效。未关闭此账户的两步验证。两步验证密码错误. 关闭两步验证失败.', - ], -]; diff --git a/resources/lang/zh/command/messages.php b/resources/lang/zh/command/messages.php deleted file mode 100644 index e3c84860..00000000 --- a/resources/lang/zh/command/messages.php +++ /dev/null @@ -1,97 +0,0 @@ - [ - 'warning' => '似乎您已配置应用程序加密密钥。继续此操作将覆盖密钥并损坏已加密数据。请了解您所执行的操作后再决定是否继续!!!', - 'confirm' => '我已了解执行此操作的后果并承受丢失加密数据的风险,请继续。', - 'final_confirm' => '确您是否决定继续?更改应用程序加密密钥将导致数据丢失!!!', - ], - 'location' => [ - 'no_location_found' => '无法找到与提供的代码匹配的记录。', - 'ask_short' => '地区代码', - 'ask_long' => '地区描述', - 'created' => '已成功创建地区(:name),编号为 :id。', - 'deleted' => '已成功删除请求的地区。', - ], - 'user' => [ - 'search_users' => '请输入用户名、UUID 或电子邮件地址', - 'select_search_user' => '待删除的用户编号(请键入 \'0\' 来重新搜索)', - 'deleted' => '已成功从面板删除用户。', - 'confirm_delete' => '您是否想从面板中删除此用户?', - 'no_users_found' => '未找到匹配搜索项的用户。', - 'multiple_found' => '已找到多个匹配搜索项的用户,由于 --no-interaction 参数而无法删除。', - 'ask_admin' => '此用户是否为管理员?', - 'ask_email' => '电子邮件地址', - 'ask_username' => '用户名', - 'ask_name_first' => '姓氏', - 'ask_name_last' => '名称', - 'ask_password' => '密码', - 'ask_password_tip' => '若您想创建用户并稍后发送生成的随机密码给用户,请重新运行此命令(CTRL+C)并添加 `--no-password` 参数。', - 'ask_password_help' => '密码长度必须至少为八位且包含至少一位大写字母和数字。', - '2fa_help_text' => [ - '此命令将关闭账户的两步验证(若启用)。此命令应作为账户被锁定时的恢复措施。', - '若您不想这么做,请使用 CTRL+C 退出进程。', - ], - '2fa_disabled' => '已成功为 :email 禁用两步验证。', - ], - 'schedule' => [ - 'output_line' => '首次任务将于 `:schedule`(:hash)后执行。', - ], - 'maintenance' => [ - 'deleting_service_backup' => '正在删除服务备份文件 :file。', - ], - 'server' => [ - 'rebuild_failed' => '节点 ":node" 上的重构操作 ":name"(#:id) 发生了 :message 错误。', - 'power' => [ - 'confirm' => '您将在 :count 台服务器上执行 :action 操作。是否继续?', - 'action_failed' => '节点 ":node" 上的电源命令 ":name"(#:id) 发生了 :message 错误。', - ], - ], - 'environment' => [ - 'mail' => [ - 'ask_smtp_host' => 'SMTP 主机(如 smtp.gmail.com)', - 'ask_smtp_port' => 'SMTP 端口', - 'ask_smtp_username' => 'SMTP 用户名', - 'ask_smtp_password' => 'SMTP 密码', - 'ask_mailgun_domain' => 'Mailgun 域名', - 'ask_mailgun_secret' => 'Mailgun 密钥', - 'ask_mandrill_secret' => 'Mandrill 密钥', - 'ask_postmark_username' => 'Postmark API 密钥', - 'ask_driver' => '应使用哪款引擎发送邮件?', - 'ask_mail_from' => '电子邮件地址的邮件发信人为', - 'ask_mail_name' => '电子邮件的显示发信人为', - 'ask_encryption' => '加密方法', - ], - 'database' => [ - 'host_warning' => '由于经常发生套接字连接错误,我们极度不推荐您使用 “localhost” 作为数据库主机地址。若您仍想使用本地连接则应使用 “127.0.0.1”。', - 'host' => '数据库主机', - 'port' => '数据库端口', - 'database' => '数据库名', - 'username_warning' => '不仅翼龙面板不允许使用 "root" 账户连接 MySQL 数据库,且这将产生严重安全漏洞。您应为此软件单独创建 MySQL 账户。', - 'username' => '数据库用户名', - 'password_defined' => '您似乎已创建了带有密码的 MySQL 连接,您是否想更改?', - 'password' => '数据库密码', - 'connection_error' => '无法使用提供的凭证连接 MySQL 服务器。 返回的错误为 ":error"。', - 'creds_not_saved' => '您的数据库访问凭证尚未保存。您需要在继续前提供有效的连接信息。', - 'try_again' => '是否返回重试?', - ], - 'app' => [ - 'settings' => '是否启用可视化设置编辑器?', - 'author' => '管理模板作者电子邮件地址', - 'author_help' => '提供从此面板导出管理模板人员的电子邮件地址。此电子邮件地址必须合法。', - 'app_url_help' => '根据您是否启用 SSL 来决定此应用程序的 URL 应为 https:// 或 http://。若您选择错误,您的电子邮件及其他内容将指向到错误地址。', - 'app_url' => '应用程序 URL', - 'timezone_help' => '时区应匹配 PHP 所支持的时区。 如果您不确定,请参阅 http://php.net/manual/en/timezones.php。', - 'timezone' => '应用程序时区', - 'cache_driver' => '缓存驱动程序', - 'session_driver' => '会话驱动程序', - 'queue_driver' => '队列驱动程序', - 'using_redis' => '若您选择使用 Redis,请在下方提供有效的连接信息。在您未更改设置的大多数情况下,您均可使用默认值。', - 'redis_host' => 'Redis 主机', - 'redis_password' => 'Redis 密码', - 'redis_pass_help' => '默认情况下,Redis 服务器实例无需密码且在本地运行禁止外界访问。这种情况下,您只需回车即可。', - 'redis_port' => 'Redis 端口', - 'redis_pass_defined' => '您似乎已为 Redis 配置了密码,您是否想更改?', - ], - ], -]; diff --git a/resources/lang/zh/exceptions.php b/resources/lang/zh/exceptions.php deleted file mode 100644 index 38cb954e..00000000 --- a/resources/lang/zh/exceptions.php +++ /dev/null @@ -1,68 +0,0 @@ - '尝试连接守护程序是发生错误,状态码 HTTP/:code。此错误已被记录。', - 'node' => [ - 'servers_attached' => '要删除节点,您必须先取消其与其他服务器的关联。', - 'daemon_off_config_updated' => '已更新守护程序配置,但在尝试自动更新守护程序配置文件时发生错误。您需要手动更新守护程序的配置文件(core.json)以应用更改。', - ], - 'allocations' => [ - 'server_using' => '已有服务器被分配到该地址。您必须先解除关联才能删除此地址。', - 'too_many_ports' => '不支持单次添加多于 1000 个端口。', - 'invalid_mapping' => '所提供的端口 :port 无效,无法继续操作。', - 'cidr_out_of_range' => '类别域间路由仅允许介于 /25 和 /32 之间的掩码。', - 'port_out_of_range' => '分配端口的范围必须介于 1024 至 65535 之间。', - ], - 'nest' => [ - 'delete_has_servers' => '无法删除附着到活跃服务器上的管理模块。', - 'egg' => [ - 'delete_has_servers' => '无法删除附着到活跃服务器上的管理模块。', - 'invalid_copy_id' => '用于复制脚本的管理模板不存在,或脚本本身不存在。', - 'must_be_child' => '“复制设置自”选项指定的目标必须为所选管理模块的子选项。', - 'has_children' => '此管理模版为一个或多个管理模板的母模板。请先删除其他管理模板再删除此模板。', - ], - 'variables' => [ - 'env_not_unique' => '此管理面板的环境变量 :name 必须唯一。', - 'reserved_name' => '环境变量 :name 被保护的且无法被分配至其他变量。', - 'bad_validation_rule' => '验证规则 “:rule” 不是此应用程序的有效规则。', - ], - 'importer' => [ - 'json_error' => '导入 JSON 文件时发生错误::error.', - 'file_error' => '所提供的 JSON 文件无效。', - 'invalid_json_provided' => '所提供的 JSON 文件格式无法被解析。', - ], - ], - 'packs' => [ - 'delete_has_servers' => '无法删除依附到活跃服务器的整合包。', - 'update_has_servers' => '无法在有服务器依附至整合包时修改关联选项编号。', - 'invalid_upload' => '所提供的文件格式无效。', - 'invalid_mime' => '提供的文件不符合所需文件类型 :type', - 'unreadable' => '服务器无法打开所提供的归档文件。', - 'zip_extraction' => '提取归档文件至服务器时发生错误。', - 'invalid_archive_exception' => '整合包归档文件的根目录似乎缺少 archive.tar.gz 或 import.json。', - ], - 'subusers' => [ - 'editing_self' => '您无法作为子用户编辑您自己的子用户账号。', - 'user_is_owner' => '您无法作为子用户来添加为此服务器的服主。', - 'subuser_exists' => '使用该电子邮件地址的用户已被分配为此服务器的子用户。', - ], - 'databases' => [ - 'delete_has_databases' => '无法删除关联至活跃数据库的数据库服务器。', - ], - 'tasks' => [ - 'chain_interval_too_long' => '连环任务的最大时间间隔为 15 分钟。', - ], - 'locations' => [ - 'has_nodes' => '无法删除被依附活动节点的区域。', - ], - 'users' => [ - 'node_revocation_failed' => '注销节点 #:node 的密钥失败::error', - ], - 'deployment' => [ - 'no_viable_nodes' => '无法找到满足自动化部署需求的节点。', - 'no_viable_allocations' => '无法找到满足自动化部署需求的分配地址。', - ], - 'api' => [ - 'resource_not_found' => '服务器上不存在所请求的资源。', - ], -]; diff --git a/resources/lang/zh/navigation.php b/resources/lang/zh/navigation.php deleted file mode 100644 index d0c43b02..00000000 --- a/resources/lang/zh/navigation.php +++ /dev/null @@ -1,32 +0,0 @@ - '首页', - 'account' => [ - 'header' => '账户管理', - 'my_account' => '我的账户', - 'security_controls' => '安全控制', - 'api_access' => 'API', - 'my_servers' => '我的服务器', - ], - 'server' => [ - 'header' => '服务器管理', - 'console' => '控制台', - 'console-pop' => '全屏控制台', - 'file_management' => '文件管理', - 'file_browser' => '文件浏览器', - 'create_file' => '新建文件', - 'upload_files' => '上传文件', - 'subusers' => '子用户', - 'schedules' => '计划任务', - 'configuration' => '配置', - 'port_allocations' => '地址设置', - 'sftp_settings' => 'SFTP 设置', - 'startup_parameters' => '启动参数', - 'databases' => '数据库', - 'edit_file' => '编辑文件', - 'admin_header' => '管理', - 'admin' => '服务器配置', - 'server_name' => '服务器名', - ], -]; diff --git a/resources/lang/zh/pagination.php b/resources/lang/zh/pagination.php deleted file mode 100644 index 259109ed..00000000 --- a/resources/lang/zh/pagination.php +++ /dev/null @@ -1,17 +0,0 @@ - '« 上页', - 'next' => '下页 »', -]; diff --git a/resources/lang/zh/passwords.php b/resources/lang/zh/passwords.php deleted file mode 100644 index 4772044b..00000000 --- a/resources/lang/zh/passwords.php +++ /dev/null @@ -1,19 +0,0 @@ - '密码必须至少包含六位字符且与确认密码匹配。', - 'reset' => '已重设您的密码!', - 'sent' => '我们已发送密码重设链接至您的电子邮件地址!', - 'token' => '此密码重设令牌无效。', - 'user' => '我们无法找到使用此电子邮件地址的用户。', -]; diff --git a/resources/lang/zh/server.php b/resources/lang/zh/server.php deleted file mode 100644 index 56d76a97..00000000 --- a/resources/lang/zh/server.php +++ /dev/null @@ -1,334 +0,0 @@ - [ - 'title' => '查看服务器 :name ', - 'header' => '服务器控制台', - 'header_sub' => '实时掌控您的服务器。', - ], - 'schedule' => [ - 'header' => '计划任务', - 'header_sub' => '一处轻松掌管服务器任务。', - 'current' => '当前计划', - 'new' => [ - 'header' => '新建计划', - 'header_sub' => '为此服务器新建一组计划任务。', - 'submit' => '创建任务', - ], - 'manage' => [ - 'header' => '管理任务', - 'submit' => '修改任务', - 'delete' => '删除任务', - ], - 'task' => [ - 'time' => '在···之后', - 'action' => '执行操作', - 'payload' => '任务内容', - 'add_more' => '添加其他任务', - ], - 'actions' => [ - 'command' => '发送命令', - 'power' => '电源命令', - ], - 'toggle' => '更改状态', - 'run_now' => '触发任务', - 'schedule_created' => '已成功在此服务器上新建计划任务。', - 'schedule_updated' => '已更新计划任务。', - 'unnamed' => '未命名任务', - 'setup' => '任务配置', - 'day_of_week' => '星期', - 'day_of_month' => '日', - 'hour' => '时', - 'minute' => '分', - 'time_help' => '计划任务系统支持使用 Cronjob 语法来定义任务启动时间。使用上方的字段来指定计划任务的开始时间或选择多选菜单中的多个选项。', - 'task_help' => '任务时间与先前定义的任务紧密相关。每个计划最多可分配 5 项任务且任务时间间隔不得超过 15 分钟。', - ], - 'tasks' => [ - 'task_created' => '已成功在面板上新建任务。', - 'task_updated' => '已成功更新任务。现有的所有队列中的任务操作将被取消并于下个定义时间执行。', - 'header' => '计划任务', - 'header_sub' => '自动化您的服务器。', - 'current' => '当前计划任务', - 'actions' => [ - 'command' => '发送命令', - 'power' => '发送电源指令', - ], - 'new_task' => '新增任务', - 'toggle' => '更改状态', - 'new' => [ - 'header' => '新建任务', - 'header_sub' => '为此服务器新建计划任务。', - 'task_name' => '任务名称', - 'day_of_week' => '星期', - 'custom' => '自定义', - 'day_of_month' => '日', - 'hour' => '时', - 'minute' => '分', - 'sun' => '星期日', - 'mon' => '星期一', - 'tues' => '星期二', - 'wed' => '星期三', - 'thurs' => '星期四', - 'fri' => '星期五', - 'sat' => '星期六', - 'submit' => '创建任务', - 'type' => '任务类型', - 'chain_then' => '先···再···', - 'chain_do' => '执行', - 'chain_arguments' => '使用参数', - 'payload' => '任务内容', - 'payload_help' => '例如,若您选择发送命令,请在此处填写要发送的命令。若您选择发送电源指令,请在此处填写电源命令(如重启).', - ], - 'edit' => [ - 'header' => '任务管理', - 'submit' => '更新任务', - ], - ], - 'users' => [ - 'header' => '用户管理', - 'header_sub' => '掌控谁能访问您的服务器。', - 'configure' => '配置权限', - 'list' => '权限用户', - 'add' => '新增子用户', - 'update' => '更新子用户', - 'user_assigned' => '已成功分配新子用户至此服务器。', - 'user_updated' => '已成功更新权限。', - 'edit' => [ - 'header' => '编辑子用户', - 'header_sub' => '编辑此用户的服务器访问权限。', - ], - 'new' => [ - 'header' => '新增新用户', - 'header_sub' => '新增允许访问此服务器的用户。', - 'email' => '电子邮件地址', - 'email_help' => '输入您邀请管理此服务器用户的电子邮件地址。', - 'power_header' => '电源管理', - 'file_header' => '文件管理', - 'subuser_header' => '子用户管理', - 'server_header' => '服务器管理', - 'task_header' => '计划任务管理', - 'database_header' => '数据库管理', - 'power_start' => [ - 'title' => '启动服务器', - 'description' => '允许此用户启动服务器。', - ], - 'power_stop' => [ - 'title' => '停止服务器', - 'description' => '允许此用户停止服务器。', - ], - 'power_restart' => [ - 'title' => '重新启动服务器', - 'description' => '允许此用户重新启动服务器。', - ], - 'power_kill' => [ - 'title' => '强制关闭服务器', - 'description' => '允许此用户强行关闭服务器。', - ], - 'send_command' => [ - 'title' => '发送控制台命令', - 'description' => '允许用户发送控制台命令。若用户没有“停止服务器”权限,则其 stop 命令。', - ], - 'access_sftp' => [ - 'title' => 'SFTP 权限', - 'description' => '允许用户连接到守护程序所提供的 SFTP 服务器。', - ], - 'list_files' => [ - 'title' => '列出文件', - 'description' => '允许用户列出服务器上所有文件及文件夹,但是无法查看文件内容。', - ], - 'edit_files' => [ - 'title' => '编辑文件', - 'description' => '允许用户打开文件查看内容。SFTP 不受此权限影响。', - ], - 'save_files' => [ - 'title' => '保存文件', - 'description' => '允许用户保存编辑过的文件内容。SFTP 不受此权限影响。', - ], - 'move_files' => [ - 'title' => '重命名与移动文件', - 'description' => '允许用户在文件系统上重命名与移动文件及文件夹。', - ], - 'copy_files' => [ - 'title' => '复制文件', - 'description' => '允许用户在文件系统上复制文件及文件夹。', - ], - 'compress_files' => [ - 'title' => '压缩文件', - 'description' => '允许用户在文件系统上压缩文件及文件夹。', - ], - 'decompress_files' => [ - 'title' => '解压文件', - 'description' => '允许用户解压 .zip 和 .tar(.gz)归档文件。', - ], - 'create_files' => [ - 'title' => '创建文件', - 'description' => '允许用户通过面板创建文件。', - ], - 'upload_files' => [ - 'title' => '上传文件', - 'description' => '允许用户通过文件管理上传文件。', - ], - 'delete_files' => [ - 'title' => '删除文件', - 'description' => '允许用户删除文件系统上的文件。', - ], - 'download_files' => [ - 'title' => '下载文件', - 'description' => '允许用户下载文件。若用户被给予此权限,其可以在下载后查看文件而无需所需面板权限。', - ], - 'list_subusers' => [ - 'title' => '列出子用户', - 'description' => '允许用户访问此服务器的子用户列表。', - ], - 'view_subuser' => [ - 'title' => '查看子用户', - 'description' => '允许用户查看子用户的权限。', - ], - 'edit_subuser' => [ - 'title' => '编辑子用户', - 'description' => '允许用户编辑此服务器上的子用户权限。', - ], - 'create_subuser' => [ - 'title' => '创建子用户', - 'description' => '允许用户在此服务器上添加子用户。', - ], - 'delete_subuser' => [ - 'title' => '删除子用户', - 'description' => '允许用户删除此服务器上的子用户。', - ], - 'view_allocations' => [ - 'title' => '查看分配', - 'description' => '允许用户查看所有分配到此服务器上的 IP 及端口。', - ], - 'edit_allocation' => [ - 'title' => '编辑默认连接', - 'description' => '允许用户更改此服务器的默认连接地址。', - ], - 'view_startup' => [ - 'title' => '查看启动参数', - 'description' => '允许用户访问服务器的启动参数和相关变量。', - ], - 'edit_startup' => [ - 'title' => '编辑启动参数', - 'description' => '允许用户更改服务器的启动参数。', - ], - 'list_schedules' => [ - 'title' => '列出计划任务', - 'description' => '允许用户列出服务器上的所有计划任务(无论是否启用)。', - ], - 'view_schedule' => [ - 'title' => '查看计划', - 'description' => '允许用户查看计划任务的详细信息,包含执行时间及分配任务。', - ], - 'toggle_schedule' => [ - 'title' => '开关计划', - 'description' => '允许用户启用或禁用计划的。', - ], - 'queue_schedule' => [ - 'title' => '队列计划', - 'description' => '允许用户将计划纳入队列在下个周期执行。', - ], - 'edit_schedule' => [ - 'title' => '编辑计划', - 'description' => '允许用户编辑计划,包括所有的执行任务。这将允许用户移除单个任务,但无法移除计划本身。', - ], - 'create_schedule' => [ - 'title' => '创建计划', - 'description' => '允许用户新建计划任务。', - ], - 'delete_schedule' => [ - 'title' => '删除计划', - 'description' => '允许用户从服务器删除计划。', - ], - 'view_databases' => [ - 'title' => '查看数据库信息', - 'description' => '允许用户查看所有与此服务器相关联的数据库及其用户名与密码信息。', - ], - 'reset_db_password' => [ - 'title' => '重置数据库', - 'description' => '允许用户重置服务器数据库密码。', - ], - 'delete_database' => [ - 'title' => '删除数据库', - 'description' => '允许用户从面板删除此服务器数据库。', - ], - 'create_database' => [ - 'title' => '新建数据库', - 'description' => '允许用户为此服务器新建数据库。', - ], - ], - ], - 'allocations' => [ - 'mass_actions' => '批量操作', - 'delete' => '删除分配地址', - ], - 'files' => [ - 'exceptions' => [ - 'invalid_mime' => '此类型文件无法通过面板内置编辑器编辑。', - 'max_size' => '此文件过大,无法使用面板内置编辑器编辑。', - ], - 'header' => '文件管理', - 'header_sub' => '从网页直接管理您的所有文件。', - 'loading' => '正在加载初始文件结构,这可能需要几秒钟。', - 'path' => '当您配置插件或服务器设置的文件路径时,您应使用 :path 作为您的根目录。此节点通过网页上传的最大文件限制为 :size。', - 'seconds_ago' => '数秒前', - 'file_name' => '文件名', - 'size' => '大小', - 'last_modified' => '最后修改', - 'add_new' => '新建文件', - 'add_folder' => '新建文件夹', - 'mass_actions' => '批量操作', - 'delete' => '删除文件', - 'edit' => [ - 'header' => '编辑文件', - 'header_sub' => '从网页编辑文件。', - 'save' => '保存文件', - 'return' => '返回文件管理', - ], - 'add' => [ - 'header' => '新建文件', - 'header_sub' => '在您服务器上新建新文件。', - 'name' => '文件名', - 'create' => '创建文件', - ], - ], - 'config' => [ - 'name' => [ - 'header' => '服务器名', - 'header_sub' => '更改服务器名称。', - 'details' => '此服务器名只是为了让您更好的管理服务器,并不会对向游戏内玩家显示的服务器配置造成影响。', - ], - 'startup' => [ - 'header' => '启动配置', - 'header_sub' => '控制服务器的启动参数。', - 'command' => '启动命令', - 'edit_params' => '编辑参数', - 'update' => '更新启动参数', - 'startup_regex' => '输入规则', - 'edited' => '已成功编辑启动变量。这将在下次服务器启动时发挥功用。', - ], - 'sftp' => [ - 'header' => 'SFTP 配置', - 'header_sub' => 'SFTP 连接所需的账户信息。', - 'details' => 'SFTP 信息', - 'conn_addr' => '连接地址', - 'warning' => 'SFTP 密码为您的账户密码。请确保您的客户端被设置为使用 SFTP 而非 FTP 或 FTPS,这些协议间存在差异。', - ], - 'database' => [ - 'header' => '数据库', - 'header_sub' => '此服务器的所有可用数据库。', - 'your_dbs' => '已配置的数据库', - 'host' => 'MySQL 主机', - 'reset_password' => '重置密码', - 'no_dbs' => '此服务器没有可用的数据库。', - 'add_db' => '新建新数据库。', - ], - 'allocation' => [ - 'header' => '服务器地址分配', - 'header_sub' => '控制此服务器可使用的 IP 地址和端口。', - 'available' => '可用分配地址', - 'help' => '分配地址帮助', - 'help_text' => '左方列表列出了您可用于传入连接的所有可用 IP 地址及端口。', - ], - ], -]; diff --git a/resources/lang/zh/strings.php b/resources/lang/zh/strings.php deleted file mode 100644 index 577cc6f4..00000000 --- a/resources/lang/zh/strings.php +++ /dev/null @@ -1,88 +0,0 @@ - '电子邮件地址', - 'user_identifier' => '用户名或电子邮件地址', - 'password' => '密码', - 'confirm_password' => '确认密码', - 'login' => '登录', - 'home' => '首页', - 'servers' => '服务器', - 'id' => '编号', - 'name' => '名称', - 'node' => '节点', - 'connection' => '连接', - 'memory' => '内存', - 'cpu' => 'CPU', - 'status' => '状态', - 'search' => '搜索', - 'suspended' => '已停用', - 'account' => '用户', - 'security' => '安全', - 'ip' => 'IP 地址', - 'last_activity' => '上次活动', - 'revoke' => '注销', - '2fa_token' => '认证密钥', - 'submit' => '确认', - 'close' => '关闭', - 'settings' => '设置', - 'configuration' => '配置', - 'sftp' => 'SFTP', - 'databases' => '数据库', - 'memo' => '描述', - 'created' => '已创建', - 'expires' => '过期', - 'public_key' => '令牌', - 'api_access' => 'API 访问', - 'never' => '从未', - 'sign_out' => '登出', - 'admin_control' => '管理员面板', - 'required' => '需要', - 'port' => '端口', - 'username' => '用户名', - 'database' => '数据库', - 'new' => '新', - 'danger' => '危险', - 'create' => '创建', - 'select_all' => '全选', - 'select_none' => '反选', - 'alias' => '别名', - 'primary' => '主要', - 'make_primary' => '设置为主要', - 'none' => '无', - 'cancel' => '取消', - 'created_at' => '创建于', - 'action' => '操作', - 'data' => '数据', - 'queued' => '队列', - 'last_run' => '上次运行', - 'next_run' => '下次运行', - 'not_run_yet' => '从未允许', - 'yes' => '是', - 'no' => '否', - 'delete' => '删除', - '2fa' => '两步验证', - 'logout' => '登出', - 'admin_cp' => '管理员控制面板', - 'optional' => '可选项', - 'read_only' => '只读', - 'relation' => '关系', - 'owner' => '所有者', - 'admin' => '管理员', - 'subuser' => '子用户', - 'captcha_invalid' => '验证码无效', - 'tasks' => '任务', - 'seconds' => '秒', - 'minutes' => '分', - 'under_maintenance' => '维护中', - 'days' => [ - 'sun' => '星期天', - 'mon' => '星期一', - 'tues' => '星期二', - 'wed' => '星期三', - 'thurs' => '星期四', - 'fri' => '星期五', - 'sat' => '星期六', - ], - 'last_used' => '上次使用', -]; diff --git a/resources/lang/zh/validation.php b/resources/lang/zh/validation.php deleted file mode 100644 index 201880ec..00000000 --- a/resources/lang/zh/validation.php +++ /dev/null @@ -1,105 +0,0 @@ - 'The :attribute must be accepted.', - 'active_url' => 'The :attribute is not a valid URL.', - 'after' => 'The :attribute must be a date after :date.', - 'after_or_equal' => 'The :attribute must be a date after or equal to :date.', - 'alpha' => 'The :attribute may only contain letters.', - 'alpha_dash' => 'The :attribute may only contain letters, numbers, and dashes.', - 'alpha_num' => 'The :attribute may only contain letters and numbers.', - 'array' => 'The :attribute must be an array.', - 'before' => 'The :attribute must be a date before :date.', - 'before_or_equal' => 'The :attribute must be a date before or equal to :date.', - 'between' => [ - 'numeric' => 'The :attribute must be between :min and :max.', - 'file' => 'The :attribute must be between :min and :max kilobytes.', - 'string' => 'The :attribute must be between :min and :max characters.', - 'array' => 'The :attribute must have between :min and :max items.', - ], - 'boolean' => 'The :attribute field must be true or false.', - 'confirmed' => 'The :attribute confirmation does not match.', - 'date' => 'The :attribute is not a valid date.', - 'date_format' => 'The :attribute does not match the format :format.', - 'different' => 'The :attribute and :other must be different.', - 'digits' => 'The :attribute must be :digits digits.', - 'digits_between' => 'The :attribute must be between :min and :max digits.', - 'dimensions' => 'The :attribute has invalid image dimensions.', - 'distinct' => 'The :attribute field has a duplicate value.', - 'email' => 'The :attribute must be a valid email address.', - 'exists' => 'The selected :attribute is invalid.', - 'file' => 'The :attribute must be a file.', - 'filled' => 'The :attribute field is required.', - 'image' => 'The :attribute must be an image.', - 'in' => 'The selected :attribute is invalid.', - 'in_array' => 'The :attribute field does not exist in :other.', - 'integer' => 'The :attribute must be an integer.', - 'ip' => 'The :attribute must be a valid IP address.', - 'json' => 'The :attribute must be a valid JSON string.', - 'max' => [ - 'numeric' => 'The :attribute may not be greater than :max.', - 'file' => 'The :attribute may not be greater than :max kilobytes.', - 'string' => 'The :attribute may not be greater than :max characters.', - 'array' => 'The :attribute may not have more than :max items.', - ], - 'mimes' => 'The :attribute must be a file of type: :values.', - 'mimetypes' => 'The :attribute must be a file of type: :values.', - 'min' => [ - 'numeric' => 'The :attribute must be at least :min.', - 'file' => 'The :attribute must be at least :min kilobytes.', - 'string' => 'The :attribute must be at least :min characters.', - 'array' => 'The :attribute must have at least :min items.', - ], - 'not_in' => 'The selected :attribute is invalid.', - 'numeric' => 'The :attribute must be a number.', - 'present' => 'The :attribute field must be present.', - 'regex' => 'The :attribute format is invalid.', - 'required' => 'The :attribute field is required.', - 'required_if' => 'The :attribute field is required when :other is :value.', - 'required_unless' => 'The :attribute field is required unless :other is in :values.', - 'required_with' => 'The :attribute field is required when :values is present.', - 'required_with_all' => 'The :attribute field is required when :values is present.', - 'required_without' => 'The :attribute field is required when :values is not present.', - 'required_without_all' => 'The :attribute field is required when none of :values are present.', - 'same' => 'The :attribute and :other must match.', - 'size' => [ - 'numeric' => 'The :attribute must be :size.', - 'file' => 'The :attribute must be :size kilobytes.', - 'string' => 'The :attribute must be :size characters.', - 'array' => 'The :attribute must contain :size items.', - ], - 'string' => 'The :attribute must be a string.', - 'timezone' => 'The :attribute must be a valid zone.', - 'unique' => 'The :attribute has already been taken.', - 'uploaded' => 'The :attribute failed to upload.', - 'url' => 'The :attribute format is invalid.', - - /* - |-------------------------------------------------------------------------- - | Custom Validation Attributes - |-------------------------------------------------------------------------- - | - | The following language lines are used to swap attribute place-holders - | with something more reader friendly such as E-Mail Address instead - | of "email". This simply helps us make messages a little cleaner. - | - */ - - 'attributes' => [], - - // Internal validation logic for Pterodactyl - 'internal' => [ - 'variable_value' => ':env variable', - ], -]; diff --git a/resources/scripts/.eslintrc.yml b/resources/scripts/.eslintrc.yml new file mode 100644 index 00000000..b18f90af --- /dev/null +++ b/resources/scripts/.eslintrc.yml @@ -0,0 +1,89 @@ +parser: "@typescript-eslint/parser" +parserOptions: + ecmaVersion: 6 + ecmaFeatures: + jsx: true + project: "./tsconfig.json" + tsconfigRootDir: "./" +settings: + react: + pragma: "React" + version: "detect" + linkComponents: + - name: Link + linkAttribute: to + - name: NavLink + linkAttribute: to +env: + browser: true + es6: true +plugins: + - "react" + - "react-hooks" + - "@typescript-eslint" +extends: + - "standard" + - "plugin:react/recommended" + - "plugin:@typescript-eslint/recommended" +rules: + quotes: + - warn + - single + indent: + - warn + - 4 + - SwitchCase: 1 + semi: + - warn + - always + comma-dangle: + - warn + - always-multiline + spaced-comment: + - warn + array-bracket-spacing: + - warn + - always + "react-hooks/rules-of-hooks": + - error + "react-hooks/exhaustive-deps": 0 + "@typescript-eslint/explicit-function-return-type": 0 + "@typescript-eslint/explicit-member-accessibility": 0 + "@typescript-eslint/ban-ts-ignore": 0 + "@typescript-eslint/no-explicit-any": 0 + "@typescript-eslint/no-non-null-assertion": 0 + "@typescript-eslint/ban-ts-comment": 0 + # This would be nice to have, but don't want to deal with the warning spam at the moment. + "@typescript-eslint/explicit-module-boundary-types": 0 + no-restricted-imports: + - error + - paths: + - name: styled-components + message: Please import from styled-components/macro. + patterns: + - "!styled-components/macro" + # Not sure, this rule just doesn't work right and is protected by our use of Typescript anyways + # so I'm just not going to worry about it. + "react/prop-types": 0 + "react/display-name": 0 + "react/jsx-indent-props": + - warn + - 4 + "react/jsx-boolean-value": + - warn + - never + "react/jsx-closing-bracket-location": + - 1 + - "line-aligned" + "react/jsx-closing-tag-location": 1 +overrides: + - files: + - "**/*.tsx" + rules: + operator-linebreak: + - error + - before + - overrides: + "&&": "after" + "?": "ignore" + ":": "ignore" diff --git a/resources/scripts/TransitionRouter.tsx b/resources/scripts/TransitionRouter.tsx new file mode 100644 index 00000000..342e31a7 --- /dev/null +++ b/resources/scripts/TransitionRouter.tsx @@ -0,0 +1,35 @@ +import React, { useRef } from 'react'; +import { Route } from 'react-router'; +import { SwitchTransition } from 'react-transition-group'; +import Fade from '@/components/elements/Fade'; +import styled from 'styled-components/macro'; +import tw from 'twin.macro'; +import v4 from 'uuid/v4'; + +const StyledSwitchTransition = styled(SwitchTransition)` + ${tw`relative`}; + + & section { + ${tw`absolute w-full top-0 left-0`}; + } +`; + +const TransitionRouter: React.FC = ({ children }) => { + const uuid = useRef(v4()).current; + + return ( + ( + + +
    + {children} +
    +
    +
    + )} + /> + ); +}; + +export default TransitionRouter; diff --git a/resources/scripts/api/account/createApiKey.ts b/resources/scripts/api/account/createApiKey.ts new file mode 100644 index 00000000..7067ec14 --- /dev/null +++ b/resources/scripts/api/account/createApiKey.ts @@ -0,0 +1,17 @@ +import http from '@/api/http'; +import { ApiKey, rawDataToApiKey } from '@/api/account/getApiKeys'; + +export default (description: string, allowedIps: string): Promise => { + return new Promise((resolve, reject) => { + http.post('/api/client/account/api-keys', { + description, + allowed_ips: allowedIps.length > 0 ? allowedIps.split('\n') : [], + }) + .then(({ data }) => resolve({ + ...rawDataToApiKey(data.attributes), + // eslint-disable-next-line camelcase + secretToken: data.meta?.secret_token ?? '', + })) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/account/deleteApiKey.ts b/resources/scripts/api/account/deleteApiKey.ts new file mode 100644 index 00000000..e34350d0 --- /dev/null +++ b/resources/scripts/api/account/deleteApiKey.ts @@ -0,0 +1,9 @@ +import http from '@/api/http'; + +export default (identifier: string): Promise => { + return new Promise((resolve, reject) => { + http.delete(`/api/client/account/api-keys/${identifier}`) + .then(() => resolve()) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/account/disableAccountTwoFactor.ts b/resources/scripts/api/account/disableAccountTwoFactor.ts new file mode 100644 index 00000000..2b41fe20 --- /dev/null +++ b/resources/scripts/api/account/disableAccountTwoFactor.ts @@ -0,0 +1,9 @@ +import http from '@/api/http'; + +export default (password: string): Promise => { + return new Promise((resolve, reject) => { + http.delete('/api/client/account/two-factor', { params: { password } }) + .then(() => resolve()) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/account/enableAccountTwoFactor.ts b/resources/scripts/api/account/enableAccountTwoFactor.ts new file mode 100644 index 00000000..e7a15f62 --- /dev/null +++ b/resources/scripts/api/account/enableAccountTwoFactor.ts @@ -0,0 +1,7 @@ +import http from '@/api/http'; + +export default async (code: string): Promise => { + const { data } = await http.post('/api/client/account/two-factor', { code }); + + return data.attributes.tokens; +}; diff --git a/resources/scripts/api/account/getApiKeys.ts b/resources/scripts/api/account/getApiKeys.ts new file mode 100644 index 00000000..f8937ddd --- /dev/null +++ b/resources/scripts/api/account/getApiKeys.ts @@ -0,0 +1,25 @@ +import http from '@/api/http'; + +export interface ApiKey { + identifier: string; + description: string; + allowedIps: string[]; + createdAt: Date | null; + lastUsedAt: Date | null; +} + +export const rawDataToApiKey = (data: any): ApiKey => ({ + identifier: data.identifier, + description: data.description, + allowedIps: data.allowed_ips, + createdAt: data.created_at ? new Date(data.created_at) : null, + lastUsedAt: data.last_used_at ? new Date(data.last_used_at) : null, +}); + +export default (): Promise => { + return new Promise((resolve, reject) => { + http.get('/api/client/account/api-keys') + .then(({ data }) => resolve((data.data || []).map((d: any) => rawDataToApiKey(d.attributes)))) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/account/getTwoFactorTokenUrl.ts b/resources/scripts/api/account/getTwoFactorTokenUrl.ts new file mode 100644 index 00000000..6d9a2aa9 --- /dev/null +++ b/resources/scripts/api/account/getTwoFactorTokenUrl.ts @@ -0,0 +1,9 @@ +import http from '@/api/http'; + +export default (): Promise => { + return new Promise((resolve, reject) => { + http.get('/api/client/account/two-factor') + .then(({ data }) => resolve(data.data.image_url_data)) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/account/updateAccountEmail.ts b/resources/scripts/api/account/updateAccountEmail.ts new file mode 100644 index 00000000..5ff23026 --- /dev/null +++ b/resources/scripts/api/account/updateAccountEmail.ts @@ -0,0 +1,9 @@ +import http from '@/api/http'; + +export default (email: string, password: string): Promise => { + return new Promise((resolve, reject) => { + http.put('/api/client/account/email', { email, password }) + .then(() => resolve()) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/account/updateAccountPassword.ts b/resources/scripts/api/account/updateAccountPassword.ts new file mode 100644 index 00000000..d59e85e9 --- /dev/null +++ b/resources/scripts/api/account/updateAccountPassword.ts @@ -0,0 +1,19 @@ +import http from '@/api/http'; + +interface Data { + current: string; + password: string; + confirmPassword: string; +} + +export default ({ current, password, confirmPassword }: Data): Promise => { + return new Promise((resolve, reject) => { + http.put('/api/client/account/password', { + current_password: current, + password: password, + password_confirmation: confirmPassword, + }) + .then(() => resolve()) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/auth/login.ts b/resources/scripts/api/auth/login.ts new file mode 100644 index 00000000..af2f5faa --- /dev/null +++ b/resources/scripts/api/auth/login.ts @@ -0,0 +1,35 @@ +import http from '@/api/http'; + +export interface LoginResponse { + complete: boolean; + intended?: string; + confirmationToken?: string; +} + +export interface LoginData { + username: string; + password: string; + recaptchaData?: string | null; +} + +export default ({ username, password, recaptchaData }: LoginData): Promise => { + return new Promise((resolve, reject) => { + http.post('/auth/login', { + user: username, + password, + 'g-recaptcha-response': recaptchaData, + }) + .then(response => { + if (!(response.data instanceof Object)) { + return reject(new Error('An error occurred while processing the login request.')); + } + + return resolve({ + complete: response.data.data.complete, + intended: response.data.data.intended || undefined, + confirmationToken: response.data.data.confirmation_token || undefined, + }); + }) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/auth/loginCheckpoint.ts b/resources/scripts/api/auth/loginCheckpoint.ts new file mode 100644 index 00000000..2d139fa5 --- /dev/null +++ b/resources/scripts/api/auth/loginCheckpoint.ts @@ -0,0 +1,17 @@ +import http from '@/api/http'; +import { LoginResponse } from '@/api/auth/login'; + +export default (token: string, code: string, recoveryToken?: string): Promise => { + return new Promise((resolve, reject) => { + http.post('/auth/login/checkpoint', { + confirmation_token: token, + authentication_code: code, + recovery_token: (recoveryToken && recoveryToken.length > 0) ? recoveryToken : undefined, + }) + .then(response => resolve({ + complete: response.data.data.complete, + intended: response.data.data.intended || undefined, + })) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/auth/performPasswordReset.ts b/resources/scripts/api/auth/performPasswordReset.ts new file mode 100644 index 00000000..6695099e --- /dev/null +++ b/resources/scripts/api/auth/performPasswordReset.ts @@ -0,0 +1,28 @@ +import http from '@/api/http'; + +interface Data { + token: string; + password: string; + passwordConfirmation: string; +} + +interface PasswordResetResponse { + redirectTo?: string | null; + sendToLogin: boolean; +} + +export default (email: string, data: Data): Promise => { + return new Promise((resolve, reject) => { + http.post('/auth/password/reset', { + email, + token: data.token, + password: data.password, + password_confirmation: data.passwordConfirmation, + }) + .then(response => resolve({ + redirectTo: response.data.redirect_to, + sendToLogin: response.data.send_to_login, + })) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/auth/requestPasswordResetEmail.ts b/resources/scripts/api/auth/requestPasswordResetEmail.ts new file mode 100644 index 00000000..2168160c --- /dev/null +++ b/resources/scripts/api/auth/requestPasswordResetEmail.ts @@ -0,0 +1,9 @@ +import http from '@/api/http'; + +export default (email: string, recaptchaData?: string): Promise => { + return new Promise((resolve, reject) => { + http.post('/auth/password', { email, 'g-recaptcha-response': recaptchaData }) + .then(response => resolve(response.data.status || '')) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/getServers.ts b/resources/scripts/api/getServers.ts new file mode 100644 index 00000000..63329bfa --- /dev/null +++ b/resources/scripts/api/getServers.ts @@ -0,0 +1,25 @@ +import { rawDataToServerObject, Server } from '@/api/server/getServer'; +import http, { getPaginationSet, PaginatedResult } from '@/api/http'; + +interface QueryParams { + query?: string; + page?: number; + onlyAdmin?: boolean; +} + +export default ({ query, page = 1, onlyAdmin = false }: QueryParams): Promise> => { + return new Promise((resolve, reject) => { + http.get('/api/client', { + params: { + type: onlyAdmin ? 'admin' : undefined, + 'filter[name]': query, + page, + }, + }) + .then(({ data }) => resolve({ + items: (data.data || []).map((datum: any) => rawDataToServerObject(datum)), + pagination: getPaginationSet(data.meta.pagination), + })) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/getSystemPermissions.ts b/resources/scripts/api/getSystemPermissions.ts new file mode 100644 index 00000000..0e7f27ca --- /dev/null +++ b/resources/scripts/api/getSystemPermissions.ts @@ -0,0 +1,10 @@ +import { PanelPermissions } from '@/state/permissions'; +import http from '@/api/http'; + +export default (): Promise => { + return new Promise((resolve, reject) => { + http.get('/api/client/permissions') + .then(({ data }) => resolve(data.attributes.permissions)) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/http.ts b/resources/scripts/api/http.ts new file mode 100644 index 00000000..a642bb16 --- /dev/null +++ b/resources/scripts/api/http.ts @@ -0,0 +1,113 @@ +import axios, { AxiosInstance } from 'axios'; +import { store } from '@/state'; + +const http: AxiosInstance = axios.create({ + timeout: 20000, + headers: { + 'X-Requested-With': 'XMLHttpRequest', + Accept: 'application/json', + 'Content-Type': 'application/json', + 'X-CSRF-Token': (window as any).X_CSRF_TOKEN as string || '', + }, +}); + +http.interceptors.request.use(req => { + if (!req.url?.endsWith('/resources') && (req.url?.indexOf('_debugbar') || -1) < 0) { + store.getActions().progress.startContinuous(); + } + + return req; +}); + +http.interceptors.response.use(resp => { + if (!resp.request?.url?.endsWith('/resources') && (resp.request?.url?.indexOf('_debugbar') || -1) < 0) { + store.getActions().progress.setComplete(); + } + + return resp; +}, error => { + store.getActions().progress.setComplete(); + + throw error; +}); + +// If we have a phpdebugbar instance registered at this point in time go +// ahead and route the response data through to it so things show up. +// @ts-ignore +if (typeof window.phpdebugbar !== 'undefined') { + http.interceptors.response.use(response => { + // @ts-ignore + window.phpdebugbar.ajaxHandler.handle(response.request); + + return response; + }); +} + +export default http; + +/** + * Converts an error into a human readable response. Mostly just a generic helper to + * make sure we display the message from the server back to the user if we can. + */ +export function httpErrorToHuman (error: any): string { + if (error.response && error.response.data) { + let { data } = error.response; + + // Some non-JSON requests can still return the error as a JSON block. In those cases, attempt + // to parse it into JSON so we can display an actual error. + if (typeof data === 'string') { + try { + data = JSON.parse(data); + } catch (e) { + // do nothing, bad json + } + } + + if (data.errors && data.errors[0] && data.errors[0].detail) { + return data.errors[0].detail; + } + + // Errors from wings directory, mostly just for file uploads. + if (data.error && typeof data.error === 'string') { + return data.error; + } + } + + return error.message; +} + +export interface FractalResponseData { + object: string; + attributes: { + [k: string]: any; + relationships?: Record; + }; +} + +export interface FractalResponseList { + object: 'list'; + data: FractalResponseData[]; +} + +export interface PaginatedResult { + items: T[]; + pagination: PaginationDataSet; +} + +interface PaginationDataSet { + total: number; + count: number; + perPage: number; + currentPage: number; + totalPages: number; +} + +export function getPaginationSet (data: any): PaginationDataSet { + return { + total: data.total, + count: data.count, + perPage: data.per_page, + currentPage: data.current_page, + totalPages: data.total_pages, + }; +} diff --git a/resources/scripts/api/server/backups/createServerBackup.ts b/resources/scripts/api/server/backups/createServerBackup.ts new file mode 100644 index 00000000..a27d5d14 --- /dev/null +++ b/resources/scripts/api/server/backups/createServerBackup.ts @@ -0,0 +1,13 @@ +import http from '@/api/http'; +import { ServerBackup } from '@/api/server/types'; +import { rawDataToServerBackup } from '@/api/transformers'; + +export default (uuid: string, name?: string, ignored?: string): Promise => { + return new Promise((resolve, reject) => { + http.post(`/api/client/servers/${uuid}/backups`, { + name, ignored, + }) + .then(({ data }) => resolve(rawDataToServerBackup(data))) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/server/backups/deleteBackup.ts b/resources/scripts/api/server/backups/deleteBackup.ts new file mode 100644 index 00000000..01f48d23 --- /dev/null +++ b/resources/scripts/api/server/backups/deleteBackup.ts @@ -0,0 +1,9 @@ +import http from '@/api/http'; + +export default (uuid: string, backup: string): Promise => { + return new Promise((resolve, reject) => { + http.delete(`/api/client/servers/${uuid}/backups/${backup}`) + .then(() => resolve()) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/server/backups/getBackupDownloadUrl.ts b/resources/scripts/api/server/backups/getBackupDownloadUrl.ts new file mode 100644 index 00000000..70a3ae5e --- /dev/null +++ b/resources/scripts/api/server/backups/getBackupDownloadUrl.ts @@ -0,0 +1,9 @@ +import http from '@/api/http'; + +export default (uuid: string, backup: string): Promise => { + return new Promise((resolve, reject) => { + http.get(`/api/client/servers/${uuid}/backups/${backup}/download`) + .then(({ data }) => resolve(data.attributes.url)) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/server/createServerDatabase.ts b/resources/scripts/api/server/createServerDatabase.ts new file mode 100644 index 00000000..90103337 --- /dev/null +++ b/resources/scripts/api/server/createServerDatabase.ts @@ -0,0 +1,15 @@ +import { rawDataToServerDatabase, ServerDatabase } from '@/api/server/getServerDatabases'; +import http from '@/api/http'; + +export default (uuid: string, data: { connectionsFrom: string; databaseName: string }): Promise => { + return new Promise((resolve, reject) => { + http.post(`/api/client/servers/${uuid}/databases`, { + database: data.databaseName, + remote: data.connectionsFrom, + }, { + params: { include: 'password' }, + }) + .then(response => resolve(rawDataToServerDatabase(response.data.attributes))) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/server/deleteServerDatabase.ts b/resources/scripts/api/server/deleteServerDatabase.ts new file mode 100644 index 00000000..23275bd3 --- /dev/null +++ b/resources/scripts/api/server/deleteServerDatabase.ts @@ -0,0 +1,9 @@ +import http from '@/api/http'; + +export default (uuid: string, database: string): Promise => { + return new Promise((resolve, reject) => { + http.delete(`/api/client/servers/${uuid}/databases/${database}`) + .then(() => resolve()) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/server/files/compressFiles.ts b/resources/scripts/api/server/files/compressFiles.ts new file mode 100644 index 00000000..4204f088 --- /dev/null +++ b/resources/scripts/api/server/files/compressFiles.ts @@ -0,0 +1,12 @@ +import { FileObject } from '@/api/server/files/loadDirectory'; +import http from '@/api/http'; +import { rawDataToFileObject } from '@/api/transformers'; + +export default async (uuid: string, directory: string, files: string[]): Promise => { + const { data } = await http.post(`/api/client/servers/${uuid}/files/compress`, { root: directory, files }, { + timeout: 60000, + timeoutErrorMessage: 'It looks like this archive is taking a long time to generate. It will appear once completed.', + }); + + return rawDataToFileObject(data); +}; diff --git a/resources/scripts/api/server/files/copyFile.ts b/resources/scripts/api/server/files/copyFile.ts new file mode 100644 index 00000000..b19525c4 --- /dev/null +++ b/resources/scripts/api/server/files/copyFile.ts @@ -0,0 +1,9 @@ +import http from '@/api/http'; + +export default (uuid: string, location: string): Promise => { + return new Promise((resolve, reject) => { + http.post(`/api/client/servers/${uuid}/files/copy`, { location }) + .then(() => resolve()) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/server/files/createDirectory.ts b/resources/scripts/api/server/files/createDirectory.ts new file mode 100644 index 00000000..58886865 --- /dev/null +++ b/resources/scripts/api/server/files/createDirectory.ts @@ -0,0 +1,9 @@ +import http from '@/api/http'; + +export default (uuid: string, root: string, name: string): Promise => { + return new Promise((resolve, reject) => { + http.post(`/api/client/servers/${uuid}/files/create-folder`, { root, name }) + .then(() => resolve()) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/server/files/decompressFiles.ts b/resources/scripts/api/server/files/decompressFiles.ts new file mode 100644 index 00000000..d674eadb --- /dev/null +++ b/resources/scripts/api/server/files/decompressFiles.ts @@ -0,0 +1,8 @@ +import http from '@/api/http'; + +export default async (uuid: string, directory: string, file: string): Promise => { + await http.post(`/api/client/servers/${uuid}/files/decompress`, { root: directory, file }, { + timeout: 300000, + timeoutErrorMessage: 'It looks like this archive is taking a long time to be unarchived. Once completed the unarchived files will appear.', + }); +}; diff --git a/resources/scripts/api/server/files/deleteFiles.ts b/resources/scripts/api/server/files/deleteFiles.ts new file mode 100644 index 00000000..1250463e --- /dev/null +++ b/resources/scripts/api/server/files/deleteFiles.ts @@ -0,0 +1,9 @@ +import http from '@/api/http'; + +export default (uuid: string, directory: string, files: string[]): Promise => { + return new Promise((resolve, reject) => { + http.post(`/api/client/servers/${uuid}/files/delete`, { root: directory, files }) + .then(() => resolve()) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/server/files/getFileContents.ts b/resources/scripts/api/server/files/getFileContents.ts new file mode 100644 index 00000000..ef25b1db --- /dev/null +++ b/resources/scripts/api/server/files/getFileContents.ts @@ -0,0 +1,13 @@ +import http from '@/api/http'; + +export default (server: string, file: string): Promise => { + return new Promise((resolve, reject) => { + http.get(`/api/client/servers/${server}/files/contents`, { + params: { file }, + transformResponse: res => res, + responseType: 'text', + }) + .then(({ data }) => resolve(data)) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/server/files/getFileDownloadUrl.ts b/resources/scripts/api/server/files/getFileDownloadUrl.ts new file mode 100644 index 00000000..39db9729 --- /dev/null +++ b/resources/scripts/api/server/files/getFileDownloadUrl.ts @@ -0,0 +1,9 @@ +import http from '@/api/http'; + +export default (uuid: string, file: string): Promise => { + return new Promise((resolve, reject) => { + http.get(`/api/client/servers/${uuid}/files/download`, { params: { file } }) + .then(({ data }) => resolve(data.attributes.url)) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/server/files/getFileUploadUrl.ts b/resources/scripts/api/server/files/getFileUploadUrl.ts new file mode 100644 index 00000000..690e8587 --- /dev/null +++ b/resources/scripts/api/server/files/getFileUploadUrl.ts @@ -0,0 +1,9 @@ +import http from '@/api/http'; + +export default (uuid: string): Promise => { + return new Promise((resolve, reject) => { + http.get(`/api/client/servers/${uuid}/files/upload`) + .then(({ data }) => resolve(data.attributes.url)) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/server/files/loadDirectory.ts b/resources/scripts/api/server/files/loadDirectory.ts new file mode 100644 index 00000000..1caf6bf9 --- /dev/null +++ b/resources/scripts/api/server/files/loadDirectory.ts @@ -0,0 +1,24 @@ +import http from '@/api/http'; +import { rawDataToFileObject } from '@/api/transformers'; + +export interface FileObject { + key: string; + name: string; + mode: string; + size: number; + isFile: boolean; + isSymlink: boolean; + mimetype: string; + createdAt: Date; + modifiedAt: Date; + isArchiveType: () => boolean; + isEditable: () => boolean; +} + +export default async (uuid: string, directory?: string): Promise => { + const { data } = await http.get(`/api/client/servers/${uuid}/files/list`, { + params: { directory }, + }); + + return (data.data || []).map(rawDataToFileObject); +}; diff --git a/resources/scripts/api/server/files/renameFiles.ts b/resources/scripts/api/server/files/renameFiles.ts new file mode 100644 index 00000000..53f92c4c --- /dev/null +++ b/resources/scripts/api/server/files/renameFiles.ts @@ -0,0 +1,14 @@ +import http from '@/api/http'; + +interface Data { + to: string; + from: string; +} + +export default (uuid: string, directory: string, files: Data[]): Promise => { + return new Promise((resolve, reject) => { + http.put(`/api/client/servers/${uuid}/files/rename`, { root: directory, files }) + .then(() => resolve()) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/server/files/saveFileContents.ts b/resources/scripts/api/server/files/saveFileContents.ts new file mode 100644 index 00000000..22f1766c --- /dev/null +++ b/resources/scripts/api/server/files/saveFileContents.ts @@ -0,0 +1,18 @@ +import http from '@/api/http'; + +export default (uuid: string, file: string, content: string): Promise => { + return new Promise((resolve, reject) => { + http.post( + `/api/client/servers/${uuid}/files/write`, + content, + { + params: { file }, + headers: { + 'Content-Type': 'text/plain', + }, + }, + ) + .then(() => resolve()) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/server/getServer.ts b/resources/scripts/api/server/getServer.ts new file mode 100644 index 00000000..278b21e1 --- /dev/null +++ b/resources/scripts/api/server/getServer.ts @@ -0,0 +1,73 @@ +import http, { FractalResponseData, FractalResponseList } from '@/api/http'; +import { rawDataToServerAllocation, rawDataToServerEggVariable } from '@/api/transformers'; +import { ServerEggVariable } from '@/api/server/types'; + +export interface Allocation { + id: number; + ip: string; + alias: string | null; + port: number; + notes: string | null; + isDefault: boolean; +} + +export interface Server { + id: string; + uuid: string; + name: string; + node: string; + sftpDetails: { + ip: string; + port: number; + }; + invocation: string; + description: string; + limits: { + memory: number; + swap: number; + disk: number; + io: number; + cpu: number; + threads: string; + }; + featureLimits: { + databases: number; + allocations: number; + backups: number; + }; + isSuspended: boolean; + isInstalling: boolean; + variables: ServerEggVariable[]; + allocations: Allocation[]; +} + +export const rawDataToServerObject = ({ attributes: data }: FractalResponseData): Server => ({ + id: data.identifier, + uuid: data.uuid, + name: data.name, + node: data.node, + invocation: data.invocation, + sftpDetails: { + ip: data.sftp_details.ip, + port: data.sftp_details.port, + }, + description: data.description ? ((data.description.length > 0) ? data.description : null) : null, + limits: { ...data.limits }, + featureLimits: { ...data.feature_limits }, + isSuspended: data.is_suspended, + isInstalling: data.is_installing, + variables: ((data.relationships?.variables as FractalResponseList | undefined)?.data || []).map(rawDataToServerEggVariable), + allocations: ((data.relationships?.allocations as FractalResponseList | undefined)?.data || []).map(rawDataToServerAllocation), +}); + +export default (uuid: string): Promise<[ Server, string[] ]> => { + return new Promise((resolve, reject) => { + http.get(`/api/client/servers/${uuid}`) + .then(({ data }) => resolve([ + rawDataToServerObject(data), + // eslint-disable-next-line camelcase + data.meta?.is_server_owner ? [ '*' ] : (data.meta?.user_permissions || []), + ])) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/server/getServerDatabases.ts b/resources/scripts/api/server/getServerDatabases.ts new file mode 100644 index 00000000..cf7c9037 --- /dev/null +++ b/resources/scripts/api/server/getServerDatabases.ts @@ -0,0 +1,31 @@ +import http from '@/api/http'; + +export interface ServerDatabase { + id: string; + name: string; + username: string; + connectionString: string; + allowConnectionsFrom: string; + password?: string; +} + +export const rawDataToServerDatabase = (data: any): ServerDatabase => ({ + id: data.id, + name: data.name, + username: data.username, + connectionString: `${data.host.address}:${data.host.port}`, + allowConnectionsFrom: data.connections_from, + password: data.relationships && data.relationships.password ? data.relationships.password.attributes.password : undefined, +}); + +export default (uuid: string, includePassword = true): Promise => { + return new Promise((resolve, reject) => { + http.get(`/api/client/servers/${uuid}/databases`, { + params: includePassword ? { include: 'password' } : undefined, + }) + .then(response => resolve( + (response.data.data || []).map((item: any) => rawDataToServerDatabase(item.attributes)) + )) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/server/getServerResourceUsage.ts b/resources/scripts/api/server/getServerResourceUsage.ts new file mode 100644 index 00000000..6b71dcf5 --- /dev/null +++ b/resources/scripts/api/server/getServerResourceUsage.ts @@ -0,0 +1,29 @@ +import http from '@/api/http'; + +export type ServerPowerState = 'offline' | 'starting' | 'running' | 'stopping'; + +export interface ServerStats { + status: ServerPowerState; + isSuspended: boolean; + memoryUsageInBytes: number; + cpuUsagePercent: number; + diskUsageInBytes: number; + networkRxInBytes: number; + networkTxInBytes: number; +} + +export default (server: string): Promise => { + return new Promise((resolve, reject) => { + http.get(`/api/client/servers/${server}/resources`) + .then(({ data: { attributes } }) => resolve({ + status: attributes.current_state, + isSuspended: attributes.is_suspended, + memoryUsageInBytes: attributes.resources.memory_bytes, + cpuUsagePercent: attributes.resources.cpu_absolute, + diskUsageInBytes: attributes.resources.disk_bytes, + networkRxInBytes: attributes.resources.network_rx_bytes, + networkTxInBytes: attributes.resources.network_tx_bytes, + })) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/server/getWebsocketToken.ts b/resources/scripts/api/server/getWebsocketToken.ts new file mode 100644 index 00000000..da78dfd0 --- /dev/null +++ b/resources/scripts/api/server/getWebsocketToken.ts @@ -0,0 +1,17 @@ +import http from '@/api/http'; + +interface Response { + token: string; + socket: string; +} + +export default (server: string): Promise => { + return new Promise((resolve, reject) => { + http.get(`/api/client/servers/${server}/websocket`) + .then(({ data }) => resolve({ + token: data.data.token, + socket: data.data.socket, + })) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/server/network/deleteServerAllocation.ts b/resources/scripts/api/server/network/deleteServerAllocation.ts new file mode 100644 index 00000000..92fd4b30 --- /dev/null +++ b/resources/scripts/api/server/network/deleteServerAllocation.ts @@ -0,0 +1,4 @@ +import { Allocation } from '@/api/server/getServer'; +import http from '@/api/http'; + +export default async (uuid: string, id: number): Promise => await http.delete(`/api/client/servers/${uuid}/network/allocations/${id}`); diff --git a/resources/scripts/api/server/network/getServerAllocations.ts b/resources/scripts/api/server/network/getServerAllocations.ts new file mode 100644 index 00000000..7309bd26 --- /dev/null +++ b/resources/scripts/api/server/network/getServerAllocations.ts @@ -0,0 +1,9 @@ +import http from '@/api/http'; +import { rawDataToServerAllocation } from '@/api/transformers'; +import { Allocation } from '@/api/server/getServer'; + +export default async (uuid: string): Promise => { + const { data } = await http.get(`/api/client/servers/${uuid}/network/allocations`); + + return (data.data || []).map(rawDataToServerAllocation); +}; diff --git a/resources/scripts/api/server/network/setPrimaryServerAllocation.ts b/resources/scripts/api/server/network/setPrimaryServerAllocation.ts new file mode 100644 index 00000000..27c09b72 --- /dev/null +++ b/resources/scripts/api/server/network/setPrimaryServerAllocation.ts @@ -0,0 +1,9 @@ +import { Allocation } from '@/api/server/getServer'; +import http from '@/api/http'; +import { rawDataToServerAllocation } from '@/api/transformers'; + +export default async (uuid: string, id: number): Promise => { + const { data } = await http.post(`/api/client/servers/${uuid}/network/allocations/${id}/primary`); + + return rawDataToServerAllocation(data); +}; diff --git a/resources/scripts/api/server/network/setServerAllocationNotes.ts b/resources/scripts/api/server/network/setServerAllocationNotes.ts new file mode 100644 index 00000000..4531dc75 --- /dev/null +++ b/resources/scripts/api/server/network/setServerAllocationNotes.ts @@ -0,0 +1,9 @@ +import { Allocation } from '@/api/server/getServer'; +import http from '@/api/http'; +import { rawDataToServerAllocation } from '@/api/transformers'; + +export default async (uuid: string, id: number, notes: string | null): Promise => { + const { data } = await http.post(`/api/client/servers/${uuid}/network/allocations/${id}`, { notes }); + + return rawDataToServerAllocation(data); +}; diff --git a/resources/scripts/api/server/reinstallServer.ts b/resources/scripts/api/server/reinstallServer.ts new file mode 100644 index 00000000..5cb2ca5e --- /dev/null +++ b/resources/scripts/api/server/reinstallServer.ts @@ -0,0 +1,9 @@ +import http from '@/api/http'; + +export default (uuid: string): Promise => { + return new Promise((resolve, reject) => { + http.post(`/api/client/servers/${uuid}/settings/reinstall`) + .then(() => resolve()) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/server/renameServer.ts b/resources/scripts/api/server/renameServer.ts new file mode 100644 index 00000000..a4a95141 --- /dev/null +++ b/resources/scripts/api/server/renameServer.ts @@ -0,0 +1,9 @@ +import http from '@/api/http'; + +export default (uuid: string, name: string): Promise => { + return new Promise((resolve, reject) => { + http.post(`/api/client/servers/${uuid}/settings/rename`, { name }) + .then(() => resolve()) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/server/rotateDatabasePassword.ts b/resources/scripts/api/server/rotateDatabasePassword.ts new file mode 100644 index 00000000..c6c9e8ae --- /dev/null +++ b/resources/scripts/api/server/rotateDatabasePassword.ts @@ -0,0 +1,10 @@ +import { rawDataToServerDatabase, ServerDatabase } from '@/api/server/getServerDatabases'; +import http from '@/api/http'; + +export default (uuid: string, database: string): Promise => { + return new Promise((resolve, reject) => { + http.post(`/api/client/servers/${uuid}/databases/${database}/rotate-password`) + .then((response) => resolve(rawDataToServerDatabase(response.data.attributes))) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/server/schedules/createOrUpdateSchedule.ts b/resources/scripts/api/server/schedules/createOrUpdateSchedule.ts new file mode 100644 index 00000000..0545650b --- /dev/null +++ b/resources/scripts/api/server/schedules/createOrUpdateSchedule.ts @@ -0,0 +1,19 @@ +import { rawDataToServerSchedule, Schedule } from '@/api/server/schedules/getServerSchedules'; +import http from '@/api/http'; + +type Data = Pick & { id?: number } + +export default (uuid: string, schedule: Data): Promise => { + return new Promise((resolve, reject) => { + http.post(`/api/client/servers/${uuid}/schedules${schedule.id ? `/${schedule.id}` : ''}`, { + is_active: schedule.isActive, + name: schedule.name, + minute: schedule.cron.minute, + hour: schedule.cron.hour, + day_of_month: schedule.cron.dayOfMonth, + day_of_week: schedule.cron.dayOfWeek, + }) + .then(({ data }) => resolve(rawDataToServerSchedule(data.attributes))) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/server/schedules/createOrUpdateScheduleTask.ts b/resources/scripts/api/server/schedules/createOrUpdateScheduleTask.ts new file mode 100644 index 00000000..c0d7fbbe --- /dev/null +++ b/resources/scripts/api/server/schedules/createOrUpdateScheduleTask.ts @@ -0,0 +1,19 @@ +import { rawDataToServerTask, Task } from '@/api/server/schedules/getServerSchedules'; +import http from '@/api/http'; + +interface Data { + action: string; + payload: string; + timeOffset: string | number; +} + +export default (uuid: string, schedule: number, task: number | undefined, { timeOffset, ...data }: Data): Promise => { + return new Promise((resolve, reject) => { + http.post(`/api/client/servers/${uuid}/schedules/${schedule}/tasks${task ? `/${task}` : ''}`, { + ...data, + time_offset: timeOffset, + }) + .then(({ data }) => resolve(rawDataToServerTask(data.attributes))) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/server/schedules/deleteSchedule.ts b/resources/scripts/api/server/schedules/deleteSchedule.ts new file mode 100644 index 00000000..c3669988 --- /dev/null +++ b/resources/scripts/api/server/schedules/deleteSchedule.ts @@ -0,0 +1,9 @@ +import http from '@/api/http'; + +export default (uuid: string, schedule: number): Promise => { + return new Promise((resolve, reject) => { + http.delete(`/api/client/servers/${uuid}/schedules/${schedule}`) + .then(() => resolve()) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/server/schedules/deleteScheduleTask.ts b/resources/scripts/api/server/schedules/deleteScheduleTask.ts new file mode 100644 index 00000000..8867677b --- /dev/null +++ b/resources/scripts/api/server/schedules/deleteScheduleTask.ts @@ -0,0 +1,9 @@ +import http from '@/api/http'; + +export default (uuid: string, scheduleId: number, taskId: number): Promise => { + return new Promise((resolve, reject) => { + http.delete(`/api/client/servers/${uuid}/schedules/${scheduleId}/tasks/${taskId}`) + .then(() => resolve()) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/server/schedules/getServerSchedule.ts b/resources/scripts/api/server/schedules/getServerSchedule.ts new file mode 100644 index 00000000..63e3d6f9 --- /dev/null +++ b/resources/scripts/api/server/schedules/getServerSchedule.ts @@ -0,0 +1,14 @@ +import http from '@/api/http'; +import { rawDataToServerSchedule, Schedule } from '@/api/server/schedules/getServerSchedules'; + +export default (uuid: string, schedule: number): Promise => { + return new Promise((resolve, reject) => { + http.get(`/api/client/servers/${uuid}/schedules/${schedule}`, { + params: { + include: [ 'tasks' ], + }, + }) + .then(({ data }) => resolve(rawDataToServerSchedule(data.attributes))) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/server/schedules/getServerSchedules.tsx b/resources/scripts/api/server/schedules/getServerSchedules.tsx new file mode 100644 index 00000000..42514582 --- /dev/null +++ b/resources/scripts/api/server/schedules/getServerSchedules.tsx @@ -0,0 +1,73 @@ +import http from '@/api/http'; + +export interface Schedule { + id: number; + name: string; + cron: { + dayOfWeek: string; + dayOfMonth: string; + hour: string; + minute: string; + }; + isActive: boolean; + isProcessing: boolean; + lastRunAt: Date | null; + nextRunAt: Date | null; + createdAt: Date; + updatedAt: Date; + + tasks: Task[]; +} + +export interface Task { + id: number; + sequenceId: number; + action: string; + payload: string; + timeOffset: number; + isQueued: boolean; + createdAt: Date; + updatedAt: Date; +} + +export const rawDataToServerTask = (data: any): Task => ({ + id: data.id, + sequenceId: data.sequence_id, + action: data.action, + payload: data.payload, + timeOffset: data.time_offset, + isQueued: data.is_queued, + createdAt: new Date(data.created_at), + updatedAt: new Date(data.updated_at), +}); + +export const rawDataToServerSchedule = (data: any): Schedule => ({ + id: data.id, + name: data.name, + cron: { + dayOfWeek: data.cron.day_of_week, + dayOfMonth: data.cron.day_of_month, + hour: data.cron.hour, + minute: data.cron.minute, + }, + isActive: data.is_active, + isProcessing: data.is_processing, + lastRunAt: data.last_run_at ? new Date(data.last_run_at) : null, + nextRunAt: data.next_run_at ? new Date(data.next_run_at) : null, + createdAt: new Date(data.created_at), + updatedAt: new Date(data.updated_at), + + tasks: (data.relationships?.tasks?.data || []).map((row: any) => rawDataToServerTask(row.attributes)), +}); + +export default (uuid: string): Promise => { + return new Promise((resolve, reject) => { + http.get(`/api/client/servers/${uuid}/schedules`, { + params: { + include: [ 'tasks' ], + }, + }) + .then(({ data }) => resolve((data.data || []).map((row: any) => rawDataToServerSchedule(row.attributes)))) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/server/types.d.ts b/resources/scripts/api/server/types.d.ts new file mode 100644 index 00000000..b37fae40 --- /dev/null +++ b/resources/scripts/api/server/types.d.ts @@ -0,0 +1,20 @@ +export interface ServerBackup { + uuid: string; + isSuccessful: boolean; + name: string; + ignoredFiles: string; + checksum: string; + bytes: number; + createdAt: Date; + completedAt: Date | null; +} + +export interface ServerEggVariable { + name: string; + description: string; + envVariable: string; + defaultValue: string; + serverValue: string; + isEditable: boolean; + rules: string[]; +} diff --git a/resources/scripts/api/server/updateStartupVariable.ts b/resources/scripts/api/server/updateStartupVariable.ts new file mode 100644 index 00000000..74498caa --- /dev/null +++ b/resources/scripts/api/server/updateStartupVariable.ts @@ -0,0 +1,9 @@ +import http from '@/api/http'; +import { ServerEggVariable } from '@/api/server/types'; +import { rawDataToServerEggVariable } from '@/api/transformers'; + +export default async (uuid: string, key: string, value: string): Promise<[ ServerEggVariable, string ]> => { + const { data } = await http.put(`/api/client/servers/${uuid}/startup/variable`, { key, value }); + + return [ rawDataToServerEggVariable(data), data.meta.startup_command ]; +}; diff --git a/resources/scripts/api/server/users/createOrUpdateSubuser.ts b/resources/scripts/api/server/users/createOrUpdateSubuser.ts new file mode 100644 index 00000000..93303a2d --- /dev/null +++ b/resources/scripts/api/server/users/createOrUpdateSubuser.ts @@ -0,0 +1,18 @@ +import http from '@/api/http'; +import { rawDataToServerSubuser } from '@/api/server/users/getServerSubusers'; +import { Subuser } from '@/state/server/subusers'; + +interface Params { + email: string; + permissions: string[]; +} + +export default (uuid: string, params: Params, subuser?: Subuser): Promise => { + return new Promise((resolve, reject) => { + http.post(`/api/client/servers/${uuid}/users${subuser ? `/${subuser.uuid}` : ''}`, { + ...params, + }) + .then(data => resolve(rawDataToServerSubuser(data.data))) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/server/users/deleteSubuser.ts b/resources/scripts/api/server/users/deleteSubuser.ts new file mode 100644 index 00000000..dccd98e6 --- /dev/null +++ b/resources/scripts/api/server/users/deleteSubuser.ts @@ -0,0 +1,9 @@ +import http from '@/api/http'; + +export default (uuid: string, userId: string): Promise => { + return new Promise((resolve, reject) => { + http.delete(`/api/client/servers/${uuid}/users/${userId}`) + .then(() => resolve()) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/server/users/getServerSubusers.ts b/resources/scripts/api/server/users/getServerSubusers.ts new file mode 100644 index 00000000..177bde81 --- /dev/null +++ b/resources/scripts/api/server/users/getServerSubusers.ts @@ -0,0 +1,21 @@ +import http, { FractalResponseData } from '@/api/http'; +import { Subuser } from '@/state/server/subusers'; + +export const rawDataToServerSubuser = (data: FractalResponseData): Subuser => ({ + uuid: data.attributes.uuid, + username: data.attributes.username, + email: data.attributes.email, + image: data.attributes.image, + twoFactorEnabled: data.attributes['2fa_enabled'], + createdAt: new Date(data.attributes.created_at), + permissions: data.attributes.permissions || [], + can: permission => (data.attributes.permissions || []).indexOf(permission) >= 0, +}); + +export default (uuid: string): Promise => { + return new Promise((resolve, reject) => { + http.get(`/api/client/servers/${uuid}/users`) + .then(({ data }) => resolve((data.data || []).map(rawDataToServerSubuser))) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/swr/getServerBackups.ts b/resources/scripts/api/swr/getServerBackups.ts new file mode 100644 index 00000000..0c38cd27 --- /dev/null +++ b/resources/scripts/api/swr/getServerBackups.ts @@ -0,0 +1,18 @@ +import useSWR from 'swr'; +import http, { getPaginationSet, PaginatedResult } from '@/api/http'; +import { ServerBackup } from '@/api/server/types'; +import { rawDataToServerBackup } from '@/api/transformers'; +import { ServerContext } from '@/state/server'; + +export default (page?: number | string) => { + const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); + + return useSWR>([ 'server:backups', uuid, page ], async () => { + const { data } = await http.get(`/api/client/servers/${uuid}/backups`, { params: { page } }); + + return ({ + items: (data.data || []).map(rawDataToServerBackup), + pagination: getPaginationSet(data.meta.pagination), + }); + }); +}; diff --git a/resources/scripts/api/swr/getServerStartup.ts b/resources/scripts/api/swr/getServerStartup.ts new file mode 100644 index 00000000..892f78fd --- /dev/null +++ b/resources/scripts/api/swr/getServerStartup.ts @@ -0,0 +1,17 @@ +import useSWR from 'swr'; +import http, { FractalResponseList } from '@/api/http'; +import { rawDataToServerEggVariable } from '@/api/transformers'; +import { ServerEggVariable } from '@/api/server/types'; + +interface Response { + invocation: string; + variables: ServerEggVariable[]; +} + +export default (uuid: string, initialData?: Response) => useSWR([ uuid, '/startup' ], async (): Promise => { + const { data } = await http.get(`/api/client/servers/${uuid}/startup`); + + const variables = ((data as FractalResponseList).data || []).map(rawDataToServerEggVariable); + + return { invocation: data.meta.startup_command, variables }; +}, { initialData, errorRetryCount: 3 }); diff --git a/resources/scripts/api/transformers.ts b/resources/scripts/api/transformers.ts new file mode 100644 index 00000000..f17787e0 --- /dev/null +++ b/resources/scripts/api/transformers.ts @@ -0,0 +1,75 @@ +import { Allocation } from '@/api/server/getServer'; +import { FractalResponseData } from '@/api/http'; +import { FileObject } from '@/api/server/files/loadDirectory'; +import { ServerBackup, ServerEggVariable } from '@/api/server/types'; + +export const rawDataToServerAllocation = (data: FractalResponseData): Allocation => ({ + id: data.attributes.id, + ip: data.attributes.ip, + alias: data.attributes.ip_alias, + port: data.attributes.port, + notes: data.attributes.notes, + isDefault: data.attributes.is_default, +}); + +export const rawDataToFileObject = (data: FractalResponseData): FileObject => ({ + key: `${data.attributes.is_file ? 'file' : 'dir'}_${data.attributes.name}`, + name: data.attributes.name, + mode: data.attributes.mode, + size: Number(data.attributes.size), + isFile: data.attributes.is_file, + isSymlink: data.attributes.is_symlink, + mimetype: data.attributes.mimetype, + createdAt: new Date(data.attributes.created_at), + modifiedAt: new Date(data.attributes.modified_at), + + isArchiveType: function () { + return this.isFile && [ + 'application/vnd.rar', // .rar + 'application/x-rar-compressed', // .rar (2) + 'application/x-tar', // .tar + 'application/x-br', // .tar.br + 'application/x-bzip2', // .tar.bz2, .bz2 + 'application/gzip', // .tar.gz, .gz + 'application/x-lzip', // .tar.lz4, .lz4 (not sure if this mime type is correct) + 'application/x-sz', // .tar.sz, .sz (not sure if this mime type is correct) + 'application/x-xz', // .tar.xz, .xz + 'application/zstd', // .tar.zst, .zst + 'application/zip', // .zip + ].indexOf(this.mimetype) >= 0; + }, + + isEditable: function () { + if (this.isArchiveType() || !this.isFile) return false; + + const matches = [ + 'application/jar', + 'application/octet-stream', + 'inode/directory', + /^image\//, + ]; + + return matches.every(m => !this.mimetype.match(m)); + }, +}); + +export const rawDataToServerBackup = ({ attributes }: FractalResponseData): ServerBackup => ({ + uuid: attributes.uuid, + isSuccessful: attributes.is_successful, + name: attributes.name, + ignoredFiles: attributes.ignored_files, + checksum: attributes.checksum, + bytes: attributes.bytes, + createdAt: new Date(attributes.created_at), + completedAt: attributes.completed_at ? new Date(attributes.completed_at) : null, +}); + +export const rawDataToServerEggVariable = ({ attributes }: FractalResponseData): ServerEggVariable => ({ + name: attributes.name, + description: attributes.description, + envVariable: attributes.env_variable, + defaultValue: attributes.default_value, + serverValue: attributes.server_value, + isEditable: attributes.is_editable, + rules: attributes.rules.split('|'), +}); diff --git a/resources/scripts/assets/css/GlobalStylesheet.ts b/resources/scripts/assets/css/GlobalStylesheet.ts new file mode 100644 index 00000000..a38dff74 --- /dev/null +++ b/resources/scripts/assets/css/GlobalStylesheet.ts @@ -0,0 +1,72 @@ +import tw from 'twin.macro'; +import { createGlobalStyle } from 'styled-components/macro'; + +export default createGlobalStyle` + body { + ${tw`font-sans bg-neutral-800 text-neutral-200`}; + letter-spacing: 0.015em; + } + + h1, h2, h3, h4, h5, h6 { + ${tw`font-medium tracking-normal font-header`}; + } + + p { + ${tw`text-neutral-200 leading-snug font-sans`}; + } + + form { + ${tw`m-0`}; + } + + textarea, select, input, button, button:focus, button:focus-visible { + ${tw`outline-none`}; + } + + input[type=number]::-webkit-outer-spin-button, + input[type=number]::-webkit-inner-spin-button { + -webkit-appearance: none !important; + margin: 0; + } + + input[type=number] { + -moz-appearance: textfield !important; + } + + /* Scroll Bar Style */ + ::-webkit-scrollbar { + background: none; + width: 16px; + height: 16px; + } + + ::-webkit-scrollbar-thumb { + border: solid 0 rgb(0 0 0 / 0%); + border-right-width: 4px; + border-left-width: 4px; + -webkit-border-radius: 9px 4px; + -webkit-box-shadow: inset 0 0 0 1px hsl(211, 10%, 53%), inset 0 0 0 4px hsl(209deg 18% 30%); + } + + ::-webkit-scrollbar-track-piece { + margin: 4px 0; + } + + ::-webkit-scrollbar-thumb:horizontal { + border-right-width: 0; + border-left-width: 0; + border-top-width: 4px; + border-bottom-width: 4px; + -webkit-border-radius: 4px 9px; + } + + ::-webkit-scrollbar-thumb:hover { + -webkit-box-shadow: + inset 0 0 0 1px hsl(212, 92%, 43%), + inset 0 0 0 4px hsl(212, 92%, 43%); + } + + ::-webkit-scrollbar-corner { + background: transparent; + } +`; diff --git a/resources/scripts/components/App.tsx b/resources/scripts/components/App.tsx new file mode 100644 index 00000000..350387fa --- /dev/null +++ b/resources/scripts/components/App.tsx @@ -0,0 +1,79 @@ +import React, { useEffect } from 'react'; +import ReactGA from 'react-ga'; +import { hot } from 'react-hot-loader/root'; +import { BrowserRouter, Route, Switch } from 'react-router-dom'; +import { StoreProvider } from 'easy-peasy'; +import { store } from '@/state'; +import DashboardRouter from '@/routers/DashboardRouter'; +import ServerRouter from '@/routers/ServerRouter'; +import AuthenticationRouter from '@/routers/AuthenticationRouter'; +import { Provider } from 'react-redux'; +import { SiteSettings } from '@/state/settings'; +import ProgressBar from '@/components/elements/ProgressBar'; +import NotFound from '@/components/screens/NotFound'; +import tw from 'twin.macro'; +import GlobalStylesheet from '@/assets/css/GlobalStylesheet'; + +interface ExtendedWindow extends Window { + SiteConfiguration?: SiteSettings; + PterodactylUser?: { + uuid: string; + username: string; + email: string; + /* eslint-disable camelcase */ + root_admin: boolean; + use_totp: boolean; + language: string; + updated_at: string; + created_at: string; + /* eslint-enable camelcase */ + }; +} + +const App = () => { + const { PterodactylUser, SiteConfiguration } = (window as ExtendedWindow); + if (PterodactylUser && !store.getState().user.data) { + store.getActions().user.setUserData({ + uuid: PterodactylUser.uuid, + username: PterodactylUser.username, + email: PterodactylUser.email, + language: PterodactylUser.language, + rootAdmin: PterodactylUser.root_admin, + useTotp: PterodactylUser.use_totp, + createdAt: new Date(PterodactylUser.created_at), + updatedAt: new Date(PterodactylUser.updated_at), + }); + } + + if (!store.getState().settings.data) { + store.getActions().settings.setSettings(SiteConfiguration!); + } + + useEffect(() => { + ReactGA.initialize(SiteConfiguration!.analytics); + ReactGA.pageview(location.pathname); + }, []); + + return ( + <> + + + + +
    + + + + + + + + +
    +
    +
    + + ); +}; + +export default hot(App); diff --git a/resources/scripts/components/FlashMessageRender.tsx b/resources/scripts/components/FlashMessageRender.tsx new file mode 100644 index 00000000..6bd22f89 --- /dev/null +++ b/resources/scripts/components/FlashMessageRender.tsx @@ -0,0 +1,35 @@ +import React from 'react'; +import MessageBox from '@/components/MessageBox'; +import { useStoreState } from 'easy-peasy'; +import tw from 'twin.macro'; + +type Props = Readonly<{ + byKey?: string; + className?: string; +}>; + +const FlashMessageRender = ({ byKey, className }: Props) => { + const flashes = useStoreState(state => state.flashes.items.filter( + flash => byKey ? flash.key === byKey : true, + )); + + return ( + flashes.length ? +
    + { + flashes.map((flash, index) => ( + + {index > 0 &&
    } + + {flash.message} + +
    + )) + } +
    + : + null + ); +}; + +export default FlashMessageRender; diff --git a/resources/scripts/components/MessageBox.tsx b/resources/scripts/components/MessageBox.tsx new file mode 100644 index 00000000..e16986ed --- /dev/null +++ b/resources/scripts/components/MessageBox.tsx @@ -0,0 +1,69 @@ +import * as React from 'react'; +import tw, { TwStyle } from 'twin.macro'; +import styled from 'styled-components/macro'; + +export type FlashMessageType = 'success' | 'info' | 'warning' | 'error'; + +interface Props { + title?: string; + children: string; + type?: FlashMessageType; +} + +const styling = (type?: FlashMessageType): TwStyle | string => { + switch (type) { + case 'error': + return tw`bg-red-600 border-red-800`; + case 'info': + return tw`bg-primary-600 border-primary-800`; + case 'success': + return tw`bg-green-600 border-green-800`; + case 'warning': + return tw`bg-yellow-600 border-yellow-800`; + default: + return ''; + } +}; + +const getBackground = (type?: FlashMessageType): TwStyle | string => { + switch (type) { + case 'error': + return tw`bg-red-500`; + case 'info': + return tw`bg-primary-500`; + case 'success': + return tw`bg-green-500`; + case 'warning': + return tw`bg-yellow-500`; + default: + return ''; + } +}; + +const Container = styled.div<{ $type?: FlashMessageType }>` + ${tw`p-2 border items-center leading-normal rounded flex w-full text-sm text-white`}; + ${props => styling(props.$type)}; +`; +Container.displayName = 'MessageBox.Container'; + +const MessageBox = ({ title, children, type }: Props) => ( + + {title && + + {title} + + } + + {children} + + +); +MessageBox.displayName = 'MessageBox'; + +export default MessageBox; diff --git a/resources/scripts/components/NavigationBar.tsx b/resources/scripts/components/NavigationBar.tsx new file mode 100644 index 00000000..384a32ed --- /dev/null +++ b/resources/scripts/components/NavigationBar.tsx @@ -0,0 +1,77 @@ +import * as React from 'react'; +import { Link, NavLink } from 'react-router-dom'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faCogs, faLayerGroup, faSignOutAlt, faUserCircle } from '@fortawesome/free-solid-svg-icons'; +import { useStoreState } from 'easy-peasy'; +import { ApplicationStore } from '@/state'; +import SearchContainer from '@/components/dashboard/search/SearchContainer'; +import tw from 'twin.macro'; +import styled from 'styled-components/macro'; +// @ts-ignore +import * as config from '@/../../tailwind.config.js'; + +const Navigation = styled.div` + ${tw`w-full bg-neutral-900 shadow-md overflow-x-auto`}; + + & > div { + ${tw`mx-auto w-full flex items-center`}; + } + + & #logo { + ${tw`flex-1`}; + + & > a { + ${tw`text-2xl font-header px-4 no-underline text-neutral-200 hover:text-neutral-100 transition-colors duration-150`}; + } + } +`; + +const RightNavigation = styled.div` + ${tw`flex h-full items-center justify-center`}; + + & > a, & > .navigation-link { + ${tw`flex items-center h-full no-underline text-neutral-300 px-6 cursor-pointer transition-all duration-150`}; + + &:active, &:hover { + ${tw`text-neutral-100 bg-black`}; + } + + &:active, &:hover, &.active { + box-shadow: inset 0 -2px ${config.theme.colors.cyan['700']}; + } + } +`; + +export default () => { + const name = useStoreState((state: ApplicationStore) => state.settings.data!.name); + const rootAdmin = useStoreState((state: ApplicationStore) => state.user.data!.rootAdmin); + + return ( + +
    +
    + + {name} + +
    + + + + + + + + + {rootAdmin && + + + + } + + + + +
    +
    + ); +}; diff --git a/resources/scripts/components/auth/ForgotPasswordContainer.tsx b/resources/scripts/components/auth/ForgotPasswordContainer.tsx new file mode 100644 index 00000000..f82b76e2 --- /dev/null +++ b/resources/scripts/components/auth/ForgotPasswordContainer.tsx @@ -0,0 +1,107 @@ +import * as React from 'react'; +import { useRef, useState } from 'react'; +import { Link } from 'react-router-dom'; +import requestPasswordResetEmail from '@/api/auth/requestPasswordResetEmail'; +import { httpErrorToHuman } from '@/api/http'; +import LoginFormContainer from '@/components/auth/LoginFormContainer'; +import { useStoreState } from 'easy-peasy'; +import Field from '@/components/elements/Field'; +import { Formik, FormikHelpers } from 'formik'; +import { object, string } from 'yup'; +import tw from 'twin.macro'; +import Button from '@/components/elements/Button'; +import Reaptcha from 'reaptcha'; +import useFlash from '@/plugins/useFlash'; + +interface Values { + email: string; +} + +export default () => { + const ref = useRef(null); + const [ token, setToken ] = useState(''); + + const { clearFlashes, addFlash } = useFlash(); + const { enabled: recaptchaEnabled, siteKey } = useStoreState(state => state.settings.data!.recaptcha); + + const handleSubmission = ({ email }: Values, { setSubmitting, resetForm }: FormikHelpers) => { + clearFlashes(); + + // If there is no token in the state yet, request the token and then abort this submit request + // since it will be re-submitted when the recaptcha data is returned by the component. + if (recaptchaEnabled && !token) { + ref.current!.execute().catch(error => console.error(error)); + return; + } + + requestPasswordResetEmail(email, token) + .then(response => { + resetForm(); + addFlash({ type: 'success', title: 'Success', message: response }); + }) + .catch(error => { + console.error(error); + addFlash({ type: 'error', title: 'Error', message: httpErrorToHuman(error) }); + }) + .then(() => setSubmitting(false)); + }; + + return ( + + {({ isSubmitting, setSubmitting, submitForm }) => ( + + +
    + +
    + {recaptchaEnabled && + { + setToken(response); + submitForm(); + }} + onExpire={() => { + setSubmitting(false); + setToken(''); + }} + /> + } +
    + + Return to Login + +
    +
    + )} +
    + ); +}; diff --git a/resources/scripts/components/auth/LoginCheckpointContainer.tsx b/resources/scripts/components/auth/LoginCheckpointContainer.tsx new file mode 100644 index 00000000..f4507328 --- /dev/null +++ b/resources/scripts/components/auth/LoginCheckpointContainer.tsx @@ -0,0 +1,124 @@ +import React, { useState } from 'react'; +import { Link, RouteComponentProps } from 'react-router-dom'; +import loginCheckpoint from '@/api/auth/loginCheckpoint'; +import { httpErrorToHuman } from '@/api/http'; +import LoginFormContainer from '@/components/auth/LoginFormContainer'; +import { ActionCreator } from 'easy-peasy'; +import { StaticContext } from 'react-router'; +import { useFormikContext, withFormik } from 'formik'; +import useFlash from '@/plugins/useFlash'; +import { FlashStore } from '@/state/flashes'; +import Field from '@/components/elements/Field'; +import tw from 'twin.macro'; +import Button from '@/components/elements/Button'; + +interface Values { + code: string; + recoveryCode: '', +} + +type OwnProps = RouteComponentProps, StaticContext, { token?: string }> + +type Props = OwnProps & { + addError: ActionCreator; + clearFlashes: ActionCreator; +} + +const LoginCheckpointContainer = () => { + const { isSubmitting, setFieldValue } = useFormikContext(); + const [ isMissingDevice, setIsMissingDevice ] = useState(false); + + return ( + +
    + +
    +
    + +
    +
    + { + setFieldValue('code', ''); + setFieldValue('recoveryCode', ''); + setIsMissingDevice(s => !s); + }} + css={tw`cursor-pointer text-xs text-neutral-500 tracking-wide uppercase no-underline hover:text-neutral-700`} + > + {!isMissingDevice ? 'I\'ve Lost My Device' : 'I Have My Device'} + +
    +
    + + Return to Login + +
    +
    + ); +}; + +const EnhancedForm = withFormik({ + handleSubmit: ({ code, recoveryCode }, { setSubmitting, props: { addError, clearFlashes, location } }) => { + clearFlashes(); + + loginCheckpoint(location.state?.token || '', code, recoveryCode) + .then(response => { + if (response.complete) { + // @ts-ignore + window.location = response.intended || '/'; + return; + } + + setSubmitting(false); + }) + .catch(error => { + console.error(error); + setSubmitting(false); + addError({ message: httpErrorToHuman(error) }); + }); + }, + + mapPropsToValues: () => ({ + code: '', + recoveryCode: '', + }), +})(LoginCheckpointContainer); + +export default ({ history, location, ...props }: OwnProps) => { + const { addError, clearFlashes } = useFlash(); + + if (!location.state?.token) { + history.replace('/auth/login'); + + return null; + } + + return ; +}; diff --git a/resources/scripts/components/auth/LoginContainer.tsx b/resources/scripts/components/auth/LoginContainer.tsx new file mode 100644 index 00000000..35b2ba88 --- /dev/null +++ b/resources/scripts/components/auth/LoginContainer.tsx @@ -0,0 +1,115 @@ +import React, { useRef, useState } from 'react'; +import { Link, RouteComponentProps } from 'react-router-dom'; +import login from '@/api/auth/login'; +import LoginFormContainer from '@/components/auth/LoginFormContainer'; +import { useStoreState } from 'easy-peasy'; +import { Formik, FormikHelpers } from 'formik'; +import { object, string } from 'yup'; +import Field from '@/components/elements/Field'; +import tw from 'twin.macro'; +import Button from '@/components/elements/Button'; +import Reaptcha from 'reaptcha'; +import useFlash from '@/plugins/useFlash'; + +interface Values { + username: string; + password: string; +} + +const LoginContainer = ({ history }: RouteComponentProps) => { + const ref = useRef(null); + const [ token, setToken ] = useState(''); + + const { clearFlashes, clearAndAddHttpError } = useFlash(); + const { enabled: recaptchaEnabled, siteKey } = useStoreState(state => state.settings.data!.recaptcha); + + const onSubmit = (values: Values, { setSubmitting }: FormikHelpers) => { + clearFlashes(); + + // If there is no token in the state yet, request the token and then abort this submit request + // since it will be re-submitted when the recaptcha data is returned by the component. + if (recaptchaEnabled && !token) { + ref.current!.execute().catch(error => console.error(error)); + return; + } + + login({ ...values, recaptchaData: token }) + .then(response => { + if (response.complete) { + // @ts-ignore + window.location = response.intended || '/'; + return; + } + + history.replace('/auth/login/checkpoint', { token: response.confirmationToken }); + }) + .catch(error => { + console.error(error); + + setSubmitting(false); + clearAndAddHttpError({ error }); + }); + }; + + return ( + + {({ isSubmitting, setSubmitting, submitForm }) => ( + + +
    + +
    +
    + +
    + {recaptchaEnabled && + { + setToken(response); + submitForm(); + }} + onExpire={() => { + setSubmitting(false); + setToken(''); + }} + /> + } +
    + + Forgot password? + +
    +
    + )} +
    + ); +}; + +export default LoginContainer; diff --git a/resources/scripts/components/auth/LoginFormContainer.tsx b/resources/scripts/components/auth/LoginFormContainer.tsx new file mode 100644 index 00000000..2423e025 --- /dev/null +++ b/resources/scripts/components/auth/LoginFormContainer.tsx @@ -0,0 +1,61 @@ +import React, { forwardRef } from 'react'; +import { Form } from 'formik'; +import styled from 'styled-components/macro'; +import { breakpoint } from '@/theme'; +import FlashMessageRender from '@/components/FlashMessageRender'; +import tw from 'twin.macro'; + +type Props = React.DetailedHTMLProps, HTMLFormElement> & { + title?: string; +} + +const Container = styled.div` + ${breakpoint('sm')` + ${tw`w-4/5 mx-auto`} + `}; + + ${breakpoint('md')` + ${tw`p-10`} + `}; + + ${breakpoint('lg')` + ${tw`w-3/5`} + `}; + + ${breakpoint('xl')` + ${tw`w-full`} + max-width: 700px; + `}; +`; + +export default forwardRef(({ title, ...props }, ref) => ( + + {title && +

    + {title} +

    + } + +
    +
    +
    + +
    +
    + {props.children} +
    +
    +
    +

    + © 2015 - 2020  + + Pterodactyl Software + +

    +
    +)); diff --git a/resources/scripts/components/auth/ResetPasswordContainer.tsx b/resources/scripts/components/auth/ResetPasswordContainer.tsx new file mode 100644 index 00000000..cded2e9a --- /dev/null +++ b/resources/scripts/components/auth/ResetPasswordContainer.tsx @@ -0,0 +1,111 @@ +import React, { useState } from 'react'; +import { RouteComponentProps } from 'react-router'; +import { parse } from 'query-string'; +import { Link } from 'react-router-dom'; +import performPasswordReset from '@/api/auth/performPasswordReset'; +import { httpErrorToHuman } from '@/api/http'; +import LoginFormContainer from '@/components/auth/LoginFormContainer'; +import { Actions, useStoreActions } from 'easy-peasy'; +import { ApplicationStore } from '@/state'; +import { Formik, FormikHelpers } from 'formik'; +import { object, ref, string } from 'yup'; +import Field from '@/components/elements/Field'; +import Input from '@/components/elements/Input'; +import tw from 'twin.macro'; +import Button from '@/components/elements/Button'; + +interface Values { + password: string; + passwordConfirmation: string; +} + +export default ({ match, location }: RouteComponentProps<{ token: string }>) => { + const [ email, setEmail ] = useState(''); + + const { clearFlashes, addFlash } = useStoreActions((actions: Actions) => actions.flashes); + + const parsed = parse(location.search); + if (email.length === 0 && parsed.email) { + setEmail(parsed.email as string); + } + + const submit = ({ password, passwordConfirmation }: Values, { setSubmitting }: FormikHelpers) => { + clearFlashes(); + performPasswordReset(email, { token: match.params.token, password, passwordConfirmation }) + .then(() => { + // @ts-ignore + window.location = '/'; + }) + .catch(error => { + console.error(error); + + setSubmitting(false); + addFlash({ type: 'error', title: 'Error', message: httpErrorToHuman(error) }); + }); + }; + + return ( + + {({ isSubmitting }) => ( + +
    + + +
    +
    + +
    +
    + +
    +
    + +
    +
    + + Return to Login + +
    +
    + )} +
    + ); +}; diff --git a/resources/scripts/components/dashboard/AccountApiContainer.tsx b/resources/scripts/components/dashboard/AccountApiContainer.tsx new file mode 100644 index 00000000..d739ea60 --- /dev/null +++ b/resources/scripts/components/dashboard/AccountApiContainer.tsx @@ -0,0 +1,112 @@ +import React, { useEffect, useState } from 'react'; +import ContentBox from '@/components/elements/ContentBox'; +import CreateApiKeyForm from '@/components/dashboard/forms/CreateApiKeyForm'; +import getApiKeys, { ApiKey } from '@/api/account/getApiKeys'; +import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faKey, faTrashAlt } from '@fortawesome/free-solid-svg-icons'; +import ConfirmationModal from '@/components/elements/ConfirmationModal'; +import deleteApiKey from '@/api/account/deleteApiKey'; +import { Actions, useStoreActions } from 'easy-peasy'; +import { ApplicationStore } from '@/state'; +import FlashMessageRender from '@/components/FlashMessageRender'; +import { httpErrorToHuman } from '@/api/http'; +import { format } from 'date-fns'; +import PageContentBlock from '@/components/elements/PageContentBlock'; +import tw from 'twin.macro'; +import GreyRowBox from '@/components/elements/GreyRowBox'; + +export default () => { + const [ deleteIdentifier, setDeleteIdentifier ] = useState(''); + const [ keys, setKeys ] = useState([]); + const [ loading, setLoading ] = useState(true); + const { addError, clearFlashes } = useStoreActions((actions: Actions) => actions.flashes); + + useEffect(() => { + clearFlashes('account'); + getApiKeys() + .then(keys => setKeys(keys)) + .then(() => setLoading(false)) + .catch(error => { + console.error(error); + addError({ key: 'account', message: httpErrorToHuman(error) }); + }); + }, []); + + const doDeletion = (identifier: string) => { + setLoading(true); + clearFlashes('account'); + deleteApiKey(identifier) + .then(() => setKeys(s => ([ + ...(s || []).filter(key => key.identifier !== identifier), + ]))) + .catch(error => { + console.error(error); + addError({ key: 'account', message: httpErrorToHuman(error) }); + }) + .then(() => setLoading(false)); + }; + + return ( + + +
    + + setKeys(s => ([ ...s!, key ]))}/> + + + + { + doDeletion(deleteIdentifier); + setDeleteIdentifier(''); + }} + onModalDismissed={() => setDeleteIdentifier('')} + > + Are you sure you wish to delete this API key? All requests using it will immediately be + invalidated and will fail. + + { + keys.length === 0 ? +

    + {loading ? 'Loading...' : 'No API keys exist for this account.'} +

    + : + keys.map((key, index) => ( + 0 && tw`mt-2` ]} + > + +
    +

    {key.description}

    +

    + Last used:  + {key.lastUsedAt ? format(key.lastUsedAt, 'MMM do, yyyy HH:mm') : 'Never'} +

    +
    + + +
    + )) + } +
    +
    +
    + ); +}; diff --git a/resources/scripts/components/dashboard/AccountOverviewContainer.tsx b/resources/scripts/components/dashboard/AccountOverviewContainer.tsx new file mode 100644 index 00000000..98e7a8d5 --- /dev/null +++ b/resources/scripts/components/dashboard/AccountOverviewContainer.tsx @@ -0,0 +1,47 @@ +import * as React from 'react'; +import ContentBox from '@/components/elements/ContentBox'; +import UpdatePasswordForm from '@/components/dashboard/forms/UpdatePasswordForm'; +import UpdateEmailAddressForm from '@/components/dashboard/forms/UpdateEmailAddressForm'; +import ConfigureTwoFactorForm from '@/components/dashboard/forms/ConfigureTwoFactorForm'; +import PageContentBlock from '@/components/elements/PageContentBlock'; +import tw from 'twin.macro'; +import { breakpoint } from '@/theme'; +import styled from 'styled-components/macro'; + +const Container = styled.div` + ${tw`flex flex-wrap my-10`}; + + & > div { + ${tw`w-full`}; + + ${breakpoint('md')` + width: calc(50% - 1rem); + `} + + ${breakpoint('xl')` + ${tw`w-auto flex-1`}; + `} + } +`; + +export default () => { + return ( + + + + + + + + + + + + + + ); +}; diff --git a/resources/scripts/components/dashboard/ApiKeyModal.tsx b/resources/scripts/components/dashboard/ApiKeyModal.tsx new file mode 100644 index 00000000..e7327430 --- /dev/null +++ b/resources/scripts/components/dashboard/ApiKeyModal.tsx @@ -0,0 +1,38 @@ +import React, { useContext } from 'react'; +import tw from 'twin.macro'; +import Button from '@/components/elements/Button'; +import asModal from '@/hoc/asModal'; +import ModalContext from '@/context/ModalContext'; + +interface Props { + apiKey: string; +} + +const ApiKeyModal = ({ apiKey }: Props) => { + const { dismiss } = useContext(ModalContext); + + return ( + <> +

    Your API Key

    +

    + The API key you have requested is shown below. Please store this in a safe location, it will not be + shown again. +

    +
    +                {apiKey}
    +            
    +
    + +
    + + ); +}; + +ApiKeyModal.displayName = 'ApiKeyModal'; + +export default asModal({ + closeOnEscape: false, + closeOnBackground: false, +})(ApiKeyModal); diff --git a/resources/scripts/components/dashboard/DashboardContainer.tsx b/resources/scripts/components/dashboard/DashboardContainer.tsx new file mode 100644 index 00000000..f8b13eda --- /dev/null +++ b/resources/scripts/components/dashboard/DashboardContainer.tsx @@ -0,0 +1,72 @@ +import React, { useEffect, useState } from 'react'; +import { Server } from '@/api/server/getServer'; +import getServers from '@/api/getServers'; +import ServerRow from '@/components/dashboard/ServerRow'; +import Spinner from '@/components/elements/Spinner'; +import PageContentBlock from '@/components/elements/PageContentBlock'; +import useFlash from '@/plugins/useFlash'; +import { useStoreState } from 'easy-peasy'; +import { usePersistedState } from '@/plugins/usePersistedState'; +import Switch from '@/components/elements/Switch'; +import tw from 'twin.macro'; +import useSWR from 'swr'; +import { PaginatedResult } from '@/api/http'; +import Pagination from '@/components/elements/Pagination'; + +export default () => { + const { clearFlashes, clearAndAddHttpError } = useFlash(); + const [ page, setPage ] = useState(1); + const { rootAdmin } = useStoreState(state => state.user.data!); + const [ showOnlyAdmin, setShowOnlyAdmin ] = usePersistedState('show_all_servers', false); + + const { data: servers, error } = useSWR>( + [ '/api/client/servers', showOnlyAdmin, page ], + () => getServers({ onlyAdmin: showOnlyAdmin, page }), + ); + + useEffect(() => { + if (error) clearAndAddHttpError({ key: 'dashboard', error }); + if (!error) clearFlashes('dashboard'); + }, [ error ]); + + return ( + + {rootAdmin && +
    +

    + {showOnlyAdmin ? 'Showing other\'s servers' : 'Showing your servers'} +

    + setShowOnlyAdmin(s => !s)} + /> +
    + } + {!servers ? + + : + + {({ items }) => ( + items.length > 0 ? + items.map((server, index) => ( + 0 ? tw`mt-2` : undefined} + /> + )) + : +

    + {showOnlyAdmin ? + 'There are no other servers to display.' + : + 'There are no servers associated with your account.' + } +

    + )} +
    + } +
    + ); +}; diff --git a/resources/scripts/components/dashboard/ServerRow.tsx b/resources/scripts/components/dashboard/ServerRow.tsx new file mode 100644 index 00000000..7373fa24 --- /dev/null +++ b/resources/scripts/components/dashboard/ServerRow.tsx @@ -0,0 +1,152 @@ +import React, { memo, useEffect, useRef, useState } from 'react'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faEthernet, faHdd, faMemory, faMicrochip, faServer } from '@fortawesome/free-solid-svg-icons'; +import { Link } from 'react-router-dom'; +import { Server } from '@/api/server/getServer'; +import getServerResourceUsage, { ServerPowerState, ServerStats } from '@/api/server/getServerResourceUsage'; +import { bytesToHuman, megabytesToHuman } from '@/helpers'; +import tw from 'twin.macro'; +import GreyRowBox from '@/components/elements/GreyRowBox'; +import Spinner from '@/components/elements/Spinner'; +import styled from 'styled-components/macro'; +import isEqual from 'react-fast-compare'; + +// Determines if the current value is in an alarm threshold so we can show it in red rather +// than the more faded default style. +const isAlarmState = (current: number, limit: number): boolean => limit > 0 && (current / (limit * 1024 * 1024) >= 0.90); + +const Icon = memo(styled(FontAwesomeIcon)<{ $alarm: boolean }>` + ${props => props.$alarm ? tw`text-red-400` : tw`text-neutral-500`}; +`, isEqual); + +const IconDescription = styled.p<{ $alarm: boolean }>` + ${tw`text-sm ml-2`}; + ${props => props.$alarm ? tw`text-white` : tw`text-neutral-400`}; +`; + +const StatusIndicatorBox = styled(GreyRowBox)<{ $status: ServerPowerState | undefined }>` + ${tw`grid grid-cols-12 gap-4 relative`}; + + & .status-bar { + ${tw`w-2 bg-red-500 absolute right-0 z-20 rounded-full m-1 opacity-50 transition-all duration-150`}; + height: calc(100% - 0.5rem); + + ${({ $status }) => (!$status || $status === 'offline') ? tw`bg-red-500` : ($status === 'running' ? tw`bg-green-500` : tw`bg-yellow-500`)}; + } + + &:hover .status-bar { + ${tw`opacity-75`}; + } +`; + +export default ({ server, className }: { server: Server; className?: string }) => { + const interval = useRef(null); + const [ stats, setStats ] = useState(null); + const [ statsError, setStatsError ] = useState(false); + + const getStats = () => { + setStatsError(false); + return getServerResourceUsage(server.uuid) + .then(data => setStats(data)) + .catch(error => { + setStatsError(true); + console.error(error); + }); + }; + + useEffect(() => { + getStats().then(() => { + // @ts-ignore + interval.current = setInterval(() => getStats(), 20000); + }); + + return () => { + interval.current && clearInterval(interval.current); + }; + }, []); + + const alarms = { cpu: false, memory: false, disk: false }; + if (stats) { + alarms.cpu = server.limits.cpu === 0 ? false : (stats.cpuUsagePercent >= (server.limits.cpu * 0.9)); + alarms.memory = isAlarmState(stats.memoryUsageInBytes, server.limits.memory); + alarms.disk = server.limits.disk === 0 ? false : isAlarmState(stats.diskUsageInBytes, server.limits.disk); + } + + const disklimit = server.limits.disk !== 0 ? megabytesToHuman(server.limits.disk) : 'Unlimited'; + const memorylimit = server.limits.memory !== 0 ? megabytesToHuman(server.limits.memory) : 'Unlimited'; + + return ( + +
    +
    + +
    +
    +

    {server.name}

    + {!!server.description && +

    {server.description}

    + } +
    +
    +
    + +

    + { + server.allocations.filter(alloc => alloc.isDefault).map(allocation => ( + + {allocation.alias || allocation.ip}:{allocation.port} + + )) + } +

    +
    +
    + {!stats ? + !statsError ? + + : + server.isInstalling ? +
    + + Installing + +
    + : +
    + + {server.isSuspended ? 'Suspended' : 'Connection Error'} + +
    + : + + +
    +
    + + + {bytesToHuman(stats.memoryUsageInBytes)} + +
    +

    of {memorylimit}

    +
    +
    +
    + + + {bytesToHuman(stats.diskUsageInBytes)} + +
    +

    of {disklimit}

    +
    +
    + } +
    +
    + + ); +}; diff --git a/resources/scripts/components/dashboard/forms/ConfigureTwoFactorForm.tsx b/resources/scripts/components/dashboard/forms/ConfigureTwoFactorForm.tsx new file mode 100644 index 00000000..0b0db4d7 --- /dev/null +++ b/resources/scripts/components/dashboard/forms/ConfigureTwoFactorForm.tsx @@ -0,0 +1,59 @@ +import React, { useState } from 'react'; +import { useStoreState } from 'easy-peasy'; +import { ApplicationStore } from '@/state'; +import SetupTwoFactorModal from '@/components/dashboard/forms/SetupTwoFactorModal'; +import DisableTwoFactorModal from '@/components/dashboard/forms/DisableTwoFactorModal'; +import tw from 'twin.macro'; +import Button from '@/components/elements/Button'; + +export default () => { + const user = useStoreState((state: ApplicationStore) => state.user.data!); + const [ visible, setVisible ] = useState(false); + + return user.useTotp ? +
    + {visible && + setVisible(false)} + /> + } +

    + Two-factor authentication is currently enabled on your account. +

    +
    + +
    +
    + : +
    + {visible && + setVisible(false)} + /> + } +

    + You do not currently have two-factor authentication enabled on your account. Click + the button below to begin configuring it. +

    +
    + +
    +
    + ; +}; diff --git a/resources/scripts/components/dashboard/forms/CreateApiKeyForm.tsx b/resources/scripts/components/dashboard/forms/CreateApiKeyForm.tsx new file mode 100644 index 00000000..9022ae6c --- /dev/null +++ b/resources/scripts/components/dashboard/forms/CreateApiKeyForm.tsx @@ -0,0 +1,86 @@ +import React, { useState } from 'react'; +import { Field, Form, Formik, FormikHelpers } from 'formik'; +import { object, string } from 'yup'; +import FormikFieldWrapper from '@/components/elements/FormikFieldWrapper'; +import createApiKey from '@/api/account/createApiKey'; +import { Actions, useStoreActions } from 'easy-peasy'; +import { ApplicationStore } from '@/state'; +import { httpErrorToHuman } from '@/api/http'; +import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; +import { ApiKey } from '@/api/account/getApiKeys'; +import tw from 'twin.macro'; +import Button from '@/components/elements/Button'; +import Input, { Textarea } from '@/components/elements/Input'; +import styled from 'styled-components/macro'; +import ApiKeyModal from '@/components/dashboard/ApiKeyModal'; + +interface Values { + description: string; + allowedIps: string; +} + +const CustomTextarea = styled(Textarea)`${tw`h-32`}`; + +export default ({ onKeyCreated }: { onKeyCreated: (key: ApiKey) => void }) => { + const [ apiKey, setApiKey ] = useState(''); + const { addError, clearFlashes } = useStoreActions((actions: Actions) => actions.flashes); + + const submit = (values: Values, { setSubmitting, resetForm }: FormikHelpers) => { + clearFlashes('account'); + createApiKey(values.description, values.allowedIps) + .then(({ secretToken, ...key }) => { + resetForm(); + setSubmitting(false); + setApiKey(`${key.identifier}${secretToken}`); + onKeyCreated(key); + }) + .catch(error => { + console.error(error); + + addError({ key: 'account', message: httpErrorToHuman(error) }); + setSubmitting(false); + }); + }; + + return ( + <> + 0} + onModalDismissed={() => setApiKey('')} + apiKey={apiKey} + /> + + {({ isSubmitting }) => ( +
    + + + + + + + +
    + +
    + + )} +
    + + ); +}; diff --git a/resources/scripts/components/dashboard/forms/DisableTwoFactorModal.tsx b/resources/scripts/components/dashboard/forms/DisableTwoFactorModal.tsx new file mode 100644 index 00000000..170cd01d --- /dev/null +++ b/resources/scripts/components/dashboard/forms/DisableTwoFactorModal.tsx @@ -0,0 +1,72 @@ +import React from 'react'; +import { Form, Formik, FormikHelpers } from 'formik'; +import Modal, { RequiredModalProps } from '@/components/elements/Modal'; +import FlashMessageRender from '@/components/FlashMessageRender'; +import Field from '@/components/elements/Field'; +import { object, string } from 'yup'; +import { Actions, useStoreActions } from 'easy-peasy'; +import { ApplicationStore } from '@/state'; +import disableAccountTwoFactor from '@/api/account/disableAccountTwoFactor'; +import { httpErrorToHuman } from '@/api/http'; +import tw from 'twin.macro'; +import Button from '@/components/elements/Button'; + +interface Values { + password: string; +} + +export default ({ ...props }: RequiredModalProps) => { + const { addError, clearFlashes } = useStoreActions((actions: Actions) => actions.flashes); + const updateUserData = useStoreActions((actions: Actions) => actions.user.updateUserData); + + const submit = ({ password }: Values, { setSubmitting }: FormikHelpers) => { + clearFlashes('account:two-factor'); + disableAccountTwoFactor(password) + .then(() => { + updateUserData({ useTotp: false }); + props.onDismissed(); + }) + .catch(error => { + console.error(error); + + addError({ message: httpErrorToHuman(error), key: 'account:two-factor' }); + setSubmitting(false); + }); + }; + + return ( + + {({ isSubmitting, isValid }) => ( + +
    + + +
    + +
    + +
    + )} +
    + ); +}; diff --git a/resources/scripts/components/dashboard/forms/SetupTwoFactorModal.tsx b/resources/scripts/components/dashboard/forms/SetupTwoFactorModal.tsx new file mode 100644 index 00000000..61906748 --- /dev/null +++ b/resources/scripts/components/dashboard/forms/SetupTwoFactorModal.tsx @@ -0,0 +1,144 @@ +import React, { useEffect, useState } from 'react'; +import Modal, { RequiredModalProps } from '@/components/elements/Modal'; +import { Form, Formik, FormikHelpers } from 'formik'; +import { object, string } from 'yup'; +import getTwoFactorTokenUrl from '@/api/account/getTwoFactorTokenUrl'; +import enableAccountTwoFactor from '@/api/account/enableAccountTwoFactor'; +import { Actions, useStoreActions } from 'easy-peasy'; +import { ApplicationStore } from '@/state'; +import { httpErrorToHuman } from '@/api/http'; +import FlashMessageRender from '@/components/FlashMessageRender'; +import Field from '@/components/elements/Field'; +import tw from 'twin.macro'; +import Button from '@/components/elements/Button'; + +interface Values { + code: string; +} + +export default ({ onDismissed, ...props }: RequiredModalProps) => { + const [ token, setToken ] = useState(''); + const [ loading, setLoading ] = useState(true); + const [ recoveryTokens, setRecoveryTokens ] = useState([]); + + const updateUserData = useStoreActions((actions: Actions) => actions.user.updateUserData); + const { addError, clearFlashes } = useStoreActions((actions: Actions) => actions.flashes); + + useEffect(() => { + clearFlashes('account:two-factor'); + getTwoFactorTokenUrl() + .then(setToken) + .catch(error => { + console.error(error); + addError({ message: httpErrorToHuman(error), key: 'account:two-factor' }); + }); + }, []); + + const submit = ({ code }: Values, { setSubmitting }: FormikHelpers) => { + clearFlashes('account:two-factor'); + enableAccountTwoFactor(code) + .then(tokens => { + setRecoveryTokens(tokens); + }) + .catch(error => { + console.error(error); + + addError({ message: httpErrorToHuman(error), key: 'account:two-factor' }); + }) + .then(() => setSubmitting(false)); + }; + + const dismiss = () => { + if (recoveryTokens.length > 0) { + updateUserData({ useTotp: true }); + } + + onDismissed(); + }; + + return ( + + {({ isSubmitting }) => ( + + {recoveryTokens.length > 0 ? + <> +

    Two-factor authentication enabled

    +

    + Two-factor authentication has been enabled on your account. Should you loose access to + this device you'll need to use on of the codes displayed below in order to access your + account. +

    +

    + These codes will not be displayed again. Please take note of them now + by storing them in a secure repository such as a password manager. +

    +
    +                                {recoveryTokens.map(token => {token})}
    +                            
    +
    + +
    + + : +
    + +
    +
    +
    + {!token || !token.length ? + + : + setLoading(false)} + css={tw`w-full h-full shadow-none rounded-none`} + /> + } +
    +
    +
    +
    + +
    +
    + +
    +
    +
    + + } +
    + )} +
    + ); +}; diff --git a/resources/scripts/components/dashboard/forms/UpdateEmailAddressForm.tsx b/resources/scripts/components/dashboard/forms/UpdateEmailAddressForm.tsx new file mode 100644 index 00000000..547cb26c --- /dev/null +++ b/resources/scripts/components/dashboard/forms/UpdateEmailAddressForm.tsx @@ -0,0 +1,85 @@ +import React from 'react'; +import { Actions, State, useStoreActions, useStoreState } from 'easy-peasy'; +import { Form, Formik, FormikHelpers } from 'formik'; +import * as Yup from 'yup'; +import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; +import Field from '@/components/elements/Field'; +import { httpErrorToHuman } from '@/api/http'; +import { ApplicationStore } from '@/state'; +import tw from 'twin.macro'; +import Button from '@/components/elements/Button'; + +interface Values { + email: string; + password: string; +} + +const schema = Yup.object().shape({ + email: Yup.string().email().required(), + password: Yup.string().required('You must provide your current account password.'), +}); + +export default () => { + const user = useStoreState((state: State) => state.user.data); + const updateEmail = useStoreActions((state: Actions) => state.user.updateUserEmail); + + const { clearFlashes, addFlash } = useStoreActions((actions: Actions) => actions.flashes); + + const submit = (values: Values, { resetForm, setSubmitting }: FormikHelpers) => { + clearFlashes('account:email'); + + updateEmail({ ...values }) + .then(() => addFlash({ + type: 'success', + key: 'account:email', + message: 'Your primary email has been updated.', + })) + .catch(error => addFlash({ + type: 'error', + key: 'account:email', + title: 'Error', + message: httpErrorToHuman(error), + })) + .then(() => { + resetForm(); + setSubmitting(false); + }); + }; + + return ( + + { + ({ isSubmitting, isValid }) => ( + + +
    + +
    + +
    +
    + +
    + +
    + ) + } +
    + ); +}; diff --git a/resources/scripts/components/dashboard/forms/UpdatePasswordForm.tsx b/resources/scripts/components/dashboard/forms/UpdatePasswordForm.tsx new file mode 100644 index 00000000..7dec924a --- /dev/null +++ b/resources/scripts/components/dashboard/forms/UpdatePasswordForm.tsx @@ -0,0 +1,98 @@ +import React from 'react'; +import { Actions, State, useStoreActions, useStoreState } from 'easy-peasy'; +import { Form, Formik, FormikHelpers } from 'formik'; +import Field from '@/components/elements/Field'; +import * as Yup from 'yup'; +import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; +import updateAccountPassword from '@/api/account/updateAccountPassword'; +import { httpErrorToHuman } from '@/api/http'; +import { ApplicationStore } from '@/state'; +import tw from 'twin.macro'; +import Button from '@/components/elements/Button'; + +interface Values { + current: string; + password: string; + confirmPassword: string; +} + +const schema = Yup.object().shape({ + current: Yup.string().min(1).required('You must provide your current password.'), + password: Yup.string().min(8).required(), + confirmPassword: Yup.string().test('password', 'Password confirmation does not match the password you entered.', function (value) { + return value === this.parent.password; + }), +}); + +export default () => { + const user = useStoreState((state: State) => state.user.data); + const { clearFlashes, addFlash } = useStoreActions((actions: Actions) => actions.flashes); + + if (!user) { + return null; + } + + const submit = (values: Values, { setSubmitting }: FormikHelpers) => { + clearFlashes('account:password'); + updateAccountPassword({ ...values }) + .then(() => { + // @ts-ignore + window.location = '/auth/login'; + }) + .catch(error => addFlash({ + key: 'account:password', + type: 'error', + title: 'Error', + message: httpErrorToHuman(error), + })) + .then(() => setSubmitting(false)); + }; + + return ( + + + { + ({ isSubmitting, isValid }) => ( + + +
    + +
    + +
    +
    + +
    +
    + +
    + +
    + ) + } +
    +
    + ); +}; diff --git a/resources/scripts/components/dashboard/search/SearchContainer.tsx b/resources/scripts/components/dashboard/search/SearchContainer.tsx new file mode 100644 index 00000000..d552d2c1 --- /dev/null +++ b/resources/scripts/components/dashboard/search/SearchContainer.tsx @@ -0,0 +1,32 @@ +import React, { useState } from 'react'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faSearch } from '@fortawesome/free-solid-svg-icons'; +import useEventListener from '@/plugins/useEventListener'; +import SearchModal from '@/components/dashboard/search/SearchModal'; + +export default () => { + const [ visible, setVisible ] = useState(false); + + useEventListener('keydown', (e: KeyboardEvent) => { + if ([ 'input', 'textarea' ].indexOf(((e.target as HTMLElement).tagName || 'input').toLowerCase()) < 0) { + if (!visible && e.key.toLowerCase() === 'k') { + setVisible(true); + } + } + }); + + return ( + <> + {visible && + setVisible(false)} + /> + } +
    setVisible(true)}> + +
    + + ); +}; diff --git a/resources/scripts/components/dashboard/search/SearchModal.tsx b/resources/scripts/components/dashboard/search/SearchModal.tsx new file mode 100644 index 00000000..75eff1bf --- /dev/null +++ b/resources/scripts/components/dashboard/search/SearchModal.tsx @@ -0,0 +1,134 @@ +import React, { useEffect, useRef, useState } from 'react'; +import Modal, { RequiredModalProps } from '@/components/elements/Modal'; +import { Field, Form, Formik, FormikHelpers, useFormikContext } from 'formik'; +import { Actions, useStoreActions, useStoreState } from 'easy-peasy'; +import { object, string } from 'yup'; +import debounce from 'debounce'; +import FormikFieldWrapper from '@/components/elements/FormikFieldWrapper'; +import InputSpinner from '@/components/elements/InputSpinner'; +import getServers from '@/api/getServers'; +import { Server } from '@/api/server/getServer'; +import { ApplicationStore } from '@/state'; +import { httpErrorToHuman } from '@/api/http'; +import { Link } from 'react-router-dom'; +import styled from 'styled-components/macro'; +import tw from 'twin.macro'; +import Input from '@/components/elements/Input'; + +type Props = RequiredModalProps; + +interface Values { + term: string; +} + +const ServerResult = styled(Link)` + ${tw`flex items-center bg-neutral-900 p-4 rounded border-l-4 border-neutral-900 no-underline transition-all duration-150`}; + + &:hover { + ${tw`shadow border-cyan-500`}; + } + + &:not(:last-of-type) { + ${tw`mb-2`}; + } +`; + +const SearchWatcher = () => { + const { values, submitForm } = useFormikContext(); + + useEffect(() => { + if (values.term.length >= 3) { + submitForm(); + } + }, [ values.term ]); + + return null; +}; + +export default ({ ...props }: Props) => { + const ref = useRef(null); + const [ loading, setLoading ] = useState(false); + const [ servers, setServers ] = useState([]); + const isAdmin = useStoreState(state => state.user.data!.rootAdmin); + const { addError, clearFlashes } = useStoreActions((actions: Actions) => actions.flashes); + + const search = debounce(({ term }: Values, { setSubmitting }: FormikHelpers) => { + setLoading(true); + setSubmitting(false); + clearFlashes('search'); + + getServers({ query: term }) + .then(servers => setServers(servers.items.filter((_, index) => index < 5))) + .catch(error => { + console.error(error); + addError({ key: 'search', message: httpErrorToHuman(error) }); + }) + .then(() => setLoading(false)); + }, 500); + + useEffect(() => { + if (props.visible) { + setTimeout(() => ref.current?.focus(), 250); + } + }, [ props.visible ]); + + return ( + + +
    + + + + + + +
    + {servers.length > 0 && +
    + { + servers.map(server => ( + props.onDismissed()} + > +
    +

    {server.name}

    +

    + { + server.allocations.filter(alloc => alloc.isDefault).map(allocation => ( + {allocation.alias || allocation.ip}:{allocation.port} + )) + } +

    +
    +
    + + {server.node} + +
    +
    + )) + } +
    + } +
    +
    + ); +}; diff --git a/resources/scripts/components/elements/Button.tsx b/resources/scripts/components/elements/Button.tsx new file mode 100644 index 00000000..300f1a9e --- /dev/null +++ b/resources/scripts/components/elements/Button.tsx @@ -0,0 +1,99 @@ +import React from 'react'; +import styled, { css } from 'styled-components/macro'; +import tw from 'twin.macro'; +import Spinner from '@/components/elements/Spinner'; + +interface Props { + isLoading?: boolean; + size?: 'xsmall' | 'small' | 'large' | 'xlarge'; + color?: 'green' | 'red' | 'primary' | 'grey'; + isSecondary?: boolean; +} + +const ButtonStyle = styled.button>` + ${tw`relative inline-block rounded p-2 uppercase tracking-wide text-sm transition-all duration-150 border`}; + + ${props => ((!props.isSecondary && !props.color) || props.color === 'primary') && css` + ${props => !props.isSecondary && tw`bg-primary-500 border-primary-600 border text-primary-50`}; + + &:hover:not(:disabled) { + ${tw`bg-primary-600 border-primary-700`}; + } + `}; + + ${props => props.color === 'grey' && css` + ${tw`border-neutral-600 bg-neutral-500 text-neutral-50`}; + + &:hover:not(:disabled) { + ${tw`bg-neutral-600 border-neutral-700`}; + } + `}; + + ${props => props.color === 'green' && css` + ${tw`border-green-600 bg-green-500 text-green-50`}; + + &:hover:not(:disabled) { + ${tw`bg-green-600 border-green-700`}; + } + + ${props => props.isSecondary && css` + &:active:not(:disabled) { + ${tw`bg-green-600 border-green-700`}; + } + `}; + `}; + + ${props => props.color === 'red' && css` + ${tw`border-red-600 bg-red-500 text-red-50`}; + + &:hover:not(:disabled) { + ${tw`bg-red-600 border-red-700`}; + } + + ${props => props.isSecondary && css` + &:active:not(:disabled) { + ${tw`bg-red-600 border-red-700`}; + } + `}; + `}; + + ${props => props.size === 'xsmall' && tw`p-2 text-xs`}; + ${props => (!props.size || props.size === 'small') && tw`p-3`}; + ${props => props.size === 'large' && tw`p-4 text-sm`}; + ${props => props.size === 'xlarge' && tw`p-4 w-full`}; + + ${props => props.isSecondary && css` + ${tw`border-neutral-600 bg-transparent text-neutral-200`}; + + &:hover:not(:disabled) { + ${tw`border-neutral-500 text-neutral-100`}; + ${props => props.color === 'red' && tw`bg-red-500 border-red-600 text-red-50`}; + ${props => props.color === 'primary' && tw`bg-primary-500 border-primary-600 text-primary-50`}; + ${props => props.color === 'green' && tw`bg-green-500 border-green-600 text-green-50`}; + } + `}; + + &:disabled { opacity: 0.55; cursor: default } +`; + +type ComponentProps = Omit & Props; + +const Button: React.FC = ({ children, isLoading, ...props }) => ( + + {isLoading && +
    + +
    + } + + {children} + +
    +); + +type LinkProps = Omit & Props; + +const LinkButton: React.FC = props => ; + +export { LinkButton, ButtonStyle }; +export default Button; diff --git a/resources/scripts/components/elements/Can.tsx b/resources/scripts/components/elements/Can.tsx new file mode 100644 index 00000000..dd9d4845 --- /dev/null +++ b/resources/scripts/components/elements/Can.tsx @@ -0,0 +1,27 @@ +import React, { memo } from 'react'; +import { usePermissions } from '@/plugins/usePermissions'; +import isEqual from 'react-fast-compare'; + +interface Props { + action: string | string[]; + matchAny?: boolean; + renderOnError?: React.ReactNode | null; + children: React.ReactNode; +} + +const Can = ({ action, matchAny = false, renderOnError, children }: Props) => { + const can = usePermissions(action); + + return ( + <> + { + ((matchAny && can.filter(p => p).length > 0) || (!matchAny && can.every(p => p))) ? + children + : + renderOnError + } + + ); +}; + +export default memo(Can, isEqual); diff --git a/resources/scripts/components/elements/Checkbox.tsx b/resources/scripts/components/elements/Checkbox.tsx new file mode 100644 index 00000000..79053648 --- /dev/null +++ b/resources/scripts/components/elements/Checkbox.tsx @@ -0,0 +1,45 @@ +import React from 'react'; +import { Field, FieldProps } from 'formik'; +import Input from '@/components/elements/Input'; + +interface Props { + name: string; + value: string; + className?: string; +} + +type OmitFields = 'ref' | 'name' | 'value' | 'type' | 'checked' | 'onClick' | 'onChange'; + +type InputProps = Omit; + +const Checkbox = ({ name, value, className, ...props }: Props & InputProps) => ( + + {({ field, form }: FieldProps) => { + if (!Array.isArray(field.value)) { + console.error('Attempting to mount a checkbox using a field value that is not an array.'); + + return null; + } + + return ( + form.setFieldTouched(field.name, true)} + onChange={e => { + const set = new Set(field.value); + set.has(value) ? set.delete(value) : set.add(value); + + field.onChange(e); + form.setFieldValue(field.name, Array.from(set)); + }} + /> + ); + }} + +); + +export default Checkbox; diff --git a/resources/scripts/components/elements/CodemirrorEditor.tsx b/resources/scripts/components/elements/CodemirrorEditor.tsx new file mode 100644 index 00000000..e38ec77a --- /dev/null +++ b/resources/scripts/components/elements/CodemirrorEditor.tsx @@ -0,0 +1,217 @@ +import React, { useCallback, useEffect, useState } from 'react'; +import CodeMirror from 'codemirror'; +import styled from 'styled-components/macro'; +import tw from 'twin.macro'; +import modes from '@/modes'; + +require('codemirror/lib/codemirror.css'); +require('codemirror/theme/ayu-mirage.css'); +require('codemirror/addon/edit/closebrackets'); +require('codemirror/addon/edit/closetag'); +require('codemirror/addon/edit/matchbrackets'); +require('codemirror/addon/edit/matchtags'); +require('codemirror/addon/edit/trailingspace'); +require('codemirror/addon/fold/foldcode'); +require('codemirror/addon/fold/foldgutter.css'); +require('codemirror/addon/fold/foldgutter'); +require('codemirror/addon/fold/brace-fold'); +require('codemirror/addon/fold/comment-fold'); +require('codemirror/addon/fold/indent-fold'); +require('codemirror/addon/fold/markdown-fold'); +require('codemirror/addon/fold/xml-fold'); +require('codemirror/addon/hint/css-hint'); +require('codemirror/addon/hint/html-hint'); +require('codemirror/addon/hint/javascript-hint'); +require('codemirror/addon/hint/show-hint.css'); +require('codemirror/addon/hint/show-hint'); +require('codemirror/addon/hint/sql-hint'); +require('codemirror/addon/hint/xml-hint'); +require('codemirror/addon/mode/simple'); +require('codemirror/addon/dialog/dialog.css'); +require('codemirror/addon/dialog/dialog'); +require('codemirror/addon/scroll/annotatescrollbar'); +require('codemirror/addon/scroll/scrollpastend'); +require('codemirror/addon/scroll/simplescrollbars.css'); +require('codemirror/addon/scroll/simplescrollbars'); +require('codemirror/addon/search/jump-to-line'); +require('codemirror/addon/search/match-highlighter'); +require('codemirror/addon/search/matchesonscrollbar.css'); +require('codemirror/addon/search/matchesonscrollbar'); +require('codemirror/addon/search/search'); +require('codemirror/addon/search/searchcursor'); + +require('codemirror/mode/brainfuck/brainfuck'); +require('codemirror/mode/clike/clike'); +require('codemirror/mode/css/css'); +require('codemirror/mode/dart/dart'); +require('codemirror/mode/diff/diff'); +require('codemirror/mode/dockerfile/dockerfile'); +require('codemirror/mode/erlang/erlang'); +require('codemirror/mode/gfm/gfm'); +require('codemirror/mode/go/go'); +require('codemirror/mode/handlebars/handlebars'); +require('codemirror/mode/htmlembedded/htmlembedded'); +require('codemirror/mode/htmlmixed/htmlmixed'); +require('codemirror/mode/http/http'); +require('codemirror/mode/javascript/javascript'); +require('codemirror/mode/jsx/jsx'); +require('codemirror/mode/julia/julia'); +require('codemirror/mode/lua/lua'); +require('codemirror/mode/markdown/markdown'); +require('codemirror/mode/nginx/nginx'); +require('codemirror/mode/perl/perl'); +require('codemirror/mode/php/php'); +require('codemirror/mode/properties/properties'); +require('codemirror/mode/protobuf/protobuf'); +require('codemirror/mode/pug/pug'); +require('codemirror/mode/python/python'); +require('codemirror/mode/rpm/rpm'); +require('codemirror/mode/ruby/ruby'); +require('codemirror/mode/rust/rust'); +require('codemirror/mode/sass/sass'); +require('codemirror/mode/shell/shell'); +require('codemirror/mode/smarty/smarty'); +require('codemirror/mode/sql/sql'); +require('codemirror/mode/swift/swift'); +require('codemirror/mode/toml/toml'); +require('codemirror/mode/twig/twig'); +require('codemirror/mode/vue/vue'); +require('codemirror/mode/xml/xml'); +require('codemirror/mode/yaml/yaml'); + +const EditorContainer = styled.div` + min-height: 16rem; + height: calc(100vh - 20rem); + ${tw`relative`}; + + > div { + ${tw`rounded h-full`}; + } + + .CodeMirror { + font-size: 12px; + line-height: 1.375rem; + } + + .CodeMirror-linenumber { + padding: 1px 12px 0 12px !important; + } + + .CodeMirror-foldmarker { + color: #CBCCC6; + text-shadow: none; + margin-left: 0.25rem; + margin-right: 0.25rem; + } +`; + +export interface Props { + style?: React.CSSProperties; + initialContent?: string; + mode: string; + filename?: string; + onModeChanged: (mode: string) => void; + fetchContent: (callback: () => Promise) => void; + onContentSaved: () => void; +} + +const findModeByFilename = (filename: string) => { + for (let i = 0; i < modes.length; i++) { + const info = modes[i]; + + if (info.file && info.file.test(filename)) { + return info; + } + } + + const dot = filename.lastIndexOf('.'); + const ext = dot > -1 && filename.substring(dot + 1, filename.length); + + if (ext) { + for (let i = 0; i < modes.length; i++) { + const info = modes[i]; + if (info.ext) { + for (let j = 0; j < info.ext.length; j++) { + if (info.ext[j] === ext) { + return info; + } + } + } + } + } + + return undefined; +}; + +export default ({ style, initialContent, filename, mode, fetchContent, onContentSaved, onModeChanged }: Props) => { + const [ editor, setEditor ] = useState(); + + const ref = useCallback((node) => { + if (!node) return; + + const e = CodeMirror.fromTextArea(node, { + mode: 'text/plain', + theme: 'ayu-mirage', + indentUnit: 4, + smartIndent: true, + tabSize: 4, + indentWithTabs: false, + lineWrapping: true, + lineNumbers: true, + foldGutter: true, + fixedGutter: true, + scrollbarStyle: 'overlay', + coverGutterNextToScrollbar: false, + readOnly: false, + showCursorWhenSelecting: false, + autofocus: false, + spellcheck: true, + autocorrect: false, + autocapitalize: false, + lint: false, + // This property is actually used, the d.ts file for CodeMirror is incorrect. + // @ts-ignore + autoCloseBrackets: true, + matchBrackets: true, + gutters: [ 'CodeMirror-linenumbers', 'CodeMirror-foldgutter' ], + }); + + setEditor(e); + }, []); + + useEffect(() => { + if (filename === undefined) { + return; + } + + onModeChanged(findModeByFilename(filename)?.mime || 'text/plain'); + }, [ filename ]); + + useEffect(() => { + editor && editor.setOption('mode', mode); + }, [ editor, mode ]); + + useEffect(() => { + editor && editor.setValue(initialContent || ''); + }, [ editor, initialContent ]); + + useEffect(() => { + if (!editor) { + fetchContent(() => Promise.reject(new Error('no editor session has been configured'))); + return; + } + + editor.addKeyMap({ + 'Ctrl-S': () => onContentSaved(), + 'Cmd-S': () => onContentSaved(), + }); + + fetchContent(() => Promise.resolve(editor.getValue())); + }, [ editor, fetchContent, onContentSaved ]); + + return ( + + -
    -
    - - -

    The version of this package, or the version of the files contained within the package.

    -
    -
    - - -

    The option that this pack is associated with. Only servers that are assigned this option will be able to access this pack.

    -
    -
    -
    -
    -
    -
    -
    -

    Pack Configuration

    -
    -
    -
    -
    - - -
    -

    Check this box if user should be able to select this pack to install on their servers.

    -
    -
    -
    - - -
    -

    Check this box if this pack is visible in the dropdown menu. If this pack is assigned to a server it will be visible regardless of this setting.

    -
    -
    -
    - - -
    -

    Check this box if servers assigned this pack should not be able to switch to a different pack.

    -
    -
    -
    - - -

    This package file must be a .tar.gz archive of pack files to be decompressed into the server folder.

    -

    If your file is larger than 50MB it is recommended to upload it using SFTP. Once you have added this pack to the system, a path will be provided where you should upload the file.

    -
    -

    This server is currently configured with the following limits:
    upload_max_filesize={{ ini_get('upload_max_filesize') }}
    post_max_size={{ ini_get('post_max_size') }}

    If your file is larger than either of those values this request will fail.

    -
    -
    -
    - -
    -
    - - -@endsection - -@section('footer-scripts') - @parent - -@endsection diff --git a/resources/themes/pterodactyl/admin/packs/view.blade.php b/resources/themes/pterodactyl/admin/packs/view.blade.php deleted file mode 100644 index cba76304..00000000 --- a/resources/themes/pterodactyl/admin/packs/view.blade.php +++ /dev/null @@ -1,154 +0,0 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} -@extends('layouts.admin') - -@section('title') - Packs → View → {{ $pack->name }} -@endsection - -@section('content-header') -

    {{ $pack->name }}{{ str_limit($pack->description, 60) }}

    - -@endsection - -@section('content') -
    -
    -
    -
    -
    -

    Pack Details

    -
    -
    -
    - - -

    A short but descriptive name of what this pack is. For example, Counter Strike: Source if it is a Counter Strike package.

    -
    -
    - - -
    -
    - - -

    The version of this package, or the version of the files contained within the package.

    -
    -
    - - -

    If you would like to modify the stored pack you will need to upload a new archive.tar.gz to the location defined above.

    -
    -
    -
    -
    -
    -
    -
    -

    Pack Configuration

    -
    -
    -
    - - -

    The option that this pack is associated with. Only servers that are assigned this option will be able to access this pack. This assigned option cannot be changed if servers are attached to this pack.

    -
    -
    -
    - selectable ?: 'checked' }}/> - -
    -

    Check this box if user should be able to select this pack to install on their servers.

    -
    -
    -
    - visible ?: 'checked' }}/> - -
    -

    Check this box if this pack is visible in the dropdown menu. If this pack is assigned to a server it will be visible regardless of this setting.

    -
    -
    -
    - locked ?: 'checked' }}/> - -
    -

    Check this box if servers assigned this pack should not be able to switch to a different pack.

    -
    -
    - -
    -
    -
    -
    -
    -
    -
    -
    -

    Servers Using This Pack

    -
    -
    - - - - - - - - @foreach($pack->servers as $server) - - - - - - - @endforeach -
    IDServer NameNodeOwner
    {{ $server->uuidShort }}{{ $server->name }}{{ $server->node->name }}{{ $server->user->email }}
    -
    -
    -
    -
    -
    -
    -
    - {!! csrf_field() !!} - -
    -
    - {!! csrf_field() !!} - -
    -
    -
    -@endsection - -@section('footer-scripts') - @parent - -@endsection diff --git a/resources/themes/pterodactyl/admin/servers/view/manage.blade.php b/resources/themes/pterodactyl/admin/servers/view/manage.blade.php deleted file mode 100644 index 57319b4e..00000000 --- a/resources/themes/pterodactyl/admin/servers/view/manage.blade.php +++ /dev/null @@ -1,132 +0,0 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} -@extends('layouts.admin') - -@section('title') - Server — {{ $server->name }}: Manage -@endsection - -@section('content-header') -

    {{ $server->name }}Additional actions to control this server.

    - -@endsection - -@section('content') -
    -
    - -
    -
    -
    -
    -
    -
    -

    Reinstall Server

    -
    -
    -

    This will reinstall the server with the assigned pack and service scripts. Danger! This could overwrite server data.

    -
    - -
    -
    -
    -
    -
    -

    Install Status

    -
    -
    -

    If you need to change the install status from uninstalled to installed, or vice versa, you may do so with the button below.

    -
    - -
    -
    -
    -
    -
    -

    Rebuild Container

    -
    -
    -

    This will trigger a rebuild of the server container when it next starts up. This is useful if you modified the server configuration file manually, or something just didn't work out correctly.

    -
    - -
    -
    - @if(! $server->suspended) -
    -
    -
    -

    Suspend Server

    -
    -
    -

    This will suspend the server, stop any running processes, and immediately block the user from being able to access their files or otherwise manage the server through the panel or API.

    -
    - -
    -
    - @else -
    -
    -
    -

    Unsuspend Server

    -
    -
    -

    This will unsuspend the server and restore normal user access.

    -
    - -
    -
    - @endif -
    -@endsection diff --git a/resources/themes/pterodactyl/auth/login.blade.php b/resources/themes/pterodactyl/auth/login.blade.php deleted file mode 100644 index e81af8d3..00000000 --- a/resources/themes/pterodactyl/auth/login.blade.php +++ /dev/null @@ -1,75 +0,0 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} -@extends('layouts.auth') - -@section('title') - Login -@endsection - -@section('content') -
    -
    - @if (count($errors) > 0) -
    - - @lang('auth.auth_error')

    -
      - @foreach ($errors->all() as $error) -
    • {{ $error }}
    • - @endforeach -
    -
    - @endif - @foreach (Alert::getMessages() as $type => $messages) - @foreach ($messages as $message) - - @endforeach - @endforeach -
    -
    -
    - -
    -@endsection - -@section('scripts') - @parent - @if(config('recaptcha.enabled')) - - - @endif -@endsection diff --git a/resources/themes/pterodactyl/auth/passwords/email.blade.php b/resources/themes/pterodactyl/auth/passwords/email.blade.php deleted file mode 100644 index d90d36c5..00000000 --- a/resources/themes/pterodactyl/auth/passwords/email.blade.php +++ /dev/null @@ -1,71 +0,0 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} -@extends('layouts.auth') - -@section('title') - Forgot Password -@endsection - -@section('content') -
    -
    - @if (count($errors) > 0) -
    - - @lang('auth.auth_error')

    -
      - @foreach ($errors->all() as $error) -
    • {{ $error }}
    • - @endforeach -
    -
    - @endif - @if (session('status')) -
    - @lang('auth.email_sent') -
    - @endif -
    -
    -
    - -
    -@endsection - -@section('scripts') - @parent - @if(config('recaptcha.enabled')) - - - @endif -@endsection diff --git a/resources/themes/pterodactyl/auth/passwords/reset.blade.php b/resources/themes/pterodactyl/auth/passwords/reset.blade.php deleted file mode 100644 index d79b9a6a..00000000 --- a/resources/themes/pterodactyl/auth/passwords/reset.blade.php +++ /dev/null @@ -1,90 +0,0 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} -@extends('layouts.auth') - -@section('title') - Reset Password -@endsection - -@section('content') -
    -
    - @if (count($errors) > 0) -
    - - @lang('auth.auth_error')

    -
      - @foreach ($errors->all() as $error) -
    • {{ $error }}
    • - @endforeach -
    -
    - @endif -
    -
    -
    - -
    -@endsection - -@section('scripts') - @parent - @if(config('recaptcha.enabled')) - - - @endif -@endsection diff --git a/resources/themes/pterodactyl/auth/totp.blade.php b/resources/themes/pterodactyl/auth/totp.blade.php deleted file mode 100644 index 3c1ee253..00000000 --- a/resources/themes/pterodactyl/auth/totp.blade.php +++ /dev/null @@ -1,42 +0,0 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} -@extends('layouts.auth') - -@section('title') - 2FA Checkpoint -@endsection - -@section('scripts') - @parent - -@endsection - -@section('content') -
    - -
    -@endsection diff --git a/resources/themes/pterodactyl/base/account.blade.php b/resources/themes/pterodactyl/base/account.blade.php deleted file mode 100644 index c01ebbc1..00000000 --- a/resources/themes/pterodactyl/base/account.blade.php +++ /dev/null @@ -1,147 +0,0 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} -@extends('layouts.master') - -@section('title') - @lang('base.account.header') -@endsection - -@section('content-header') -

    @lang('base.account.header')@lang('base.account.header_sub')

    - -@endsection - -@section('content') -
    -
    -
    -
    -
    -
    -

    @lang('base.account.update_pass')

    -
    -
    -
    -
    - -
    - -
    -
    -
    - -
    - -

    @lang('auth.password_requirements')

    -
    -
    -
    - -
    - -
    -
    -
    - -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    @lang('base.account.update_identity')

    -
    -
    -
    -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    -
    -
    - -
    - -

    @lang('base.account.username_help', [ 'requirements' => 'a-z A-Z 0-9 _ - .'])

    -
    -
    -
    -
    -
    - -
    - -
    -
    -
    -
    - -
    -
    -
    -
    -
    -
    -
    -
    -

    @lang('base.account.update_email')

    -
    -
    -
    -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    - -
    -
    -
    -
    -
    -
    -@endsection diff --git a/resources/themes/pterodactyl/base/api/index.blade.php b/resources/themes/pterodactyl/base/api/index.blade.php deleted file mode 100644 index 99e5a1ca..00000000 --- a/resources/themes/pterodactyl/base/api/index.blade.php +++ /dev/null @@ -1,123 +0,0 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} -@extends('layouts.master') - -@section('title') - @lang('base.api.index.header') -@endsection - -@section('content-header') -

    @lang('base.api.index.header')@lang('base.api.index.header_sub')

    - -@endsection - -@section('content') -
    -
    -
    -
    -

    Credentials List

    - -
    -
    - - - - - - - - - @foreach($keys as $key) - - - - - - - - @endforeach -
    KeyMemoLast UsedCreated
    - - •••••••• - - - {{ $key->memo }} - @if(!is_null($key->last_used_at)) - @datetimeHuman($key->last_used_at) - @else - — - @endif - @datetimeHuman($key->created_at) - - - -
    -
    -
    -
    -
    -@endsection - -@section('footer-scripts') - @parent - -@endsection diff --git a/resources/themes/pterodactyl/base/api/new.blade.php b/resources/themes/pterodactyl/base/api/new.blade.php deleted file mode 100644 index 8c927265..00000000 --- a/resources/themes/pterodactyl/base/api/new.blade.php +++ /dev/null @@ -1,47 +0,0 @@ -@extends('layouts.master') - -@section('title') - @lang('base.api.new.header') -@endsection - -@section('content-header') -

    @lang('base.api.new.header')@lang('base.api.new.header_sub')

    - -@endsection - -@section('content') -
    -
    -
    -
    -
    -
    - - -
    -

    Set an easy to understand description for this API key to help you identify it later on.

    -
    -
    -
    -
    -
    -
    -
    - - -
    -

    If you would like to limit this API key to specific IP addresses enter them above, one per line. CIDR notation is allowed for each IP address. Leave blank to allow any IP address.

    -
    - -
    -
    -
    -
    -@endsection diff --git a/resources/themes/pterodactyl/base/index.blade.php b/resources/themes/pterodactyl/base/index.blade.php deleted file mode 100644 index 8b44b48d..00000000 --- a/resources/themes/pterodactyl/base/index.blade.php +++ /dev/null @@ -1,108 +0,0 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} -@extends('layouts.master') - -@section('title') - @lang('base.index.header') -@endsection - -@section('content-header') -

    @lang('base.index.header')@lang('base.index.header_sub')

    - -@endsection - -@section('content') -
    -
    -
    -
    -

    @lang('base.index.list')

    -
    -
    -
    - -
    - -
    -
    -
    -
    -
    -
    - - - - - - - - - - - - - - @foreach($servers as $server) - - - - - - - - - - @if($server->node->maintenance_mode) - - @else - - @endif - - @if (! empty($server->description)) - - - - @endif - @endforeach - -
    @lang('strings.id')@lang('strings.name')@lang('strings.node')@lang('strings.connection')@lang('strings.relation')@lang('strings.status')
    description)) rowspan="2" @endif>{{ $server->uuidShort }}{{ $server->name }}{{ $server->getRelation('node')->name }}{{ $server->getRelation('allocation')->alias }}:{{ $server->getRelation('allocation')->port }} - @if($server->user->id === Auth::user()->id) - @lang('strings.owner') - @elseif(Auth::user()->root_admin) - @lang('strings.admin') - @else - @lang('strings.subuser') - @endif - - @lang('strings.under_maintenance') - - -

    {{ str_limit($server->description, 400) }}

    -
    - @if($servers->hasPages()) - - @endif -
    -
    -
    -@endsection - -@section('footer-scripts') - @parent - - {!! Theme::js('js/frontend/serverlist.js') !!} -@endsection diff --git a/resources/themes/pterodactyl/base/security.blade.php b/resources/themes/pterodactyl/base/security.blade.php deleted file mode 100644 index 7c4693dd..00000000 --- a/resources/themes/pterodactyl/base/security.blade.php +++ /dev/null @@ -1,135 +0,0 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} -@extends('layouts.master') - -@section('title') - @lang('base.security.header') -@endsection - -@section('content-header') -

    @lang('base.security.header')@lang('base.security.header_sub')

    - -@endsection - -@section('content') -
    -
    -
    -
    -

    @lang('base.security.sessions')

    -
    - @if(!is_null($sessions)) -
    - - - - - - - - - @foreach($sessions as $session) - - - - - - - @endforeach - -
    @lang('strings.id')@lang('strings.ip')@lang('strings.last_activity')
    {{ substr($session->id, 0, 6) }}{{ $session->ip_address }}{{ Carbon::createFromTimestamp($session->last_activity)->diffForHumans() }} - - - -
    -
    - @else -
    -

    @lang('base.security.session_mgmt_disabled')

    -
    - @endif -
    -
    -
    -
    -
    -

    @lang('base.security.2fa_header')

    -
    - @if(Auth::user()->use_totp) -
    -
    -

    @lang('base.security.2fa_enabled')

    -
    - -
    - -

    @lang('base.security.2fa_token_help')

    -
    -
    -
    - -
    - @else -
    -
    - @lang('base.security.2fa_disabled') -
    - -
    - @endif -
    -
    -
    - -@endsection - -@section('footer-scripts') - @parent - {!! Theme::js('js/frontend/2fa-modal.js') !!} -@endsection diff --git a/resources/themes/pterodactyl/errors/403.blade.php b/resources/themes/pterodactyl/errors/403.blade.php deleted file mode 100644 index f44fb6f1..00000000 --- a/resources/themes/pterodactyl/errors/403.blade.php +++ /dev/null @@ -1,30 +0,0 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} -@extends('layouts.error') - -@section('title') - @lang('base.errors.403.header') -@endsection - -@section('content-header') -@endsection - -@section('content') -
    -
    -
    -
    -

    403

    -

    @lang('base.errors.403.desc')

    -
    - -
    -
    -
    -@endsection diff --git a/resources/themes/pterodactyl/errors/404.blade.php b/resources/themes/pterodactyl/errors/404.blade.php deleted file mode 100644 index b178dccd..00000000 --- a/resources/themes/pterodactyl/errors/404.blade.php +++ /dev/null @@ -1,31 +0,0 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} -@extends('layouts.error') - -@section('title') - @lang('base.errors.404.header') -@endsection - -@section('content-header') -@endsection - -@section('content') - -
    -
    -
    -
    -

    404

    -

    @lang('base.errors.404.desc')

    -
    - -
    -
    -
    -@endsection diff --git a/resources/themes/pterodactyl/errors/503.blade.php b/resources/themes/pterodactyl/errors/503.blade.php deleted file mode 100644 index 7d55e46a..00000000 --- a/resources/themes/pterodactyl/errors/503.blade.php +++ /dev/null @@ -1,47 +0,0 @@ - - - - Be right back. - - - - - - -
    -
    -
    Be right back!
    -
    -
    - - diff --git a/resources/themes/pterodactyl/errors/installing.blade.php b/resources/themes/pterodactyl/errors/installing.blade.php deleted file mode 100644 index c9f43e65..00000000 --- a/resources/themes/pterodactyl/errors/installing.blade.php +++ /dev/null @@ -1,32 +0,0 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} -@extends('layouts.error') - -@section('title') - @lang('base.errors.installing.header') -@endsection - -@section('content-header') -@endsection - -@section('content') -
    -
    -
    -
    -
    -
    -
    -

    @lang('base.errors.installing.desc')

    -
    - -
    -
    -
    -@endsection diff --git a/resources/themes/pterodactyl/errors/maintenance.blade.php b/resources/themes/pterodactyl/errors/maintenance.blade.php deleted file mode 100644 index 8cc8eea2..00000000 --- a/resources/themes/pterodactyl/errors/maintenance.blade.php +++ /dev/null @@ -1,30 +0,0 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} -@extends('layouts.error') - -@section('title') - @lang('base.errors.maintenance.header') -@endsection - -@section('content-header') -@endsection - -@section('content') -
    -
    -
    -
    -

    @lang('base.errors.maintenance.title')

    -

    @lang('base.errors.maintenance.desc')

    -
    - -
    -
    -
    -@endsection diff --git a/resources/themes/pterodactyl/errors/suspended.blade.php b/resources/themes/pterodactyl/errors/suspended.blade.php deleted file mode 100644 index 86e804f6..00000000 --- a/resources/themes/pterodactyl/errors/suspended.blade.php +++ /dev/null @@ -1,30 +0,0 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} -@extends('layouts.error') - -@section('title') - @lang('base.errors.suspended.header') -@endsection - -@section('content-header') -@endsection - -@section('content') -
    -
    -
    -
    -

    403

    -

    @lang('base.errors.suspended.desc')

    -
    - -
    -
    -
    -@endsection diff --git a/resources/themes/pterodactyl/layouts/auth.blade.php b/resources/themes/pterodactyl/layouts/auth.blade.php deleted file mode 100644 index 108e1311..00000000 --- a/resources/themes/pterodactyl/layouts/auth.blade.php +++ /dev/null @@ -1,64 +0,0 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} - - - - - - {{ config('app.name', 'Pterodactyl') }} - @yield('title') - - - - - - - - - - - - @section('scripts') - {!! Theme::css('vendor/bootstrap/bootstrap.min.css?t={cache-version}') !!} - {!! Theme::css('vendor/adminlte/admin.min.css?t={cache-version}') !!} - {!! Theme::css('css/pterodactyl.css?t={cache-version}') !!} - - - - - @show - - -
    -
    - - @yield('content') - -
    -
    - - - {!! Theme::js('vendor/jquery/jquery.min.js?t={cache-version}') !!} - {!! Theme::js('vendor/bootstrap/bootstrap.min.js?t={cache-version}') !!} - {!! Theme::js('js/autocomplete.js?t={cache-version}') !!} - {!! Theme::js('vendor/particlesjs/particles.min.js?t={cache-version}') !!} - - - diff --git a/resources/themes/pterodactyl/layouts/error.blade.php b/resources/themes/pterodactyl/layouts/error.blade.php deleted file mode 100644 index 0ec1b00f..00000000 --- a/resources/themes/pterodactyl/layouts/error.blade.php +++ /dev/null @@ -1,70 +0,0 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} - - - - - - {{ config('app.name', 'Pterodactyl') }} - @yield('title') - - - - - - - - - - - - - @section('scripts') - {!! Theme::css('vendor/bootstrap/bootstrap.min.css?t={cache-version}') !!} - {!! Theme::css('vendor/adminlte/admin.min.css?t={cache-version}') !!} - {!! Theme::css('vendor/adminlte/colors/skin-blue.min.css?t={cache-version}') !!} - {!! Theme::css('vendor/sweetalert/sweetalert.min.css?t={cache-version}') !!} - {!! Theme::css('css/pterodactyl.css?t={cache-version}') !!} - - - - - @show - - -
    -
    - - -
    -
    -
    - @yield('content-header') -
    -
    - @yield('content') -
    -
    - -
    - @section('footer-scripts') - {!! Theme::js('js/laroute.js?t={cache-version}') !!} - {!! Theme::js('vendor/jquery/jquery.min.js?t={cache-version}') !!} - {!! Theme::js('vendor/bootstrap/bootstrap.min.js?t={cache-version}') !!} - {!! Theme::js('vendor/slimscroll/jquery.slimscroll.min.js?t={cache-version}') !!} - {!! Theme::js('vendor/adminlte/app.min.js?t={cache-version}') !!} - @show - - diff --git a/resources/themes/pterodactyl/layouts/master.blade.php b/resources/themes/pterodactyl/layouts/master.blade.php deleted file mode 100644 index f83a6f15..00000000 --- a/resources/themes/pterodactyl/layouts/master.blade.php +++ /dev/null @@ -1,311 +0,0 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} - - - - - - {{ config('app.name', 'Pterodactyl') }} - @yield('title') - - - - - - - - - - - - - @include('layouts.scripts') - - @section('scripts') - {!! Theme::css('vendor/bootstrap/bootstrap.min.css?t={cache-version}') !!} - {!! Theme::css('vendor/adminlte/admin.min.css?t={cache-version}') !!} - {!! Theme::css('vendor/adminlte/colors/skin-blue.min.css?t={cache-version}') !!} - {!! Theme::css('vendor/sweetalert/sweetalert.min.css?t={cache-version}') !!} - {!! Theme::css('vendor/animate/animate.min.css?t={cache-version}') !!} - {!! Theme::css('css/pterodactyl.css?t={cache-version}') !!} - - - - - @show - - -
    -
    - - -
    - -
    -
    - @yield('content-header') -
    -
    -
    -
    - @if (count($errors) > 0) -
    - @lang('base.validation_error')

    -
      - @foreach ($errors->all() as $error) -
    • {{ $error }}
    • - @endforeach -
    -
    - @endif - @foreach (Alert::getMessages() as $type => $messages) - @foreach ($messages as $message) - - @endforeach - @endforeach -
    -
    - @yield('content') -
    -
    -
    -
    - {{ $appVersion }}
    - {{ round(microtime(true) - LARAVEL_START, 3) }}s -
    - Copyright © 2015 - {{ date('Y') }} Pterodactyl Software. -
    - @if(isset($sidebarServerList)) - - @endif -
    -
    - @section('footer-scripts') - {!! Theme::js('js/keyboard.polyfill.js?t={cache-version}') !!} - - - {!! Theme::js('js/laroute.js?t={cache-version}') !!} - {!! Theme::js('vendor/jquery/jquery.min.js?t={cache-version}') !!} - {!! Theme::js('vendor/sweetalert/sweetalert.min.js?t={cache-version}') !!} - {!! Theme::js('vendor/bootstrap/bootstrap.min.js?t={cache-version}') !!} - {!! Theme::js('vendor/slimscroll/jquery.slimscroll.min.js?t={cache-version}') !!} - {!! Theme::js('vendor/adminlte/app.min.js?t={cache-version}') !!} - {!! Theme::js('vendor/socketio/socket.io.v203.min.js?t={cache-version}') !!} - {!! Theme::js('vendor/bootstrap-notify/bootstrap-notify.min.js?t={cache-version}') !!} - {!! Theme::js('js/autocomplete.js?t={cache-version}') !!} - - @if(Auth::user()->root_admin) - - @endif - @show - - diff --git a/resources/themes/pterodactyl/server/console.blade.php b/resources/themes/pterodactyl/server/console.blade.php deleted file mode 100644 index d1dea1d5..00000000 --- a/resources/themes/pterodactyl/server/console.blade.php +++ /dev/null @@ -1,45 +0,0 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} - - - - {{ config('app.name', 'Pterodactyl') }} - Console → {{ $server->name }} - @include('layouts.scripts') - {!! Theme::css('vendor/bootstrap/bootstrap.min.css') !!} - {!! Theme::css('css/terminal.css') !!} - - - -
    -
    -
    -
    container:~/$
    - -
    -
    - - - - {!! Theme::js('js/laroute.js') !!} - {!! Theme::js('vendor/ansi/ansi_up.js') !!} - {!! Theme::js('vendor/jquery/jquery.min.js') !!} - {!! Theme::js('vendor/socketio/socket.io.v203.min.js') !!} - {!! Theme::js('vendor/bootstrap-notify/bootstrap-notify.min.js') !!} - {!! Theme::js('js/frontend/server.socket.js') !!} - {!! Theme::js('vendor/mousewheel/jquery.mousewheel-min.js') !!} - {!! Theme::js('js/frontend/console.js') !!} - - diff --git a/resources/themes/pterodactyl/server/databases/index.blade.php b/resources/themes/pterodactyl/server/databases/index.blade.php deleted file mode 100644 index 99b1e096..00000000 --- a/resources/themes/pterodactyl/server/databases/index.blade.php +++ /dev/null @@ -1,198 +0,0 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} -@extends('layouts.master') - -@section('title') - @lang('server.config.database.header') -@endsection - -@section('content-header') -

    @lang('server.config.database.header')@lang('server.config.database.header_sub')

    - -@endsection - -@section('content') -
    -
    -
    -
    -

    @lang('server.config.database.your_dbs')

    -
    - @if(count($databases) > 0) -
    - - - - - - - - @can('reset-db-password', $server)@endcan - - @foreach($databases as $database) - - - - - - @if(Gate::allows('reset-db-password', $server) || Gate::allows('delete-database', $server)) - - @endif - - @endforeach - -
    @lang('strings.database')@lang('strings.username')@lang('strings.password')@lang('server.config.database.host')
    {{ $database->database }}{{ $database->username }} - - •••••••• - - - {{ $database->host->host }}:{{ $database->host->port }} - @can('delete-database', $server) - - @endcan - @can('reset-db-password', $server) - - @endcan -
    -
    - @else -
    -
    - @lang('server.config.database.no_dbs') -
    -
    - @endif -
    -
    - @if($allowCreation && Gate::allows('create-database', $server)) -
    -
    -
    -

    Create New Database

    -
    - @if($overLimit) -
    -
    - You are currently using {{ count($databases) }} of your {{ $server->database_limit ?? '∞' }} allowed databases. -
    -
    - @else -
    -
    -
    - -
    - s{{ $server->id }}_ - -
    -
    -
    - - -

    This should reflect the IP address that connections are allowed from. Uses standard MySQL notation. If unsure leave as %.

    -
    -
    - -
    - @endif -
    -
    - @endif -
    -@endsection - -@section('footer-scripts') - @parent - {!! Theme::js('js/frontend/server.socket.js') !!} - -@endsection diff --git a/resources/themes/pterodactyl/server/files/add.blade.php b/resources/themes/pterodactyl/server/files/add.blade.php deleted file mode 100644 index fd6bf736..00000000 --- a/resources/themes/pterodactyl/server/files/add.blade.php +++ /dev/null @@ -1,98 +0,0 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} -@extends('layouts.master') - -@section('title') - @lang('server.files.add.header') -@endsection - -@section('scripts') - {{-- This has to be loaded before the AdminLTE theme to avoid dropdown issues. --}} - {!! Theme::css('vendor/select2/select2.min.css') !!} - @parent -@endsection - -@section('content-header') -

    @lang('server.files.add.header')@lang('server.files.add.header_sub')

    - -@endsection - -@section('content') -
    -
    -
    -
    -
    - /home/container/ - -
    -
    -
    - -
    -
    -
    -@endsection - -@section('footer-scripts') - @parent - {!! Theme::js('vendor/select2/select2.full.min.js') !!} - {!! Theme::js('js/frontend/server.socket.js') !!} - {!! Theme::js('vendor/ace/ace.js') !!} - {!! Theme::js('vendor/ace/ext-modelist.js') !!} - {!! Theme::js('vendor/ace/ext-whitespace.js') !!} - {!! Theme::js('vendor/lodash/lodash.js') !!} - {!! Theme::js('js/frontend/files/editor.js') !!} - -@endsection diff --git a/resources/themes/pterodactyl/server/files/edit.blade.php b/resources/themes/pterodactyl/server/files/edit.blade.php deleted file mode 100644 index 6716a164..00000000 --- a/resources/themes/pterodactyl/server/files/edit.blade.php +++ /dev/null @@ -1,59 +0,0 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} -@extends('layouts.master') - -@section('title') - @lang('server.files.edit.header') -@endsection - -@section('content-header') -

    @lang('server.files.edit.header')@lang('server.files.edit.header_sub')

    - -@endsection - -@section('content') -
    -
    -
    -
    -

    {{ $file }}

    - -
    - - -
    -
    - -
    -
    -
    -@endsection - -@section('footer-scripts') - @parent - {!! Theme::js('js/frontend/server.socket.js') !!} - {!! Theme::js('vendor/ace/ace.js') !!} - {!! Theme::js('vendor/ace/ext-modelist.js') !!} - {!! Theme::js('vendor/ace/ext-whitespace.js') !!} - {!! Theme::js('js/frontend/files/editor.js') !!} - -@endsection diff --git a/resources/themes/pterodactyl/server/files/index.blade.php b/resources/themes/pterodactyl/server/files/index.blade.php deleted file mode 100644 index 4dd0c090..00000000 --- a/resources/themes/pterodactyl/server/files/index.blade.php +++ /dev/null @@ -1,54 +0,0 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} -@extends('layouts.master') - -@section('title') - @lang('server.files.header') -@endsection - -@section('content-header') -

    @lang('server.files.header')@lang('server.files.header_sub')

    - -@endsection - -@section('content') -
    -
    -
    -
    -
    -
    -
    @lang('server.files.loading')
    -
    -
    - -
    -
    -
    -@endsection - -@section('footer-scripts') - @parent - {!! Theme::js('js/frontend/server.socket.js') !!} - {!! Theme::js('vendor/async/async.min.js') !!} - {!! Theme::js('vendor/lodash/lodash.js') !!} - {!! Theme::js('vendor/siofu/client.min.js') !!} - @if(App::environment('production')) - {!! Theme::js('js/frontend/files/filemanager.min.js?hash=cd7ec731dc633e23ec36144929a237d18c07d2f0') !!} - @else - {!! Theme::js('js/frontend/files/src/index.js') !!} - {!! Theme::js('js/frontend/files/src/contextmenu.js') !!} - {!! Theme::js('js/frontend/files/src/actions.js') !!} - @endif - {!! Theme::js('js/frontend/files/upload.js') !!} -@endsection diff --git a/resources/themes/pterodactyl/server/files/list.blade.php b/resources/themes/pterodactyl/server/files/list.blade.php deleted file mode 100644 index e6a252e5..00000000 --- a/resources/themes/pterodactyl/server/files/list.blade.php +++ /dev/null @@ -1,170 +0,0 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} - -
    -

    /home/container{{ $directory['header'] }}

    -
    - - - - - - -
    -
    -
    - - - - - - - - - - - - @if (isset($directory['first']) && $directory['first'] === true) - - - - - - - - @endif - @if (isset($directory['show']) && $directory['show'] === true) - - - - - - - - @endif - @foreach ($folders as $folder) - - - - - - - - @endforeach - @foreach ($files as $file) - - - - - - - - @endforeach - -
    - - @lang('server.files.file_name')
    - ← {{ $directory['link_show'] }} -
    - - - {{ $folder['entry'] }} - - -
    - {{-- oh boy --}} - @if(in_array($file['mime'], [ - 'application/x-7z-compressed', - 'application/zip', - 'application/x-compressed-zip', - 'application/x-tar', - 'application/x-gzip', - 'application/x-bzip', - 'application/x-bzip2', - 'application/java-archive' - ])) - - @elseif(in_array($file['mime'], [ - 'application/json', - 'application/javascript', - 'application/xml', - 'application/xhtml+xml', - 'text/xml', - 'text/css', - 'text/html', - 'text/x-perl', - 'text/x-shellscript' - ])) - - @elseif(starts_with($file['mime'], 'image')) - - @elseif(starts_with($file['mime'], 'video')) - - @elseif(starts_with($file['mime'], 'video')) - - @elseif(starts_with($file['mime'], 'application/vnd.ms-powerpoint')) - - @elseif(in_array($file['mime'], [ - 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', - 'application/vnd.openxmlformats-officedocument.wordprocessingml.template', - 'application/msword' - ]) || starts_with($file['mime'], 'application/vnd.ms-word')) - - @elseif(in_array($file['mime'], [ - 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', - 'application/vnd.openxmlformats-officedocument.spreadsheetml.template', - ]) || starts_with($file['mime'], 'application/vnd.ms-excel')) - - @elseif($file['mime'] === 'application/pdf') - - @else - - @endif - - @if(in_array($file['mime'], $editableMime)) - @can('edit-files', $server) - {{ $file['entry'] }} - @else - {{ $file['entry'] }} - @endcan - @else - {{ $file['entry'] }} - @endif - - -
    -
    diff --git a/resources/themes/pterodactyl/server/index.blade.php b/resources/themes/pterodactyl/server/index.blade.php deleted file mode 100644 index 9cb4ba4a..00000000 --- a/resources/themes/pterodactyl/server/index.blade.php +++ /dev/null @@ -1,85 +0,0 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} -@extends('layouts.master') - -@section('title') - {{ trans('server.index.title', [ 'name' => $server->name]) }} -@endsection - -@section('scripts') - @parent - {!! Theme::css('css/terminal.css') !!} -@endsection - -@section('content-header') -

    @lang('server.index.header')@lang('server.index.header_sub')

    - -@endsection - -@section('content') -
    -
    -
    -
    -
    -
    -
    -
    container:~/$
    - -
    -
    - -
    - -
    -
    -
    -
    -
    -
    -
    -

    Memory Usage

    -
    -
    - -
    -
    -
    -
    -
    -
    -

    CPU Usage

    -
    -
    - -
    -
    -
    -
    -@endsection - -@section('footer-scripts') - @parent - {!! Theme::js('vendor/ansi/ansi_up.js') !!} - {!! Theme::js('js/frontend/server.socket.js') !!} - {!! Theme::js('vendor/mousewheel/jquery.mousewheel-min.js') !!} - {!! Theme::js('js/frontend/console.js') !!} - {!! Theme::js('vendor/chartjs/chart.min.js') !!} - {!! Theme::js('vendor/jquery/date-format.min.js') !!} - @if($server->nest->name === 'Minecraft' && $server->nest->author === 'support@pterodactyl.io') - {!! Theme::js('js/plugins/minecraft/eula.js') !!} - @endif -@endsection diff --git a/resources/themes/pterodactyl/server/schedules/index.blade.php b/resources/themes/pterodactyl/server/schedules/index.blade.php deleted file mode 100644 index aefcef5d..00000000 --- a/resources/themes/pterodactyl/server/schedules/index.blade.php +++ /dev/null @@ -1,98 +0,0 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} -@extends('layouts.master') - -@section('title') - @lang('server.schedule.header') -@endsection - -@section('content-header') -

    @lang('server.schedule.header')@lang('server.schedule.header_sub')

    - -@endsection - -@section('content') -
    -
    -
    -
    -

    @lang('server.schedule.current')

    - -
    -
    - - - - - - - - - - - @foreach($schedules as $schedule) - is_active)class="muted muted-hover"@endif> - - - - - - - - @endforeach - -
    @lang('strings.name')@lang('strings.queued')@lang('strings.tasks')@lang('strings.last_run')@lang('strings.next_run')
    - @can('edit-schedule', $server) - - {{ $schedule->name ?? trans('server.schedule.unnamed') }} - - @else - {{ $schedule->name ?? trans('server.schedule.unnamed') }} - @endcan - - @if ($schedule->is_processing) - @lang('strings.yes') - @else - @lang('strings.no') - @endif - {{ $schedule->tasks_count }} - @if($schedule->last_run_at) - {{ Carbon::parse($schedule->last_run_at)->toDayDateTimeString() }}
    ({{ Carbon::parse($schedule->last_run_at)->diffForHumans() }}) - @else - @lang('strings.not_run_yet') - @endif -
    - @if($schedule->is_active) - {{ Carbon::parse($schedule->next_run_at)->toDayDateTimeString() }}
    ({{ Carbon::parse($schedule->next_run_at)->diffForHumans() }}) - @else - n/a - @endif -
    - @can('delete-schedule', $server) - - @endcan - @can('toggle-schedule', $server) - - - @endcan -
    -
    -
    -
    -
    -@endsection - -@section('footer-scripts') - @parent - {!! Theme::js('js/frontend/server.socket.js') !!} - {!! Theme::js('js/frontend/tasks/management-actions.js') !!} -@endsection diff --git a/resources/themes/pterodactyl/server/schedules/new.blade.php b/resources/themes/pterodactyl/server/schedules/new.blade.php deleted file mode 100644 index 3202c525..00000000 --- a/resources/themes/pterodactyl/server/schedules/new.blade.php +++ /dev/null @@ -1,145 +0,0 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} -@extends('layouts.master') - -@section('title') - @lang('server.schedule.new.header') -@endsection - -@section('scripts') - {{-- This has to be loaded before the AdminLTE theme to avoid dropdown issues. --}} - {!! Theme::css('vendor/select2/select2.min.css') !!} - @parent -@endsection - -@section('content-header') -

    @lang('server.schedule.new.header')@lang('server.schedule.new.header_sub')

    - -@endsection - -@section('content') -
    -
    -
    -
    -
    -

    @lang('server.schedule.setup')

    -
    -
    -
    -
    - -
    - -
    -
    -
    -
    -
    -
    - -
    - -
    -
    -
    - -
    -
    -
    -
    - -
    - -
    -
    -
    - -
    -
    -
    -
    - -
    - -
    -
    -
    - -
    -
    -
    -
    - -
    - -
    -
    -
    - -
    -
    -
    -
    - -
    -
    -
    -
    -
    -
    - @include('partials.schedules.task-template') - -
    -
    -
    -
    -@endsection - -@section('footer-scripts') - @parent - {!! Theme::js('js/frontend/server.socket.js') !!} - {!! Theme::js('vendor/select2/select2.full.min.js') !!} - {!! Theme::js('js/frontend/tasks/view-actions.js') !!} -@endsection diff --git a/resources/themes/pterodactyl/server/schedules/view.blade.php b/resources/themes/pterodactyl/server/schedules/view.blade.php deleted file mode 100644 index 370b4f0b..00000000 --- a/resources/themes/pterodactyl/server/schedules/view.blade.php +++ /dev/null @@ -1,163 +0,0 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} -@extends('layouts.master') - -@section('title') - @lang('server.schedules.edit.header') -@endsection - -@section('scripts') - {{-- This has to be loaded before the AdminLTE theme to avoid dropdown issues. --}} - {!! Theme::css('vendor/select2/select2.min.css') !!} - @parent -@endsection - -@section('content-header') -

    @lang('server.schedule.manage.header'){{ $schedule->name }}

    - -@endsection - -@section('content') -
    -
    -
    -
    -
    -
    -
    - -
    - -
    -
    -
    -
    -
    -
    - -
    - -
    -
    -
    - -
    -
    -
    -
    - -
    - -
    -
    -
    - -
    -
    -
    -
    - -
    - -
    -
    -
    - -
    -
    -
    -
    - -
    - -
    -
    -
    - -
    -
    -
    -
    - -
    -
    -
    -
    -
    -
    - @include('partials.schedules.task-template') - -
    -
    -
    -
    -@endsection - -@section('footer-scripts') - @parent - {!! Theme::js('js/frontend/server.socket.js') !!} - {!! Theme::js('vendor/select2/select2.full.min.js') !!} - {!! Theme::js('js/frontend/tasks/view-actions.js') !!} - -@endsection diff --git a/resources/themes/pterodactyl/server/settings/allocation.blade.php b/resources/themes/pterodactyl/server/settings/allocation.blade.php deleted file mode 100644 index cc195240..00000000 --- a/resources/themes/pterodactyl/server/settings/allocation.blade.php +++ /dev/null @@ -1,120 +0,0 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} -@extends('layouts.master') - -@section('title') - @lang('server.config.allocation.header') -@endsection - -@section('content-header') -

    @lang('server.config.allocation.header')@lang('server.config.allocation.header_sub')

    - -@endsection - -@section('content') -
    -
    -
    -
    -

    @lang('server.config.allocation.available')

    -
    -
    - - - - - - - - - @foreach ($allocations as $allocation) - - - - - - - @endforeach - -
    @lang('strings.ip')@lang('strings.alias')@lang('strings.port')
    - {{ $allocation->ip }} - - @if(is_null($allocation->ip_alias)) - @lang('strings.none') - @else - {{ $allocation->ip_alias }} - @endif - {{ $allocation->port }} - @if($allocation->id === $server->allocation_id) - @lang('strings.primary') - @else - @lang('strings.make_primary') - @endif -
    -
    - -
    -
    -
    -
    -
    -

    @lang('server.config.allocation.help')

    -
    -
    -

    @lang('server.config.allocation.help_text')

    -
    -
    -
    -
    -@endsection - -@section('footer-scripts') - @parent - {!! Theme::js('js/frontend/server.socket.js') !!} - -@endsection diff --git a/resources/themes/pterodactyl/server/settings/name.blade.php b/resources/themes/pterodactyl/server/settings/name.blade.php deleted file mode 100644 index 6d59162d..00000000 --- a/resources/themes/pterodactyl/server/settings/name.blade.php +++ /dev/null @@ -1,50 +0,0 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} -@extends('layouts.master') - -@section('title') - @lang('server.config.name.header') -@endsection - -@section('content-header') -

    @lang('server.config.name.header')@lang('server.config.name.header_sub')

    - -@endsection - -@section('content') -
    -
    -
    -
    -
    -
    - -
    - -

    @lang('server.config.name.details')

    -
    -
    -
    - -
    -
    -
    -
    -@endsection - -@section('footer-scripts') - @parent - {!! Theme::js('js/frontend/server.socket.js') !!} -@endsection diff --git a/resources/themes/pterodactyl/server/settings/sftp.blade.php b/resources/themes/pterodactyl/server/settings/sftp.blade.php deleted file mode 100644 index 5da21ef7..00000000 --- a/resources/themes/pterodactyl/server/settings/sftp.blade.php +++ /dev/null @@ -1,54 +0,0 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} -@extends('layouts.master') - -@section('title') - @lang('server.config.sftp.header') -@endsection - -@section('content-header') -

    @lang('server.config.sftp.header')@lang('server.config.sftp.header_sub')

    - -@endsection - -@section('content') -
    -
    -
    -
    -

    @lang('server.config.sftp.details')

    -
    -
    -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    - -
    -
    -
    -@endsection - -@section('footer-scripts') - @parent - {!! Theme::js('js/frontend/server.socket.js') !!} -@endsection diff --git a/resources/themes/pterodactyl/server/settings/startup.blade.php b/resources/themes/pterodactyl/server/settings/startup.blade.php deleted file mode 100644 index 594dae4d..00000000 --- a/resources/themes/pterodactyl/server/settings/startup.blade.php +++ /dev/null @@ -1,87 +0,0 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} -@extends('layouts.master') - -@section('title') - @lang('server.config.startup.header') -@endsection - -@section('content-header') -

    @lang('server.config.startup.header')@lang('server.config.startup.header_sub')

    - -@endsection - -@section('content') -
    -
    -
    -
    -

    @lang('server.config.startup.command')

    -
    -
    -
    - -
    -
    -
    -
    - @can('edit-startup', $server) -
    - @foreach($variables as $v) -
    -
    -
    -

    {{ $v->name }}

    -
    -
    - user_editable) - name="environment[{{ $v->env_variable }}]" - @else - readonly - @endif - class="form-control" type="text" value="{{ old('environment.' . $v->env_variable, $server_values[$v->env_variable]) }}" /> -

    {{ $v->description }}

    -

    - @if($v->required && $v->user_editable ) - @lang('strings.required') - @elseif(! $v->required && $v->user_editable) - @lang('strings.optional') - @endif - @if(! $v->user_editable) - @lang('strings.read_only') - @endif -

    -
    - -
    -
    - @endforeach -
    -
    - -
    -
    -
    - @endcan -
    -@endsection - -@section('footer-scripts') - @parent - {!! Theme::js('js/frontend/server.socket.js') !!} -@endsection diff --git a/resources/themes/pterodactyl/server/users/index.blade.php b/resources/themes/pterodactyl/server/users/index.blade.php deleted file mode 100644 index 3d08e590..00000000 --- a/resources/themes/pterodactyl/server/users/index.blade.php +++ /dev/null @@ -1,132 +0,0 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} -@extends('layouts.master') - -@section('title') - @lang('server.users.header') -@endsection - -@section('content-header') -

    @lang('server.users.header')@lang('server.users.header_sub')

    - -@endsection - -@section('content') -
    -
    -
    -
    -

    @lang('server.users.list')

    - @can('create-subuser', $server) - - @endcan -
    -
    - - - - - - - - - @can('view-subuser', $server)@endcan - @can('delete-subuser', $server)@endcan - - @foreach($subusers as $subuser) - - - - - - @can('view-subuser', $server) - - @endcan - @can('delete-subuser', $server) - - @endcan - - @endforeach - -
    @lang('strings.username')@lang('strings.email')@lang('strings.2fa')
    User Image{{ $subuser->user->username }} - {{ $subuser->user->email }} - @if($subuser->user->use_totp) - - @else - - @endif - - - - - - - - -
    -
    -
    -
    -
    -@endsection - -@section('footer-scripts') - @parent - {!! Theme::js('js/frontend/server.socket.js') !!} - - -@endsection diff --git a/resources/themes/pterodactyl/server/users/new.blade.php b/resources/themes/pterodactyl/server/users/new.blade.php deleted file mode 100644 index 81231a70..00000000 --- a/resources/themes/pterodactyl/server/users/new.blade.php +++ /dev/null @@ -1,91 +0,0 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} -@extends('layouts.master') - -@section('title') - @lang('server.users.new.header') -@endsection - -@section('content-header') -

    @lang('server.users.new.header')@lang('server.users.new.header_sub')

    - -@endsection - -@section('content') - -
    -
    -
    -
    -
    -
    - -
    - {!! csrf_field() !!} - -

    @lang('server.users.new.email_help')

    -
    -
    -
    - -
    -
    -
    -
    - @foreach($permissions as $block => $perms) -
    -
    -
    -

    @lang('server.users.new.' . $block . '_header')

    -
    -
    - @foreach($perms as $permission => $daemon) -
    -
    - - -
    -

    @lang('server.users.new.' . str_replace('-', '_', $permission) . '.description')

    -
    - @endforeach -
    -
    -
    - @if ($loop->iteration % 2 === 0) -
    - @endif - @endforeach -
    -
    -@endsection - -@section('footer-scripts') - @parent - {!! Theme::js('js/frontend/server.socket.js') !!} - -@endsection diff --git a/resources/themes/pterodactyl/server/users/view.blade.php b/resources/themes/pterodactyl/server/users/view.blade.php deleted file mode 100644 index f175bb44..00000000 --- a/resources/themes/pterodactyl/server/users/view.blade.php +++ /dev/null @@ -1,96 +0,0 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} -@extends('layouts.master') - -@section('title') - @lang('server.users.new.header') -@endsection - -@section('content-header') -

    @lang('server.users.edit.header')@lang('server.users.edit.header_sub')

    - -@endsection - -@section('content') -@can('edit-subuser', $server) -
    -@endcan -
    -
    -
    -
    -
    - -
    - {!! csrf_field() !!} - -
    -
    -
    - @can('edit-subuser', $server) -
    - - {!! method_field('PATCH') !!} - -
    - @endcan -
    -
    -
    -
    - @foreach($permlist as $block => $perms) -
    -
    -
    -

    @lang('server.users.new.' . $block . '_header')

    -
    -
    - @foreach($perms as $permission => $daemon) -
    -
    - - -
    -

    @lang('server.users.new.' . str_replace('-', '_', $permission) . '.description')

    -
    - @endforeach -
    -
    -
    - @if ($loop->iteration % 2 === 0) -
    - @endif - @endforeach -
    -@can('edit-subuser', $server) -
    -@endcan -@endsection - -@section('footer-scripts') - @parent - {!! Theme::js('js/frontend/server.socket.js') !!} - -@endsection diff --git a/resources/themes/pterodactyl/admin/api/index.blade.php b/resources/views/admin/api/index.blade.php similarity index 97% rename from resources/themes/pterodactyl/admin/api/index.blade.php rename to resources/views/admin/api/index.blade.php index 7d82b329..d863c577 100644 --- a/resources/themes/pterodactyl/admin/api/index.blade.php +++ b/resources/views/admin/api/index.blade.php @@ -77,7 +77,7 @@ }, function () { $.ajax({ method: 'DELETE', - url: Router.route('admin.api.delete', { identifier: self.data('attr') }), + url: '/admin/api/revoke/' + self.data('attr'), headers: { 'X-CSRF-TOKEN': '{{ csrf_token() }}' } diff --git a/resources/themes/pterodactyl/admin/api/new.blade.php b/resources/views/admin/api/new.blade.php similarity index 100% rename from resources/themes/pterodactyl/admin/api/new.blade.php rename to resources/views/admin/api/new.blade.php diff --git a/resources/themes/pterodactyl/admin/databases/index.blade.php b/resources/views/admin/databases/index.blade.php similarity index 100% rename from resources/themes/pterodactyl/admin/databases/index.blade.php rename to resources/views/admin/databases/index.blade.php diff --git a/resources/themes/pterodactyl/admin/databases/view.blade.php b/resources/views/admin/databases/view.blade.php similarity index 95% rename from resources/themes/pterodactyl/admin/databases/view.blade.php rename to resources/views/admin/databases/view.blade.php index 655b833f..66eb170e 100644 --- a/resources/themes/pterodactyl/admin/databases/view.blade.php +++ b/resources/views/admin/databases/view.blade.php @@ -99,6 +99,7 @@ Database Name Username Connections From + Max Connections @foreach($databases as $database) @@ -107,6 +108,11 @@ {{ $database->database }} {{ $database->username }} {{ $database->remote }} + @if($database->max_connections != null) + {{ $database->max_connections }} + @else + Unlimited + @endif diff --git a/resources/themes/pterodactyl/admin/eggs/new.blade.php b/resources/views/admin/eggs/new.blade.php similarity index 100% rename from resources/themes/pterodactyl/admin/eggs/new.blade.php rename to resources/views/admin/eggs/new.blade.php diff --git a/resources/themes/pterodactyl/admin/eggs/scripts.blade.php b/resources/views/admin/eggs/scripts.blade.php similarity index 100% rename from resources/themes/pterodactyl/admin/eggs/scripts.blade.php rename to resources/views/admin/eggs/scripts.blade.php diff --git a/resources/themes/pterodactyl/admin/eggs/variables.blade.php b/resources/views/admin/eggs/variables.blade.php similarity index 95% rename from resources/themes/pterodactyl/admin/eggs/variables.blade.php rename to resources/views/admin/eggs/variables.blade.php index a01ea713..2441c725 100644 --- a/resources/themes/pterodactyl/admin/eggs/variables.blade.php +++ b/resources/views/admin/eggs/variables.blade.php @@ -48,7 +48,7 @@

    {{ $variable->name }}

    -
    +
    -
    -
    - Notice: Editing an Egg or any of the Process Management fields requires that each Daemon be rebooted in order to apply the changes. -
    -
    @@ -94,7 +89,7 @@
    - +

    A description of this Egg that will be displayed throughout the Panel as needed.

    @@ -160,18 +155,13 @@
    -
    -
    - Notice: Editing an Egg or any of the Process Management fields requires that each Daemon be rebooted in order to apply the changes. -
    -
    @endsection diff --git a/resources/themes/pterodactyl/admin/index.blade.php b/resources/views/admin/index.blade.php similarity index 83% rename from resources/themes/pterodactyl/admin/index.blade.php rename to resources/views/admin/index.blade.php index a0cb3097..6c1364a7 100644 --- a/resources/themes/pterodactyl/admin/index.blade.php +++ b/resources/views/admin/index.blade.php @@ -45,14 +45,14 @@
     
    @endsection diff --git a/resources/themes/pterodactyl/admin/locations/index.blade.php b/resources/views/admin/locations/index.blade.php similarity index 98% rename from resources/themes/pterodactyl/admin/locations/index.blade.php rename to resources/views/admin/locations/index.blade.php index 53bd3cd7..3d5128db 100644 --- a/resources/themes/pterodactyl/admin/locations/index.blade.php +++ b/resources/views/admin/locations/index.blade.php @@ -70,7 +70,7 @@
    -

    A longer description of this location. Must be less than 255 characters.

    +

    A longer description of this location. Must be less than 191 characters.

    diff --git a/resources/themes/pterodactyl/admin/locations/view.blade.php b/resources/views/admin/locations/view.blade.php similarity index 100% rename from resources/themes/pterodactyl/admin/locations/view.blade.php rename to resources/views/admin/locations/view.blade.php diff --git a/resources/views/admin/mounts/index.blade.php b/resources/views/admin/mounts/index.blade.php new file mode 100644 index 00000000..309dc3c1 --- /dev/null +++ b/resources/views/admin/mounts/index.blade.php @@ -0,0 +1,149 @@ +{{-- Pterodactyl - Panel --}} +{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} + +{{-- This software is licensed under the terms of the MIT license. --}} +{{-- https://opensource.org/licenses/MIT --}} + +@extends('layouts.admin') + +@section('title') + Mounts +@endsection + +@section('content-header') +

    MountsSoonTM

    + +@endsection + +@section('content') +
    +
    +
    +
    +

    Mount List

    + +
    + +
    +
    + +
    + + + + + + + + + + + + + @foreach ($mounts as $mount) + + + + + + + + + + @endforeach + +
    IDNameSourceTargetEggsNodesServers
    {{ $mount->id }}{{ $mount->name }}{{ $mount->source }}{{ $mount->target }}{{ $mount->eggs_count }}{{ $mount->nodes_count }}{{ $mount->servers_count }}
    +
    +
    +
    +
    + + +@endsection diff --git a/resources/views/admin/mounts/view.blade.php b/resources/views/admin/mounts/view.blade.php new file mode 100644 index 00000000..f53007f3 --- /dev/null +++ b/resources/views/admin/mounts/view.blade.php @@ -0,0 +1,319 @@ +{{-- Pterodactyl - Panel --}} +{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} + +{{-- This software is licensed under the terms of the MIT license. --}} +{{-- https://opensource.org/licenses/MIT --}} + +@extends('layouts.admin') + +@section('title') + Mounts → View → {{ $mount->id }} +@endsection + +@section('content-header') +

    {{ $mount->name }}{{ str_limit($mount->description, 75) }}

    + +@endsection + +@section('content') +
    +
    +
    +
    +

    Mount Details

    +
    + +
    +
    +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    +
    + + +
    + +
    + + +
    +
    + +
    +
    + + +
    +
    + read_only) checked @endif> + +
    + +
    + read_only) checked @endif> + +
    +
    +
    + +
    + + +
    +
    + user_mountable) checked @endif> + +
    + +
    + user_mountable) checked @endif> + +
    +
    +
    +
    +
    + + +
    +
    +
    + +
    +
    +
    +

    Eggs

    + +
    + +
    +
    + +
    + + + + + + + + @foreach ($mount->eggs as $egg) + + + + + + @endforeach +
    IDName
    {{ $egg->id }}{{ $egg->name }} + +
    +
    +
    + +
    +
    +

    Nodes

    + +
    + +
    +
    + +
    + + + + + + + + + @foreach ($mount->nodes as $node) + + + + + + + @endforeach +
    IDNameFQDN
    {{ $node->id }}{{ $node->name }}{{ $node->fqdn }} + +
    +
    +
    +
    +
    + + + + +@endsection + +@section('footer-scripts') + @parent + + +@endsection diff --git a/resources/themes/pterodactyl/admin/nests/index.blade.php b/resources/views/admin/nests/index.blade.php similarity index 97% rename from resources/themes/pterodactyl/admin/nests/index.blade.php rename to resources/views/admin/nests/index.blade.php index 3c726964..b45f1187 100644 --- a/resources/themes/pterodactyl/admin/nests/index.blade.php +++ b/resources/views/admin/nests/index.blade.php @@ -42,7 +42,6 @@ Name Description Eggs - Packs Servers @foreach($nests as $nest) @@ -51,7 +50,6 @@ {{ $nest->name }} {{ $nest->description }} {{ $nest->eggs_count }} - {{ $nest->packs_count }} {{ $nest->servers_count }} @endforeach diff --git a/resources/themes/pterodactyl/admin/nests/new.blade.php b/resources/views/admin/nests/new.blade.php similarity index 100% rename from resources/themes/pterodactyl/admin/nests/new.blade.php rename to resources/views/admin/nests/new.blade.php diff --git a/resources/themes/pterodactyl/admin/nests/view.blade.php b/resources/views/admin/nests/view.blade.php similarity index 99% rename from resources/themes/pterodactyl/admin/nests/view.blade.php rename to resources/views/admin/nests/view.blade.php index d54155e5..4dff5250 100644 --- a/resources/themes/pterodactyl/admin/nests/view.blade.php +++ b/resources/views/admin/nests/view.blade.php @@ -32,7 +32,7 @@
    - +
    diff --git a/resources/themes/pterodactyl/admin/nodes/index.blade.php b/resources/views/admin/nodes/index.blade.php similarity index 90% rename from resources/themes/pterodactyl/admin/nodes/index.blade.php rename to resources/views/admin/nodes/index.blade.php index b4ea579a..524d8110 100644 --- a/resources/themes/pterodactyl/admin/nodes/index.blade.php +++ b/resources/views/admin/nodes/index.blade.php @@ -28,10 +28,10 @@

    Node List

    -
    +
    - +
    @@ -55,7 +55,7 @@ @foreach ($nodes as $node) - + {!! $node->maintenance_mode ? ' ' : '' !!}{{ $node->name }} {{ $node->location->short }} {{ $node->memory }} MB @@ -87,7 +87,7 @@ type: 'GET', url: $(element).data('location'), headers: { - 'X-Access-Token': $(element).data('secret'), + 'Authorization': 'Bearer ' + $(element).data('secret'), }, timeout: 5000 }).done(function (data) { diff --git a/resources/themes/pterodactyl/admin/nodes/new.blade.php b/resources/views/admin/nodes/new.blade.php similarity index 99% rename from resources/themes/pterodactyl/admin/nodes/new.blade.php rename to resources/views/admin/nodes/new.blade.php index eff03326..1dcdca7c 100644 --- a/resources/themes/pterodactyl/admin/nodes/new.blade.php +++ b/resources/views/admin/nodes/new.blade.php @@ -108,7 +108,7 @@
    - +

    Enter the directory where server files should be stored. If you use OVH you should check your partition scheme. You may need to use /home/daemon-data to have enough space.

    diff --git a/resources/themes/pterodactyl/admin/nodes/view/allocation.blade.php b/resources/views/admin/nodes/view/allocation.blade.php similarity index 95% rename from resources/themes/pterodactyl/admin/nodes/view/allocation.blade.php rename to resources/views/admin/nodes/view/allocation.blade.php index bab9fa5d..83ac9c6c 100644 --- a/resources/themes/pterodactyl/admin/nodes/view/allocation.blade.php +++ b/resources/views/admin/nodes/view/allocation.blade.php @@ -52,10 +52,10 @@ @@ -83,8 +83,6 @@ @if(is_null($allocation->server_id)) - @else - @endif @@ -218,7 +216,7 @@ }, function () { $.ajax({ method: 'DELETE', - url: Router.route('admin.nodes.view.allocation.removeSingle', { node: Pterodactyl.node.id, allocation: allocation }), + url: '/admin/nodes/view/' + {{ $node->id }} + '/allocation/remove/' + allocation, headers: { 'X-CSRF-TOKEN': $('meta[name="_token"]').attr('content') }, }).done(function (data) { element.parent().parent().addClass('warning').delay(100).fadeOut(); @@ -247,7 +245,7 @@ clearTimeout(fadeTimers[element.data('id')]); $.ajax({ method: 'POST', - url: Router.route('admin.nodes.view.allocation.setAlias', { id: Pterodactyl.node.id }), + url: '/admin/nodes/view/' + {{ $node->id }} + '/allocation/alias', headers: { 'X-CSRF-TOKEN': $('meta[name="_token"]').attr('content') }, data: { alias: element.val(), @@ -321,9 +319,7 @@ }, function () { $.ajax({ method: 'DELETE', - url: Router.route('admin.nodes.view.allocation.removeMultiple', { - node: Pterodactyl.node.id - }), + url: '/admin/nodes/view/' + {{ $node->id }} + '/allocations', headers: {'X-CSRF-TOKEN': $('meta[name="_token"]').attr('content')}, data: JSON.stringify({ allocations: selectedIds diff --git a/resources/themes/pterodactyl/admin/nodes/view/configuration.blade.php b/resources/views/admin/nodes/view/configuration.blade.php similarity index 78% rename from resources/themes/pterodactyl/admin/nodes/view/configuration.blade.php rename to resources/views/admin/nodes/view/configuration.blade.php index 3fd6b3eb..9c7e987b 100644 --- a/resources/themes/pterodactyl/admin/nodes/view/configuration.blade.php +++ b/resources/views/admin/nodes/view/configuration.blade.php @@ -40,10 +40,10 @@

    Configuration File

    -
    {{ $node->getConfigurationAsJson(true) }}
    +
    {{ $node->getYamlConfiguration() }}
    @@ -53,7 +53,10 @@

    Auto-Deploy

    -

    To simplify the configuration of nodes it is possible to fetch the config from the panel. A token is required for this process. The button below will generate a token and provide you with the commands necessary for automatic configuration of the node. Tokens are only valid for 5 minutes.

    +

    + Use the button below to generate a custom deployment command that can be used to configure + wings on the target server with a single command. +