Compare commits
70 commits
matthewpi/
...
develop
Author | SHA1 | Date | |
---|---|---|---|
61faabcbfd | |||
|
a9bdf7a1ef | ||
|
7fa0c26d80 | ||
|
804a08bd2e | ||
|
8ffe0f1ff0 | ||
|
9b35a55eea | ||
|
35ded9def8 | ||
|
3721b2007b | ||
|
6f5fb09c13 | ||
|
5a417e9adb | ||
|
51cee7688a | ||
|
67b2d944a6 | ||
|
57d27293d2 | ||
|
1af200c464 | ||
|
97049f48c3 | ||
|
2d4071ca25 | ||
|
5cd2697be3 | ||
|
85f1259709 | ||
|
bf1768406b | ||
|
a83058668f | ||
|
987440c8ca | ||
|
341fa0a52d | ||
|
aa2f797f6f | ||
|
04d83edd36 | ||
|
15860613b6 | ||
|
3cd15d6f21 | ||
|
d3c6568522 | ||
|
29783ed240 | ||
|
b7b128efc5 | ||
|
7c8bdfc4d8 | ||
|
eaf46429f2 | ||
|
bad765039b | ||
|
b23f3114e4 | ||
|
dc6fadd1c5 | ||
|
8bfcffc477 | ||
|
f5ce1a240c | ||
|
8320c64918 | ||
|
0d225b0e8f | ||
|
8ae40a7c71 | ||
|
7241829da6 | ||
|
05d8de0671 | ||
|
f98aa11a3a | ||
|
ab4b4e6ada | ||
|
673f7282e0 | ||
|
2195aa6447 | ||
|
187c7f74cd | ||
|
0af39492e3 | ||
|
733771ae75 | ||
|
85df1a4ec7 | ||
|
a1e5afccb4 | ||
|
18f6611a2d | ||
|
1d38b4f0e2 | ||
|
ad4ddc6300 | ||
|
b746c3ead1 | ||
|
aea5c474db | ||
|
582c50c6d4 | ||
|
2a7833ca17 | ||
|
9b47403e00 | ||
|
3bd8164415 | ||
|
43f7c10617 | ||
|
c5be3ad04b | ||
|
d8b7747828 | ||
|
7c67ff3711 | ||
|
d09e61b4f4 | ||
|
4b82ca1042 | ||
|
5063db7d95 | ||
|
866b6df4b0 | ||
|
2b14e46eec | ||
|
20f23a0b27 | ||
|
a27ea3d1bc |
107 changed files with 7962 additions and 7245 deletions
|
@ -22,7 +22,7 @@ REDIS_PASSWORD=null
|
|||
REDIS_PORT=6379
|
||||
|
||||
CACHE_DRIVER=file
|
||||
QUEUE_CONNECTION=sync
|
||||
QUEUE_CONNECTION=redis
|
||||
SESSION_DRIVER=file
|
||||
|
||||
HASHIDS_SALT=
|
||||
|
|
4
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
4
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
|
@ -68,8 +68,8 @@ body:
|
|||
Run the following command to collect logs on your system.
|
||||
|
||||
Wings: `sudo wings diagnostics`
|
||||
Panel: `tail -n 100 /var/www/pterodactyl/storage/logs/laravel-$(date +%F).log | nc bin.ptdl.co 99`
|
||||
placeholder: "https://bin.ptdl.co/a1h6z"
|
||||
Panel: `tail -n 150 /var/www/pterodactyl/storage/logs/laravel-$(date +%F).log | nc pteropaste.com 99`
|
||||
placeholder: "https://pteropaste.com/a1h6z"
|
||||
render: bash
|
||||
validations:
|
||||
required: false
|
||||
|
|
16
.github/workflows/docker.yaml
vendored
16
.github/workflows/docker.yaml
vendored
|
@ -16,14 +16,13 @@ on:
|
|||
jobs:
|
||||
push:
|
||||
name: Push
|
||||
runs-on: ubuntu-20.04
|
||||
if: "!contains(github.ref, 'develop') || (!contains(github.event.head_commit.message, 'skip docker') && !contains(github.event.head_commit.message, 'docker skip'))"
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Code checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Docker metadata
|
||||
id: docker_meta
|
||||
- name: Fetch metadata
|
||||
id: metadata
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: ghcr.io/pterodactyl/panel
|
||||
|
@ -42,6 +41,7 @@ jobs:
|
|||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v2
|
||||
if: "github.event_name != 'pull_request'"
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
|
@ -55,13 +55,13 @@ jobs:
|
|||
sed -i "s/ 'version' => 'canary',/ 'version' => '${REF:1}',/" config/app.php
|
||||
|
||||
- name: Build and Push
|
||||
uses: docker/build-push-action@v3
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
file: ./Containerfile
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
platforms: linux/amd64 #,linux/arm64
|
||||
labels: ${{ steps.docker_meta.outputs.labels }}
|
||||
tags: ${{ steps.docker_meta.outputs.tags }}
|
||||
platforms: linux/amd64,linux/arm64
|
||||
labels: ${{ steps.metadata.outputs.labels }}
|
||||
tags: ${{ steps.metadata.outputs.tags }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
|
13
.github/workflows/laravel.yaml
vendored
13
.github/workflows/laravel.yaml
vendored
|
@ -13,7 +13,7 @@ on:
|
|||
jobs:
|
||||
analysis:
|
||||
name: Static Analysis
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-22.04
|
||||
env:
|
||||
APP_ENV: testing
|
||||
APP_DEBUG: "true"
|
||||
|
@ -42,7 +42,7 @@ jobs:
|
|||
|
||||
lint:
|
||||
name: Lint
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Code checkout
|
||||
uses: actions/checkout@v3
|
||||
|
@ -50,6 +50,7 @@ jobs:
|
|||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
# TODO: Update to 8.2 once php-cs-fixer supports it
|
||||
php-version: 8.1
|
||||
extensions: bcmath, curl, gd, mbstring, mysql, openssl, pdo, tokenizer, xml, zip
|
||||
tools: composer:v2
|
||||
|
@ -63,11 +64,11 @@ jobs:
|
|||
|
||||
mysql:
|
||||
name: Tests
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-22.04
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
php: [8.1]
|
||||
php: [8.1, 8.2]
|
||||
database: ["mariadb:10.2", "mariadb:10.9", "mysql:8"]
|
||||
services:
|
||||
database:
|
||||
|
@ -135,12 +136,12 @@ jobs:
|
|||
|
||||
postgres:
|
||||
name: Tests
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-22.04
|
||||
if: "!contains(github.event.head_commit.message, 'skip ci') && !contains(github.event.head_commit.message, 'ci skip')"
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
php: [8.1]
|
||||
php: [8.1, 8.2]
|
||||
database: ["postgres:13", "postgres:14", "postgres:15"]
|
||||
services:
|
||||
database:
|
||||
|
|
11
.github/workflows/release.yaml
vendored
11
.github/workflows/release.yaml
vendored
|
@ -8,22 +8,25 @@ on:
|
|||
jobs:
|
||||
release:
|
||||
name: Release
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Code checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
cache: yarn
|
||||
cache: pnpm
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn install --frozen-lockfile
|
||||
run: pnpm install
|
||||
|
||||
- name: Build
|
||||
run: yarn build
|
||||
run: pnpm run build
|
||||
|
||||
- name: Create release branch and bump version
|
||||
env:
|
||||
|
|
24
.github/workflows/ui.yaml
vendored
24
.github/workflows/ui.yaml
vendored
|
@ -13,26 +13,29 @@ on:
|
|||
jobs:
|
||||
lint:
|
||||
name: Lint
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Code checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
cache: yarn
|
||||
cache: pnpm
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn install --frozen-lockfile
|
||||
run: pnpm install
|
||||
|
||||
- name: Lint
|
||||
run: yarn run lint
|
||||
run: pnpm run lint
|
||||
|
||||
tests:
|
||||
name: Tests
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-22.04
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
|
@ -41,17 +44,20 @@ jobs:
|
|||
- name: Code checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
cache: yarn
|
||||
cache: pnpm
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn install --frozen-lockfile
|
||||
run: pnpm install
|
||||
|
||||
- name: Build
|
||||
run: yarn run build
|
||||
run: pnpm run build
|
||||
|
||||
- name: Tests
|
||||
run: yarn run test
|
||||
run: pnpm run test
|
||||
|
|
1
.npmrc
Normal file
1
.npmrc
Normal file
|
@ -0,0 +1 @@
|
|||
shamefully-hoist=true
|
63
BUILDING.md
63
BUILDING.md
|
@ -1,63 +0,0 @@
|
|||
# Local Development
|
||||
Pterodactyl is now powered by React, Typescript, and Tailwindcss using webpack at its core to generate compiled assets.
|
||||
Release versions of Pterodactyl will include pre-compiled, minified, and hashed assets ready-to-go.
|
||||
|
||||
However, if you are interested in running custom themes or making modifications to the React files you'll need a build
|
||||
system in place to generate these compiled assets. To get your environment setup you'll need at minimum:
|
||||
|
||||
* [Node.js](https://nodejs.org/en/) v14.x.x
|
||||
* [Yarn](https://classic.yarnpkg.com/lang/en/) v1.x.x
|
||||
* [Go](https://golang.org/) 1.17.x
|
||||
|
||||
### Install Dependencies
|
||||
```bash
|
||||
yarn install
|
||||
```
|
||||
|
||||
The command above will download all of the dependencies necessary to get Pterodactyl assets building. After that, its as
|
||||
simple as running the command below to generate assets while you're developing. Until you've run this command at least
|
||||
once you'll likely see a 500 error on your Panel about a missing `manifest.json` file. This is generated by the commands
|
||||
below.
|
||||
|
||||
```bash
|
||||
# Build the compiled set of assets for development.
|
||||
yarn run build
|
||||
|
||||
# Build the assets automatically as they are changed. This allows you to refresh
|
||||
# the page and see the changes immediately.
|
||||
yarn run watch
|
||||
```
|
||||
|
||||
### Hot Module Reloading
|
||||
For more advanced users, we also support 'Hot Module Reloading', allowing you to quickly see changes you're making
|
||||
to the Vue template files without having to reload the page you're on. To Get started with this, you just need
|
||||
to run the command below.
|
||||
|
||||
```bash
|
||||
PUBLIC_PATH=http://192.168.1.1:8080 yarn run serve --host 192.168.1.1
|
||||
```
|
||||
|
||||
There are two _very important_ parts of this command to take note of and change for your specific environment. The first
|
||||
is the `--host` flag, which is required and should point to the machine where the `webpack-serve` server will be running.
|
||||
The second is the `PUBLIC_PATH` environment variable which is the URL pointing to the HMR server and is appended to all of
|
||||
the asset URLs used in Pterodactyl.
|
||||
|
||||
#### Development Environment
|
||||
If you're using the [`pterodactyl/development`](https://github.com/pterodactyl/development) environments, which are
|
||||
highly recommended, you can just run `yarn run serve` to run the HMR server, no additional configuration is necessary.
|
||||
|
||||
### Building for Production
|
||||
Once you have your files squared away and ready for the live server, you'll be needing to generate compiled, minified,
|
||||
and hashed assets to push live. To do so, run the command below:
|
||||
|
||||
```bash
|
||||
yarn run build:production
|
||||
```
|
||||
|
||||
This will generate a production JS bundle and associated assets, all located in `public/assets/` which will need to
|
||||
be uploaded to your server or CDN for clients to use.
|
||||
|
||||
### Running Wings
|
||||
To run `wings` in development all you need to do is set up the configuration file as normal when adding a new node, and
|
||||
then you can build and run a local version of Wings by executing `make debug` in the Wings code directory. This must
|
||||
be run on a Linux VM of some sort, you cannot run this locally on macOS or Windows.
|
11
CHANGELOG.md
11
CHANGELOG.md
|
@ -3,6 +3,17 @@ This file is a running track of new features and fixes to each version of the pa
|
|||
|
||||
This project follows [Semantic Versioning](http://semver.org) guidelines.
|
||||
|
||||
## v1.11.3
|
||||
### Changed
|
||||
* When updating a server's description through the client API, if no value is specified, the description will now remain unchanged.
|
||||
* When installing the Panel for the first time, the queue driver will now all default to `redis` instead of `sync`.
|
||||
|
||||
### Fixed
|
||||
* `php artisan p:environment:mail` not correctly setting the right variable for `MAIL_FROM_ADDRESS`.
|
||||
* Fixed the conflict state rendering on the UI for a server showing `reinstall_failed` as `restoring_backup`.
|
||||
* Fixed the unknown column `uuid` error when jobs fail, causing them not to get stored correctly.
|
||||
* Fixed the server task endpoints in the client API not allowing `sequence_id` and `continue_on_failure` to be set.
|
||||
|
||||
## v1.11.2
|
||||
### Changed
|
||||
* Telemetry no longer sends a map of Egg and Nest UUIDs to the number of servers using them.
|
||||
|
|
|
@ -1,20 +1,20 @@
|
|||
# Stage 0 - Caddy
|
||||
FROM --platform=$TARGETOS/$TARGETARCH docker.io/library/caddy:latest AS caddy
|
||||
|
||||
# Stage 1 - Builder
|
||||
FROM --platform=$TARGETOS/$TARGETARCH registry.access.redhat.com/ubi9/nodejs-16-minimal AS builder
|
||||
FROM --platform=$TARGETOS/$TARGETARCH registry.access.redhat.com/ubi9/nodejs-18-minimal AS builder
|
||||
|
||||
RUN npm install -g yarn
|
||||
USER 0
|
||||
RUN npm install -g pnpm
|
||||
|
||||
WORKDIR /var/www/pterodactyl
|
||||
|
||||
COPY --chown=1001:0 public ./public
|
||||
COPY --chown=1001:0 resources/scripts ./resources/scripts
|
||||
COPY --chown=1001:0 .eslintignore .eslintrc.js .prettierrc.json package.json tailwind.config.js tsconfig.json vite.config.ts yarn.lock .
|
||||
COPY --chown=1001:0 .eslintignore .eslintrc.js .npmrc .prettierrc.json package.json pnpm-lock.yaml tailwind.config.js tsconfig.json vite.config.ts .
|
||||
|
||||
RUN /opt/app-root/src/.npm-global/bin/yarn install --frozen-lockfile \
|
||||
&& /opt/app-root/src/.npm-global/bin/yarn build \
|
||||
&& rm -rf resources/scripts .eslintignore .eslintrc.yml .yarnrc.yml package.json tailwind.config.js tsconfig.json vite.config.ts yarn.lock node_modules
|
||||
RUN /opt/app-root/src/.npm-global/bin/pnpm install \
|
||||
&& /opt/app-root/src/.npm-global/bin/pnpm build \
|
||||
&& rm -rf resources/scripts .eslintignore .eslintrc.yml .npmrc package.json pnpm-lock.yaml tailwind.config.js tsconfig.json vite.config.ts node_modules
|
||||
|
||||
USER 1001
|
||||
|
||||
COPY --chown=1001:0 app ./app
|
||||
COPY --chown=1001:0 bootstrap ./bootstrap
|
||||
|
@ -34,10 +34,11 @@ RUN microdnf update -y \
|
|||
&& rpm --install https://rpms.remirepo.net/enterprise/remi-release-9.rpm \
|
||||
&& microdnf update -y \
|
||||
&& microdnf install -y ca-certificates shadow-utils tar tzdata unzip wget \
|
||||
# ref; https://bugzilla.redhat.com/show_bug.cgi?id=1870814
|
||||
&& microdnf reinstall -y tzdata \
|
||||
&& microdnf module -y reset php \
|
||||
&& microdnf module -y enable php:remi-8.1 \
|
||||
&& microdnf install -y cronie php-{bcmath,cli,common,fpm,gd,gmp,intl,json,mbstring,mysqlnd,opcache,pdo,pecl-redis5,pecl-zip,phpiredis,pgsql,process,sodium,xml,zstd} supervisor \
|
||||
&& curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer \
|
||||
&& microdnf module -y enable php:remi-8.2 \
|
||||
&& microdnf install -y composer cronie php-{bcmath,cli,common,fpm,gd,gmp,intl,json,mbstring,mysqlnd,opcache,pdo,pecl-redis5,pecl-zip,phpiredis,pgsql,process,sodium,xml,zstd} supervisor \
|
||||
&& rm /etc/php-fpm.d/www.conf \
|
||||
&& useradd --home-dir /var/lib/caddy --create-home caddy \
|
||||
&& mkdir /etc/caddy \
|
||||
|
@ -66,7 +67,7 @@ RUN composer install --no-dev --optimize-autoloader \
|
|||
&& rm -rf bootstrap/cache/*.php \
|
||||
&& rm -rf .env storage/logs/*.log
|
||||
|
||||
COPY --from=caddy /usr/bin/caddy /usr/local/bin/caddy
|
||||
COPY --from=docker.io/library/caddy:latest /usr/bin/caddy /usr/local/bin/caddy
|
||||
COPY .github/docker/Caddyfile /etc/caddy/Caddyfile
|
||||
COPY .github/docker/php-fpm.conf /etc/php-fpm.conf
|
||||
COPY .github/docker/supervisord.conf /etc/supervisord.conf
|
||||
|
|
9
LICENSE
Normal file
9
LICENSE
Normal file
|
@ -0,0 +1,9 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2024 Skynet
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
13
README.md
13
README.md
|
@ -24,21 +24,20 @@ Stop settling for less. Make game servers a first class citizen on your platform
|
|||
|
||||
## Sponsors
|
||||
|
||||
I would like to extend my sincere thanks to the following sponsors for helping fund Pterodactyl's developement.
|
||||
I would like to extend my sincere thanks to the following sponsors for helping fund Pterodactyl's development.
|
||||
[Interested in becoming a sponsor?](https://github.com/sponsors/matthewpi)
|
||||
|
||||
| Company | About |
|
||||
|-----------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
|-----------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| [**WISP**](https://wisp.gg) | Extra features. |
|
||||
| [**RocketNode**](https://rocketnode.com/) | Innovative game server hosting combined with a straightforward control panel, affordable prices, and Rocket-Fast support. |
|
||||
| [**Aussie Server Hosts**](https://aussieserverhosts.com/) | No frills Australian Owned and operated High Performance Server hosting for some of the most demanding games serving Australia and New Zealand. |
|
||||
| [**WemX**](https://wemx.net/) | WemX helps automate your hosting company or SaaS business by automating billing, user management, authentication, and much more. |
|
||||
| [**BisectHosting**](https://www.bisecthosting.com/) | BisectHosting provides Minecraft, Valheim and other server hosting services with the highest reliability and lightning fast support since 2012. |
|
||||
| [**MineStrator**](https://minestrator.com/) | Looking for the most highend French hosting company for your minecraft server? More than 24,000 members on our discord trust us. Give us a try! |
|
||||
| [**Skynode**](https://www.skynode.pro/) | Skynode provides blazing fast game servers along with a top-notch user experience. Whatever our clients are looking for, we're able to provide it! |
|
||||
| [**VibeGAMES**](https://vibegames.net/) | VibeGAMES is a game server provider that specializes in DDOS protection for the games we offer. We have multiple locations in the US, Brazil, France, Germany, Singapore, Australia and South Africa. |
|
||||
| [**Pterodactyl Market**](https://pterodactylmarket.com/) | Pterodactyl Market is a one-and-stop shop for Pterodactyl. In our market, you can find Add-ons, Themes, Eggs, and more for Pterodactyl. |
|
||||
| [**UltraServers**](https://ultraservers.com/) | Deploy premium games hosting with the click of a button. Manage and swap games with ease and let us take care of the rest. We currently support Minecraft, Rust, ARK, 7 Days to Die, Garys MOD, CS:GO, Satisfactory and others. |
|
||||
| [**Realms Hosting**](https://realmshosting.com/) | Want to build your Gaming Empire? Use Realms Hosting today to kick start your game server hosting with outstanding DDOS Protection, 24/7 Support, Cheap Prices and a Custom Control Panel. | |
|
||||
| [**DutchIS**](https://dutchis.net?ref=pterodactyl) | DutchIS provides instant infrastructure such as pay per use VPS hosting. Start your game hosting journey on DutchIS. |
|
||||
| [**Skoali**](https://skoali.com/) | Skoali is a French company that hosts game servers and other types of services (VPS, WEB, Dedicated servers, ...). We also have a free plan for Minecraft and Garry's Mod. |
|
||||
| [**Rabbit Computing**](https://www.rabbitcomputing.com/link.php?id=5) | Rabbit Computing offers powerful VPS servers, highly available game hosting, and fully unlimited web hosting. Use code README for 20% off your first three months! |
|
||||
|
||||
### Supported Games
|
||||
|
||||
|
|
|
@ -44,7 +44,7 @@ class EmailSettingsCommand extends Command
|
|||
trans('command/messages.environment.mail.ask_driver'),
|
||||
[
|
||||
'smtp' => 'SMTP Server',
|
||||
'mail' => 'PHP\'s Internal Mail Function',
|
||||
'sendmail' => 'sendmail Binary',
|
||||
'mailgun' => 'Mailgun Transactional Email',
|
||||
'mandrill' => 'Mandrill Transactional Email',
|
||||
'postmark' => 'Postmark Transactional Email',
|
||||
|
|
|
@ -10,7 +10,7 @@ class PruneOrphanedBackupsCommand extends Command
|
|||
{
|
||||
protected $signature = 'p:maintenance:prune-backups {--prune-age=}';
|
||||
|
||||
protected $description = 'Marks all backups that have not completed in the last "n" minutes as being failed.';
|
||||
protected $description = 'Marks all backups older than "n" minutes that have not yet completed as being failed.';
|
||||
|
||||
/**
|
||||
* PruneOrphanedBackupsCommand constructor.
|
||||
|
@ -38,7 +38,7 @@ class PruneOrphanedBackupsCommand extends Command
|
|||
return;
|
||||
}
|
||||
|
||||
$this->warn("Marking $count backups that have not been marked as completed in the last $since minutes as failed.");
|
||||
$this->warn("Marking $count uncompleted backups that are older than $since minutes as failed.");
|
||||
|
||||
$query->update([
|
||||
'is_successful' => false,
|
||||
|
|
|
@ -18,7 +18,7 @@ class Kernel extends ConsoleKernel
|
|||
/**
|
||||
* Register the commands for the application.
|
||||
*/
|
||||
protected function commands()
|
||||
protected function commands(): void
|
||||
{
|
||||
$this->load(__DIR__ . '/Commands');
|
||||
}
|
||||
|
@ -26,8 +26,11 @@ class Kernel extends ConsoleKernel
|
|||
/**
|
||||
* Define the application's command schedule.
|
||||
*/
|
||||
protected function schedule(Schedule $schedule)
|
||||
protected function schedule(Schedule $schedule): void
|
||||
{
|
||||
// https://laravel.com/docs/10.x/upgrade#redis-cache-tags
|
||||
$schedule->command('cache:prune-stale-tags')->hourly();
|
||||
|
||||
// Execute scheduled commands for servers every minute, as if there was a normal cron running.
|
||||
$schedule->command(ProcessRunnableCommand::class)->everyMinute()->withoutOverlapping();
|
||||
$schedule->command(CleanServiceBackupFilesCommand::class)->daily();
|
||||
|
|
|
@ -73,7 +73,7 @@ final class Handler extends ExceptionHandler
|
|||
*
|
||||
* @noinspection PhpUnusedLocalVariableInspection
|
||||
*/
|
||||
public function register()
|
||||
public function register(): void
|
||||
{
|
||||
if (config('app.exceptions.report_all', false)) {
|
||||
$this->dontReport = [];
|
||||
|
|
|
@ -15,8 +15,6 @@ final class Time
|
|||
*/
|
||||
public static function getMySQLTimezoneOffset(string $timezone): string
|
||||
{
|
||||
$offset = round(CarbonImmutable::now($timezone)->getTimezone()->getOffset(CarbonImmutable::now('UTC')) / 3600);
|
||||
|
||||
return sprintf('%s%s:00', $offset > 0 ? '+' : '-', str_pad((string) abs($offset), 2, '0', STR_PAD_LEFT));
|
||||
return CarbonImmutable::now($timezone)->getTimezone()->toOffsetName();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,8 +27,6 @@ class EggController extends ApplicationApiController
|
|||
public function __construct(private EggExporterService $eggExporterService)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->eggExporterService = $eggExporterService;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -18,6 +18,7 @@ use Pterodactyl\Transformers\Api\Client\BackupTransformer;
|
|||
use Pterodactyl\Http\Controllers\Api\Client\ClientApiController;
|
||||
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
||||
use Pterodactyl\Http\Requests\Api\Client\Servers\Backups\StoreBackupRequest;
|
||||
use Pterodactyl\Http\Requests\Api\Client\Servers\Backups\RestoreBackupRequest;
|
||||
|
||||
class BackupController extends ClientApiController
|
||||
{
|
||||
|
@ -188,12 +189,8 @@ class BackupController extends ClientApiController
|
|||
*
|
||||
* @throws \Throwable
|
||||
*/
|
||||
public function restore(Request $request, Server $server, Backup $backup): JsonResponse
|
||||
public function restore(RestoreBackupRequest $request, Server $server, Backup $backup): JsonResponse
|
||||
{
|
||||
if (!$request->user()->can(Permission::ACTION_BACKUP_RESTORE, $server)) {
|
||||
throw new AuthorizationException();
|
||||
}
|
||||
|
||||
// Cannot restore a backup unless a server is fully installed and not currently
|
||||
// processing a different backup restoration request.
|
||||
if (!is_null($server->status)) {
|
||||
|
|
|
@ -9,6 +9,7 @@ use Pterodactyl\Models\Schedule;
|
|||
use Illuminate\Http\JsonResponse;
|
||||
use Pterodactyl\Facades\Activity;
|
||||
use Pterodactyl\Models\Permission;
|
||||
use Illuminate\Database\ConnectionInterface;
|
||||
use Pterodactyl\Repositories\Eloquent\TaskRepository;
|
||||
use Pterodactyl\Exceptions\Http\HttpForbiddenException;
|
||||
use Pterodactyl\Transformers\Api\Client\TaskTransformer;
|
||||
|
@ -23,8 +24,10 @@ class ScheduleTaskController extends ClientApiController
|
|||
/**
|
||||
* ScheduleTaskController constructor.
|
||||
*/
|
||||
public function __construct(private TaskRepository $repository)
|
||||
{
|
||||
public function __construct(
|
||||
private ConnectionInterface $connection,
|
||||
private TaskRepository $repository
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
|
@ -49,14 +52,35 @@ class ScheduleTaskController extends ClientApiController
|
|||
$lastTask = $schedule->tasks()->orderByDesc('sequence_id')->first();
|
||||
|
||||
/** @var \Pterodactyl\Models\Task $task */
|
||||
$task = $this->repository->create([
|
||||
$task = $this->connection->transaction(function () use ($request, $schedule, $lastTask) {
|
||||
$sequenceId = ($lastTask->sequence_id ?? 0) + 1;
|
||||
$requestSequenceId = $request->integer('sequence_id', $sequenceId);
|
||||
|
||||
// Ensure that the sequence id is at least 1.
|
||||
if ($requestSequenceId < 1) {
|
||||
$requestSequenceId = 1;
|
||||
}
|
||||
|
||||
// If the sequence id from the request is greater than or equal to the next available
|
||||
// sequence id, we don't need to do anything special. Otherwise, we need to update
|
||||
// the sequence id of all tasks that are greater than or equal to the request sequence
|
||||
// id to be one greater than the current value.
|
||||
if ($requestSequenceId < $sequenceId) {
|
||||
$schedule->tasks()
|
||||
->where('sequence_id', '>=', $requestSequenceId)
|
||||
->increment('sequence_id');
|
||||
$sequenceId = $requestSequenceId;
|
||||
}
|
||||
|
||||
return $this->repository->create([
|
||||
'schedule_id' => $schedule->id,
|
||||
'sequence_id' => ($lastTask->sequence_id ?? 0) + 1,
|
||||
'sequence_id' => $sequenceId,
|
||||
'action' => $request->input('action'),
|
||||
'payload' => $request->input('payload') ?? '',
|
||||
'time_offset' => $request->input('time_offset'),
|
||||
'continue_on_failure' => (bool) $request->input('continue_on_failure'),
|
||||
'continue_on_failure' => $request->boolean('continue_on_failure'),
|
||||
]);
|
||||
});
|
||||
|
||||
Activity::event('server:task.create')
|
||||
->subject($schedule, $task)
|
||||
|
@ -84,12 +108,34 @@ class ScheduleTaskController extends ClientApiController
|
|||
throw new HttpForbiddenException("A backup task cannot be created when the server's backup limit is set to 0.");
|
||||
}
|
||||
|
||||
$this->connection->transaction(function () use ($request, $schedule, $task) {
|
||||
$sequenceId = $request->integer('sequence_id', $task->sequence_id);
|
||||
// Ensure that the sequence id is at least 1.
|
||||
if ($sequenceId < 1) {
|
||||
$sequenceId = 1;
|
||||
}
|
||||
|
||||
// Shift all other tasks in the schedule up or down to make room for the new task.
|
||||
if ($sequenceId < $task->sequence_id) {
|
||||
$schedule->tasks()
|
||||
->where('sequence_id', '>=', $sequenceId)
|
||||
->where('sequence_id', '<', $task->sequence_id)
|
||||
->increment('sequence_id');
|
||||
} elseif ($sequenceId > $task->sequence_id) {
|
||||
$schedule->tasks()
|
||||
->where('sequence_id', '>', $task->sequence_id)
|
||||
->where('sequence_id', '<=', $sequenceId)
|
||||
->decrement('sequence_id');
|
||||
}
|
||||
|
||||
$this->repository->update($task->id, [
|
||||
'sequence_id' => $sequenceId,
|
||||
'action' => $request->input('action'),
|
||||
'payload' => $request->input('payload') ?? '',
|
||||
'time_offset' => $request->input('time_offset'),
|
||||
'continue_on_failure' => (bool) $request->input('continue_on_failure'),
|
||||
'continue_on_failure' => $request->boolean('continue_on_failure'),
|
||||
]);
|
||||
});
|
||||
|
||||
Activity::event('server:task.update')
|
||||
->subject($schedule, $task)
|
||||
|
@ -117,10 +163,9 @@ class ScheduleTaskController extends ClientApiController
|
|||
throw new HttpForbiddenException('You do not have permission to perform this action.');
|
||||
}
|
||||
|
||||
$schedule->tasks()->where('sequence_id', '>', $task->sequence_id)->update([
|
||||
'sequence_id' => $schedule->tasks()->getConnection()->raw('(sequence_id - 1)'),
|
||||
]);
|
||||
|
||||
$schedule->tasks()
|
||||
->where('sequence_id', '>', $task->sequence_id)
|
||||
->decrement('sequence_id');
|
||||
$task->delete();
|
||||
|
||||
Activity::event('server:task.delete')->subject($schedule, $task)->property('name', $schedule->name)->log();
|
||||
|
|
|
@ -35,7 +35,7 @@ class SettingsController extends ClientApiController
|
|||
public function rename(RenameServerRequest $request, Server $server): JsonResponse
|
||||
{
|
||||
$name = $request->input('name');
|
||||
$description = $request->input('description') ?? $server->description;
|
||||
$description = $request->has('description') ? (string) $request->input('description') : $server->description;
|
||||
$this->repository->update($server->id, [
|
||||
'name' => $name,
|
||||
'description' => $description,
|
||||
|
|
|
@ -86,7 +86,7 @@ class Kernel extends HttpKernel
|
|||
/**
|
||||
* The application's route middleware.
|
||||
*/
|
||||
protected $routeMiddleware = [
|
||||
protected $middlewareAliases = [
|
||||
'auth' => Authenticate::class,
|
||||
'auth.basic' => AuthenticateWithBasicAuth::class,
|
||||
'auth.session' => AuthenticateSession::class,
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
namespace Pterodactyl\Http\Requests\Api\Client\Servers\Backups;
|
||||
|
||||
use Pterodactyl\Models\Permission;
|
||||
use Pterodactyl\Http\Requests\Api\Client\ClientApiRequest;
|
||||
|
||||
class RestoreBackupRequest extends ClientApiRequest
|
||||
{
|
||||
public function permission(): string
|
||||
{
|
||||
return Permission::ACTION_BACKUP_RESTORE;
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return ['truncate' => 'required|boolean'];
|
||||
}
|
||||
}
|
|
@ -23,6 +23,7 @@ class StoreTaskRequest extends ViewScheduleRequest
|
|||
'payload' => 'required_unless:action,backup|string|nullable',
|
||||
'time_offset' => 'required|numeric|min:0|max:900',
|
||||
'sequence_id' => 'sometimes|required|numeric|min:1',
|
||||
'continue_on_failure' => 'sometimes|required|boolean',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
|||
* @property array|null $allowed_ips
|
||||
* @property string|null $memo
|
||||
* @property \Illuminate\Support\Carbon|null $last_used_at
|
||||
* @property \Illuminate\Support\Carbon|null $expires_at
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @property int $r_servers
|
||||
|
@ -97,6 +98,10 @@ class ApiKey extends Model
|
|||
protected $casts = [
|
||||
'allowed_ips' => 'array',
|
||||
'user_id' => 'int',
|
||||
'last_used_at' => 'datetime',
|
||||
'expires_at' => 'datetime',
|
||||
self::CREATED_AT => 'datetime',
|
||||
self::UPDATED_AT => 'datetime',
|
||||
'r_' . AdminAcl::RESOURCE_USERS => 'int',
|
||||
'r_' . AdminAcl::RESOURCE_ALLOCATIONS => 'int',
|
||||
'r_' . AdminAcl::RESOURCE_DATABASE_HOSTS => 'int',
|
||||
|
@ -117,6 +122,7 @@ class ApiKey extends Model
|
|||
'allowed_ips',
|
||||
'memo',
|
||||
'last_used_at',
|
||||
'expires_at',
|
||||
];
|
||||
|
||||
/**
|
||||
|
@ -137,6 +143,7 @@ class ApiKey extends Model
|
|||
'allowed_ips' => 'nullable|array',
|
||||
'allowed_ips.*' => 'string',
|
||||
'last_used_at' => 'nullable|date',
|
||||
'expires_at' => 'nullable|date',
|
||||
'r_' . AdminAcl::RESOURCE_USERS => 'integer|min:0|max:3',
|
||||
'r_' . AdminAcl::RESOURCE_ALLOCATIONS => 'integer|min:0|max:3',
|
||||
'r_' . AdminAcl::RESOURCE_DATABASE_HOSTS => 'integer|min:0|max:3',
|
||||
|
@ -148,12 +155,6 @@ class ApiKey extends Model
|
|||
'r_' . AdminAcl::RESOURCE_SERVERS => 'integer|min:0|max:3',
|
||||
];
|
||||
|
||||
protected $dates = [
|
||||
self::CREATED_AT,
|
||||
self::UPDATED_AT,
|
||||
'last_used_at',
|
||||
];
|
||||
|
||||
/**
|
||||
* Returns the user this token is assigned to.
|
||||
*/
|
||||
|
|
|
@ -42,10 +42,7 @@ class Backup extends Model
|
|||
'is_locked' => 'bool',
|
||||
'ignored_files' => 'array',
|
||||
'bytes' => 'int',
|
||||
];
|
||||
|
||||
protected $dates = [
|
||||
'completed_at',
|
||||
'completed_at' => 'datetime',
|
||||
];
|
||||
|
||||
protected $attributes = [
|
||||
|
|
|
@ -78,6 +78,9 @@ class Egg extends Model
|
|||
* Fields that are not mass assignable.
|
||||
*/
|
||||
protected $fillable = [
|
||||
'nest_id',
|
||||
'author',
|
||||
'uuid',
|
||||
'name',
|
||||
'description',
|
||||
'features',
|
||||
|
|
|
@ -71,14 +71,8 @@ class Schedule extends Model
|
|||
'is_active' => 'boolean',
|
||||
'is_processing' => 'boolean',
|
||||
'only_when_online' => 'boolean',
|
||||
];
|
||||
|
||||
/**
|
||||
* Columns to mutate to a date.
|
||||
*/
|
||||
protected $dates = [
|
||||
'last_run_at',
|
||||
'next_run_at',
|
||||
'last_run_at' => 'datetime',
|
||||
'next_run_at' => 'datetime',
|
||||
];
|
||||
|
||||
protected $attributes = [
|
||||
|
|
|
@ -140,11 +140,6 @@ class Server extends Model
|
|||
*/
|
||||
protected $with = ['allocation'];
|
||||
|
||||
/**
|
||||
* The attributes that should be mutated to dates.
|
||||
*/
|
||||
protected $dates = [self::CREATED_AT, self::UPDATED_AT, 'deleted_at', 'installed_at'];
|
||||
|
||||
/**
|
||||
* Fields that are not mass assignable.
|
||||
*/
|
||||
|
@ -167,7 +162,7 @@ class Server extends Model
|
|||
'allocation_id' => 'required|bail|unique:servers|exists:allocations,id',
|
||||
'nest_id' => 'required|exists:nests,id',
|
||||
'egg_id' => 'required|exists:eggs,id',
|
||||
'startup' => 'required|string',
|
||||
'startup' => 'nullable|string',
|
||||
'skip_scripts' => 'sometimes|boolean',
|
||||
'image' => 'required|string|max:191',
|
||||
'database_limit' => 'present|nullable|integer|min:0',
|
||||
|
@ -194,6 +189,10 @@ class Server extends Model
|
|||
'database_limit' => 'integer',
|
||||
'allocation_limit' => 'integer',
|
||||
'backup_limit' => 'integer',
|
||||
self::CREATED_AT => 'datetime',
|
||||
self::UPDATED_AT => 'datetime',
|
||||
'deleted_at' => 'datetime',
|
||||
'installed_at' => 'datetime',
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
|
@ -23,10 +23,8 @@ class TaskLog extends Model
|
|||
'id' => 'integer',
|
||||
'task_id' => 'integer',
|
||||
'run_status' => 'integer',
|
||||
'run_time' => 'datetime',
|
||||
'created_at' => 'datetime',
|
||||
'updated_at' => 'datetime',
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be mutated to dates.
|
||||
*/
|
||||
protected $dates = ['run_time', 'created_at', 'updated_at'];
|
||||
}
|
||||
|
|
|
@ -138,10 +138,9 @@ class User extends Model implements
|
|||
'root_admin' => 'boolean',
|
||||
'use_totp' => 'boolean',
|
||||
'gravatar' => 'boolean',
|
||||
'totp_authenticated_at' => 'datetime',
|
||||
];
|
||||
|
||||
protected $dates = ['totp_authenticated_at'];
|
||||
|
||||
/**
|
||||
* The attributes excluded from the model's JSON form.
|
||||
*/
|
||||
|
|
|
@ -15,7 +15,7 @@ class AppServiceProvider extends ServiceProvider
|
|||
/**
|
||||
* Bootstrap any application services.
|
||||
*/
|
||||
public function boot()
|
||||
public function boot(): void
|
||||
{
|
||||
Schema::defaultStringLength(191);
|
||||
|
||||
|
@ -48,7 +48,7 @@ class AppServiceProvider extends ServiceProvider
|
|||
/**
|
||||
* Register application service providers.
|
||||
*/
|
||||
public function register()
|
||||
public function register(): void
|
||||
{
|
||||
// Only load the settings service provider if the environment
|
||||
// is configured to allow it.
|
||||
|
|
|
@ -17,14 +17,12 @@ class AuthServiceProvider extends ServiceProvider
|
|||
Server::class => ServerPolicy::class,
|
||||
];
|
||||
|
||||
public function boot()
|
||||
public function boot(): void
|
||||
{
|
||||
Sanctum::usePersonalAccessTokenModel(ApiKey::class);
|
||||
|
||||
$this->registerPolicies();
|
||||
}
|
||||
|
||||
public function register()
|
||||
public function register(): void
|
||||
{
|
||||
Sanctum::ignoreMigrations();
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ class BackupsServiceProvider extends ServiceProvider implements DeferrableProvid
|
|||
/**
|
||||
* Register the S3 backup disk.
|
||||
*/
|
||||
public function register()
|
||||
public function register(): void
|
||||
{
|
||||
$this->app->singleton(BackupManager::class, function ($app) {
|
||||
return new BackupManager($app);
|
||||
|
|
|
@ -9,7 +9,7 @@ class BladeServiceProvider extends ServiceProvider
|
|||
/**
|
||||
* Perform post-registration booting of services.
|
||||
*/
|
||||
public function boot()
|
||||
public function boot(): void
|
||||
{
|
||||
$this->app->make('blade.compiler')
|
||||
->directive('datetimeHuman', function ($expression) {
|
||||
|
|
|
@ -10,7 +10,7 @@ class BroadcastServiceProvider extends ServiceProvider
|
|||
/**
|
||||
* Bootstrap any application services.
|
||||
*/
|
||||
public function boot()
|
||||
public function boot(): void
|
||||
{
|
||||
Broadcast::routes();
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ class HashidsServiceProvider extends ServiceProvider
|
|||
/**
|
||||
* Register the ability to use Hashids.
|
||||
*/
|
||||
public function register()
|
||||
public function register(): void
|
||||
{
|
||||
$this->app->singleton(HashidsInterface::class, function () {
|
||||
return new Hashids(
|
||||
|
|
|
@ -43,7 +43,7 @@ class RepositoryServiceProvider extends ServiceProvider
|
|||
/**
|
||||
* Register all the repository bindings.
|
||||
*/
|
||||
public function register()
|
||||
public function register(): void
|
||||
{
|
||||
// Eloquent Repositories
|
||||
$this->app->bind(AllocationRepositoryInterface::class, AllocationRepository::class);
|
||||
|
|
|
@ -19,7 +19,7 @@ class RouteServiceProvider extends ServiceProvider
|
|||
/**
|
||||
* Define your route model bindings, pattern filters, etc.
|
||||
*/
|
||||
public function boot()
|
||||
public function boot(): void
|
||||
{
|
||||
$this->configureRateLimiting();
|
||||
|
||||
|
@ -68,7 +68,7 @@ class RouteServiceProvider extends ServiceProvider
|
|||
/**
|
||||
* Configure the rate limiters for the application.
|
||||
*/
|
||||
protected function configureRateLimiting()
|
||||
protected function configureRateLimiting(): void
|
||||
{
|
||||
// Authentication rate limiting. For login and checkpoint endpoints we'll apply
|
||||
// a limit of 10 requests per minute, for the forgot password endpoint apply a
|
||||
|
|
|
@ -57,7 +57,7 @@ class SettingsServiceProvider extends ServiceProvider
|
|||
/**
|
||||
* Boot the service provider.
|
||||
*/
|
||||
public function boot(ConfigRepository $config, Encrypter $encrypter, Log $log, SettingsRepositoryInterface $settings)
|
||||
public function boot(ConfigRepository $config, Encrypter $encrypter, Log $log, SettingsRepositoryInterface $settings): void
|
||||
{
|
||||
// Only set the email driver settings from the database if we
|
||||
// are configured using SMTP as the driver.
|
||||
|
|
|
@ -10,7 +10,7 @@ class ViewComposerServiceProvider extends ServiceProvider
|
|||
/**
|
||||
* Register bindings in the container.
|
||||
*/
|
||||
public function boot()
|
||||
public function boot(): void
|
||||
{
|
||||
$this->app->make('view')->composer('*', AssetComposer::class);
|
||||
}
|
||||
|
|
|
@ -38,7 +38,9 @@ class DeployServerDatabaseService
|
|||
throw new NoSuitableDatabaseHostException();
|
||||
}
|
||||
|
||||
$databaseHostId = $hosts->random()->id;
|
||||
/** @var \Pterodactyl\Models\DatabaseHost $databaseHost */
|
||||
$databaseHost = $hosts->random();
|
||||
$databaseHostId = $databaseHost->id;
|
||||
}
|
||||
|
||||
return $this->managementService->create($server, [
|
||||
|
|
|
@ -17,54 +17,54 @@
|
|||
}
|
||||
],
|
||||
"require": {
|
||||
"php": "^8.0.2 || ^8.1 || ^8.2",
|
||||
"php": "^8.1 || ^8.2",
|
||||
"ext-json": "*",
|
||||
"ext-mbstring": "*",
|
||||
"ext-pdo": "*",
|
||||
"ext-pdo_mysql": "*",
|
||||
"ext-posix": "*",
|
||||
"ext-zip": "*",
|
||||
"aws/aws-sdk-php": "~3.253",
|
||||
"doctrine/dbal": "~3.5",
|
||||
"guzzlehttp/guzzle": "~7.5",
|
||||
"hashids/hashids": "~4.1",
|
||||
"laracasts/utilities": "~3.2",
|
||||
"laravel/framework": "~9.43",
|
||||
"laravel/helpers": "~1.5",
|
||||
"laravel/sanctum": "~3.0",
|
||||
"laravel/tinker": "~2.7",
|
||||
"laravel/ui": "~4.1",
|
||||
"lcobucci/jwt": "~4.2",
|
||||
"league/flysystem-aws-s3-v3": "~3.10",
|
||||
"league/flysystem-memory": "~3.10",
|
||||
"aws/aws-sdk-php": "~3.260.1",
|
||||
"doctrine/dbal": "~3.6.0",
|
||||
"guzzlehttp/guzzle": "~7.5.0",
|
||||
"hashids/hashids": "~5.0.0",
|
||||
"laracasts/utilities": "~3.2.2",
|
||||
"laravel/framework": "~10.1.3",
|
||||
"laravel/helpers": "~1.6.0",
|
||||
"laravel/sanctum": "~3.2.1",
|
||||
"laravel/tinker": "~2.8.1",
|
||||
"laravel/ui": "~4.2.1",
|
||||
"lcobucci/jwt": "~4.3.0",
|
||||
"league/flysystem-aws-s3-v3": "~3.12.2",
|
||||
"league/flysystem-memory": "~3.10.3",
|
||||
"matriphe/iso-639": "~1.2",
|
||||
"phpseclib/phpseclib": "~3.0",
|
||||
"pragmarx/google2fa": "~8.0",
|
||||
"predis/predis": "~2.0",
|
||||
"psr/cache": "~3.0",
|
||||
"s1lentium/iptools": "~1.1",
|
||||
"spatie/laravel-fractal": "~6.0",
|
||||
"spatie/laravel-query-builder": "~5.1",
|
||||
"staudenmeir/belongs-to-through": "~2.12",
|
||||
"symfony/http-client": "~6.0",
|
||||
"symfony/mailgun-mailer": "~6.0",
|
||||
"symfony/postmark-mailer": "~6.0",
|
||||
"symfony/yaml": "~6.0",
|
||||
"webmozart/assert": "~1.11"
|
||||
"phpseclib/phpseclib": "~3.0.18",
|
||||
"pragmarx/google2fa": "~8.0.0",
|
||||
"predis/predis": "~2.1.1",
|
||||
"prologue/alerts": "~1.1.0",
|
||||
"psr/cache": "~3.0.0",
|
||||
"s1lentium/iptools": "~1.2.0",
|
||||
"spatie/laravel-fractal": "~6.0.3",
|
||||
"spatie/laravel-query-builder": "~5.1.2",
|
||||
"staudenmeir/belongs-to-through": "~2.13",
|
||||
"symfony/http-client": "~6.2.6",
|
||||
"symfony/mailgun-mailer": "~6.2.5",
|
||||
"symfony/postmark-mailer": "~6.2.5",
|
||||
"symfony/yaml": "~6.2.5",
|
||||
"webmozart/assert": "~1.11.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"barryvdh/laravel-ide-helper": "~2.12.3",
|
||||
"fakerphp/faker": "~1.20",
|
||||
"friendsofphp/php-cs-fixer": "~3.11",
|
||||
"itsgoingd/clockwork": "~5.1",
|
||||
"laravel/sail": "~1.16",
|
||||
"mockery/mockery": "~1.5",
|
||||
"nunomaduro/collision": "~6.3",
|
||||
"nunomaduro/larastan": "^2.0",
|
||||
"phpstan/phpstan": "~1.9",
|
||||
"php-mock/php-mock-phpunit": "~2.6",
|
||||
"phpunit/phpunit": "~9.5",
|
||||
"spatie/laravel-ignition": "~1.5"
|
||||
"barryvdh/laravel-ide-helper": "~2.13.0",
|
||||
"fakerphp/faker": "~1.21.0",
|
||||
"friendsofphp/php-cs-fixer": "~3.14.4",
|
||||
"itsgoingd/clockwork": "~5.1.12",
|
||||
"laravel/sail": "~1.21.0",
|
||||
"mockery/mockery": "~1.5.1",
|
||||
"nunomaduro/collision": "~7.0.5",
|
||||
"nunomaduro/larastan": "~2.4.1",
|
||||
"phpstan/phpstan": "~1.10.1",
|
||||
"phpunit/phpunit": "~10.0.11",
|
||||
"spatie/laravel-ignition": "~2.0.0"
|
||||
},
|
||||
"autoload": {
|
||||
"files": [
|
||||
|
@ -84,24 +84,25 @@
|
|||
"scripts": {
|
||||
"cs:fix": "php-cs-fixer fix",
|
||||
"cs:check": "php-cs-fixer fix --dry-run --diff --verbose",
|
||||
"post-autoload-dump": [
|
||||
"Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
|
||||
"@php artisan package:discover --ansi || true"
|
||||
],
|
||||
"post-root-package-install": [
|
||||
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
|
||||
],
|
||||
"post-create-project-cmd": [
|
||||
"@php artisan key:generate"
|
||||
],
|
||||
"post-autoload-dump": [
|
||||
"Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
|
||||
"@php artisan package:discover || true"
|
||||
"@php artisan key:generate --ansi"
|
||||
]
|
||||
},
|
||||
"prefer-stable": true,
|
||||
"config": {
|
||||
"optimize-autoloader": true,
|
||||
"preferred-install": "dist",
|
||||
"sort-packages": true,
|
||||
"platform": {
|
||||
"php": "8.0.2"
|
||||
}
|
||||
"php": "8.1.0"
|
||||
}
|
||||
},
|
||||
"minimum-stability": "stable",
|
||||
"prefer-stable": true
|
||||
}
|
||||
|
|
2734
composer.lock
generated
2734
composer.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -27,7 +27,7 @@ return [
|
|||
| sending an e-mail. You will specify which one you are using for your
|
||||
| mailers below. You are free to add additional mailers as required.
|
||||
|
|
||||
| Supported: "smtp", "sendmail", "mailgun", "ses",
|
||||
| Supported: "smtp", "sendmail", "mailgun", "ses", "ses-v2",
|
||||
| "postmark", "log", "array", "failover"
|
||||
|
|
||||
*/
|
||||
|
|
|
@ -34,7 +34,7 @@ class EggVariableFactory extends Factory
|
|||
/**
|
||||
* Indicate that the egg variable is viewable.
|
||||
*/
|
||||
public function viewable(): Factory
|
||||
public function viewable(): static
|
||||
{
|
||||
return $this->state(function (array $attributes) {
|
||||
return [
|
||||
|
@ -46,7 +46,7 @@ class EggVariableFactory extends Factory
|
|||
/**
|
||||
* Indicate that the egg variable is editable.
|
||||
*/
|
||||
public function editable(): Factory
|
||||
public function editable(): static
|
||||
{
|
||||
return $this->state(function (array $attributes) {
|
||||
return [
|
||||
|
|
|
@ -41,7 +41,7 @@ class UserFactory extends Factory
|
|||
/**
|
||||
* Indicate that the user is an admin.
|
||||
*/
|
||||
public function admin(): Factory
|
||||
public function admin(): static
|
||||
{
|
||||
return $this->state(['root_admin' => true]);
|
||||
}
|
||||
|
|
|
@ -1,21 +1,21 @@
|
|||
{
|
||||
"_comment": "DO NOT EDIT: FILE GENERATED AUTOMATICALLY BY PTERODACTYL PANEL - PTERODACTYL.IO",
|
||||
"meta": {
|
||||
"version": "PTDL_v1",
|
||||
"version": "PTDL_v2",
|
||||
"update_url": null
|
||||
},
|
||||
"exported_at": "2022-01-18T11:44:55-05:00",
|
||||
"exported_at": "2023-03-25T13:37:00+00:00",
|
||||
"name": "Rust",
|
||||
"author": "support@pterodactyl.io",
|
||||
"description": "The only aim in Rust is to survive. To do this you will need to overcome struggles such as hunger, thirst and cold. Build a fire. Build a shelter. Kill animals for meat. Protect yourself from other players, and kill them for meat. Create alliances with other players and form a town. Do whatever it takes to survive.",
|
||||
"features": [
|
||||
"steam_disk_space"
|
||||
],
|
||||
"images": [
|
||||
"quay.io\/pterodactyl\/core:rust"
|
||||
],
|
||||
"docker_images": {
|
||||
"quay.io\/pterodactyl\/core:rust": "quay.io\/pterodactyl\/core:rust"
|
||||
},
|
||||
"file_denylist": [],
|
||||
"startup": ".\/RustDedicated -batchmode +server.port {{SERVER_PORT}} +server.identity \"rust\" +rcon.port {{RCON_PORT}} +rcon.web true +server.hostname \\\"{{HOSTNAME}}\\\" +server.level \\\"{{LEVEL}}\\\" +server.description \\\"{{DESCRIPTION}}\\\" +server.url \\\"{{SERVER_URL}}\\\" +server.headerimage \\\"{{SERVER_IMG}}\\\" +server.logoimage \\\"{{SERVER_LOGO}}\\\" +server.maxplayers {{MAX_PLAYERS}} +rcon.password \\\"{{RCON_PASS}}\\\" +server.saveinterval {{SAVEINTERVAL}} +app.port {{APP_PORT}} $( [ -z ${MAP_URL} ] && printf %s \"+server.worldsize \\\"{{WORLD_SIZE}}\\\" +server.seed \\\"{{WORLD_SEED}}\\\"\" || printf %s \"+server.levelurl {{MAP_URL}}\" ) {{ADDITIONAL_ARGS}}",
|
||||
"startup": ".\/RustDedicated -batchmode +server.port {{SERVER_PORT}} +server.queryport {{QUERY_PORT}} +server.identity \"rust\" +rcon.port {{RCON_PORT}} +rcon.web true +server.hostname \\\"{{HOSTNAME}}\\\" +server.level \\\"{{LEVEL}}\\\" +server.description \\\"{{DESCRIPTION}}\\\" +server.url \\\"{{SERVER_URL}}\\\" +server.headerimage \\\"{{SERVER_IMG}}\\\" +server.logoimage \\\"{{SERVER_LOGO}}\\\" +server.maxplayers {{MAX_PLAYERS}} +rcon.password \\\"{{RCON_PASS}}\\\" +server.saveinterval {{SAVEINTERVAL}} +app.port {{APP_PORT}} $( [ -z ${MAP_URL} ] && printf %s \"+server.worldsize \\\"{{WORLD_SIZE}}\\\" +server.seed \\\"{{WORLD_SEED}}\\\"\" || printf %s \"+server.levelurl {{MAP_URL}}\" ) {{ADDITIONAL_ARGS}}",
|
||||
"config": {
|
||||
"files": "{}",
|
||||
"startup": "{\r\n \"done\": \"Server startup complete\"\r\n}",
|
||||
|
@ -37,16 +37,18 @@
|
|||
"default_value": "A Rust Server",
|
||||
"user_viewable": true,
|
||||
"user_editable": true,
|
||||
"rules": "required|string|max:60"
|
||||
"rules": "required|string|max:60",
|
||||
"field_type": "text"
|
||||
},
|
||||
{
|
||||
"name": "OxideMod",
|
||||
"description": "Set whether you want the server to use and auto update OxideMod or not. Valid options are \"1\" for true and \"0\" for false.",
|
||||
"env_variable": "OXIDE",
|
||||
"default_value": "0",
|
||||
"name": "Modding Framework",
|
||||
"description": "The modding framework to be used: carbon, oxide, vanilla.\r\nDefaults to \"vanilla\" for a non-modded server installation.",
|
||||
"env_variable": "FRAMEWORK",
|
||||
"default_value": "vanilla",
|
||||
"user_viewable": true,
|
||||
"user_editable": true,
|
||||
"rules": "required|boolean"
|
||||
"rules": "required|in:carbon,oxide,vanilla",
|
||||
"field_type": "text"
|
||||
},
|
||||
{
|
||||
"name": "Level",
|
||||
|
@ -55,7 +57,8 @@
|
|||
"default_value": "Procedural Map",
|
||||
"user_viewable": true,
|
||||
"user_editable": true,
|
||||
"rules": "required|string|max:20"
|
||||
"rules": "required|string|max:20",
|
||||
"field_type": "text"
|
||||
},
|
||||
{
|
||||
"name": "Description",
|
||||
|
@ -64,7 +67,8 @@
|
|||
"default_value": "Powered by Pterodactyl",
|
||||
"user_viewable": true,
|
||||
"user_editable": true,
|
||||
"rules": "required|string"
|
||||
"rules": "required|string",
|
||||
"field_type": "text"
|
||||
},
|
||||
{
|
||||
"name": "URL",
|
||||
|
@ -73,7 +77,8 @@
|
|||
"default_value": "http:\/\/pterodactyl.io",
|
||||
"user_viewable": true,
|
||||
"user_editable": true,
|
||||
"rules": "nullable|url"
|
||||
"rules": "nullable|url",
|
||||
"field_type": "text"
|
||||
},
|
||||
{
|
||||
"name": "World Size",
|
||||
|
@ -82,7 +87,8 @@
|
|||
"default_value": "3000",
|
||||
"user_viewable": true,
|
||||
"user_editable": true,
|
||||
"rules": "required|integer"
|
||||
"rules": "required|integer",
|
||||
"field_type": "text"
|
||||
},
|
||||
{
|
||||
"name": "World Seed",
|
||||
|
@ -91,7 +97,8 @@
|
|||
"default_value": "",
|
||||
"user_viewable": true,
|
||||
"user_editable": true,
|
||||
"rules": "nullable|string"
|
||||
"rules": "nullable|string",
|
||||
"field_type": "text"
|
||||
},
|
||||
{
|
||||
"name": "Max Players",
|
||||
|
@ -100,7 +107,8 @@
|
|||
"default_value": "40",
|
||||
"user_viewable": true,
|
||||
"user_editable": true,
|
||||
"rules": "required|integer"
|
||||
"rules": "required|integer",
|
||||
"field_type": "text"
|
||||
},
|
||||
{
|
||||
"name": "Server Image",
|
||||
|
@ -109,7 +117,18 @@
|
|||
"default_value": "",
|
||||
"user_viewable": true,
|
||||
"user_editable": true,
|
||||
"rules": "nullable|url"
|
||||
"rules": "nullable|url",
|
||||
"field_type": "text"
|
||||
},
|
||||
{
|
||||
"name": "Query Port",
|
||||
"description": "Server Query Port. Can't be the same as Game's primary port.",
|
||||
"env_variable": "QUERY_PORT",
|
||||
"default_value": "27017",
|
||||
"user_viewable": true,
|
||||
"user_editable": false,
|
||||
"rules": "required|integer",
|
||||
"field_type": "text"
|
||||
},
|
||||
{
|
||||
"name": "RCON Port",
|
||||
|
@ -118,16 +137,18 @@
|
|||
"default_value": "28016",
|
||||
"user_viewable": true,
|
||||
"user_editable": false,
|
||||
"rules": "required|integer"
|
||||
"rules": "required|integer",
|
||||
"field_type": "text"
|
||||
},
|
||||
{
|
||||
"name": "RCON Password",
|
||||
"description": "RCON access password.",
|
||||
"env_variable": "RCON_PASS",
|
||||
"default_value": "CHANGEME",
|
||||
"default_value": "",
|
||||
"user_viewable": true,
|
||||
"user_editable": true,
|
||||
"rules": "required|regex:\/^[\\w.-]*$\/|max:64"
|
||||
"rules": "required|regex:\/^[\\w.-]*$\/|max:64",
|
||||
"field_type": "text"
|
||||
},
|
||||
{
|
||||
"name": "Save Interval",
|
||||
|
@ -136,7 +157,8 @@
|
|||
"default_value": "60",
|
||||
"user_viewable": true,
|
||||
"user_editable": true,
|
||||
"rules": "required|integer"
|
||||
"rules": "required|integer",
|
||||
"field_type": "text"
|
||||
},
|
||||
{
|
||||
"name": "Additional Arguments",
|
||||
|
@ -145,7 +167,8 @@
|
|||
"default_value": "",
|
||||
"user_viewable": true,
|
||||
"user_editable": true,
|
||||
"rules": "nullable|string"
|
||||
"rules": "nullable|string",
|
||||
"field_type": "text"
|
||||
},
|
||||
{
|
||||
"name": "App Port",
|
||||
|
@ -154,7 +177,8 @@
|
|||
"default_value": "28082",
|
||||
"user_viewable": true,
|
||||
"user_editable": false,
|
||||
"rules": "required|integer"
|
||||
"rules": "required|integer",
|
||||
"field_type": "text"
|
||||
},
|
||||
{
|
||||
"name": "Server Logo",
|
||||
|
@ -163,7 +187,8 @@
|
|||
"default_value": "",
|
||||
"user_viewable": true,
|
||||
"user_editable": true,
|
||||
"rules": "nullable|url"
|
||||
"rules": "nullable|url",
|
||||
"field_type": "text"
|
||||
},
|
||||
{
|
||||
"name": "Custom Map URL",
|
||||
|
@ -172,7 +197,8 @@
|
|||
"default_value": "",
|
||||
"user_viewable": true,
|
||||
"user_editable": true,
|
||||
"rules": "nullable|url"
|
||||
"rules": "nullable|url",
|
||||
"field_type": "text"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
return new class () extends Migration {
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('failed_jobs', function (Blueprint $table) {
|
||||
$table->string('uuid')->after('id')->nullable()->unique();
|
||||
});
|
||||
|
||||
DB::table('failed_jobs')->whereNull('uuid')->cursor()->each(function ($job) {
|
||||
DB::table('failed_jobs')
|
||||
->where('id', $job->id)
|
||||
->update(['uuid' => (string) Illuminate\Support\Str::uuid()]);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('failed_jobs', function (Blueprint $table) {
|
||||
$table->dropColumn('uuid');
|
||||
});
|
||||
}
|
||||
};
|
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
return new class () extends Migration {
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('api_keys', function (Blueprint $table) {
|
||||
$table->timestamp('expires_at')->nullable()->after('last_used_at');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('api_keys', function (Blueprint $table) {
|
||||
$table->dropColumn('expires_at');
|
||||
});
|
||||
}
|
||||
};
|
346
flake.lock
346
flake.lock
|
@ -1,61 +1,5 @@
|
|||
{
|
||||
"nodes": {
|
||||
"alejandra": {
|
||||
"inputs": {
|
||||
"fenix": "fenix",
|
||||
"flakeCompat": "flakeCompat",
|
||||
"nixpkgs": [
|
||||
"dream2nix",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1658427149,
|
||||
"narHash": "sha256-ToD/1z/q5VHsLMrS2h96vjJoLho59eNRtknOUd19ey8=",
|
||||
"owner": "kamadorueda",
|
||||
"repo": "alejandra",
|
||||
"rev": "f5a22afd2adfb249b4e68e0b33aa1f0fb73fb1be",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "kamadorueda",
|
||||
"repo": "alejandra",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"all-cabal-json": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1665552503,
|
||||
"narHash": "sha256-r14RmRSwzv5c+bWKUDaze6pXM7nOsiz1H8nvFHJvufc=",
|
||||
"owner": "nix-community",
|
||||
"repo": "all-cabal-json",
|
||||
"rev": "d7c0434eebffb305071404edcf9d5cd99703878e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"ref": "hackage",
|
||||
"repo": "all-cabal-json",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"crane": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1661875961,
|
||||
"narHash": "sha256-f1h/2c6Teeu1ofAHWzrS8TwBPcnN+EEu+z1sRVmMQTk=",
|
||||
"owner": "ipetkov",
|
||||
"repo": "crane",
|
||||
"rev": "d9f394e4e20e97c2a60c3ad82c2b6ef99be19e24",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "ipetkov",
|
||||
"repo": "crane",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"devshell": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
|
@ -74,28 +18,21 @@
|
|||
},
|
||||
"dream2nix": {
|
||||
"inputs": {
|
||||
"alejandra": "alejandra",
|
||||
"all-cabal-json": "all-cabal-json",
|
||||
"crane": "crane",
|
||||
"devshell": "devshell",
|
||||
"flake-compat": "flake-compat",
|
||||
"flake-parts": "flake-parts",
|
||||
"flake-utils-pre-commit": "flake-utils-pre-commit",
|
||||
"ghc-utils": "ghc-utils",
|
||||
"gomod2nix": "gomod2nix",
|
||||
"mach-nix": "mach-nix",
|
||||
"nix-pypi-fetcher": "nix-pypi-fetcher",
|
||||
"nix-unit": "nix-unit",
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
],
|
||||
"poetry2nix": "poetry2nix",
|
||||
"pre-commit-hooks": "pre-commit-hooks"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1669743839,
|
||||
"narHash": "sha256-zxnaRaWfCJxy0JlORD4Kmtzd0pfpcGLnyaCIJY8OlIo=",
|
||||
"lastModified": 1695717405,
|
||||
"narHash": "sha256-MvHrU3h0Bw57s2p+wCUnSZliR4wvvPi3xkW+MRWB5HU=",
|
||||
"owner": "nix-community",
|
||||
"repo": "dream2nix",
|
||||
"rev": "b6af93946130748f72671dfd2ab84a5aeaf1f191",
|
||||
"rev": "6dbd59e4a47bd916a655c4425a3e730c6aeae033",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -104,39 +41,35 @@
|
|||
"type": "github"
|
||||
}
|
||||
},
|
||||
"fenix": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"dream2nix",
|
||||
"alejandra",
|
||||
"nixpkgs"
|
||||
],
|
||||
"rust-analyzer-src": "rust-analyzer-src"
|
||||
},
|
||||
"flake-compat": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1657607339,
|
||||
"narHash": "sha256-HaqoAwlbVVZH2n4P3jN2FFPMpVuhxDy1poNOR7kzODc=",
|
||||
"owner": "nix-community",
|
||||
"repo": "fenix",
|
||||
"rev": "b814c83d9e6aa5a28d0cf356ecfdafb2505ad37d",
|
||||
"lastModified": 1673956053,
|
||||
"narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=",
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"repo": "fenix",
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-parts": {
|
||||
"inputs": {
|
||||
"nixpkgs-lib": "nixpkgs-lib"
|
||||
"nixpkgs-lib": [
|
||||
"dream2nix",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1668450977,
|
||||
"narHash": "sha256-cfLhMhnvXn6x1vPm+Jow3RiFAUSCw/l1utktCw5rVA4=",
|
||||
"lastModified": 1675933616,
|
||||
"narHash": "sha256-/rczJkJHtx16IFxMmAWu5nNYcSXNg1YYXTHoGjLrLUA=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"rev": "d591857e9d7dd9ddbfba0ea02b43b927c3c0f1fa",
|
||||
"rev": "47478a4a003e745402acf63be7f9a092d51b83d7",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -146,12 +79,15 @@
|
|||
}
|
||||
},
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1667395993,
|
||||
"narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=",
|
||||
"lastModified": 1689068808,
|
||||
"narHash": "sha256-6ixXo3wt24N/melDWjq70UuHQLxGV8jZvooRanIHXw0=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f",
|
||||
"rev": "919d646de7be200f3bf08cb76ae1f09402b6f9b4",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -160,13 +96,16 @@
|
|||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils-pre-commit": {
|
||||
"flake-utils_2": {
|
||||
"inputs": {
|
||||
"systems": "systems_2"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1644229661,
|
||||
"narHash": "sha256-1YdnJAsNy69bpcjuoKdOYQX0YxZBiCYZo4Twxerqv7k=",
|
||||
"lastModified": 1694529238,
|
||||
"narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "3cecb5b042f7f209c56ffd8371b2711a290ec797",
|
||||
"rev": "ff7b65b44d01cf9ba6a71320833626af21126384",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -175,69 +114,6 @@
|
|||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flakeCompat": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1650374568,
|
||||
"narHash": "sha256-Z+s0J8/r907g149rllvwhb4pKi8Wam5ij0st8PwAh+E=",
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"rev": "b4a34015c698c7793d592d66adbab377907a2be8",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"ghc-utils": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1662774800,
|
||||
"narHash": "sha256-1Rd2eohGUw/s1tfvkepeYpg8kCEXiIot0RijapUjAkE=",
|
||||
"ref": "refs/heads/master",
|
||||
"rev": "bb3a2d3dc52ff0253fb9c2812bd7aa2da03e0fea",
|
||||
"revCount": 1072,
|
||||
"type": "git",
|
||||
"url": "https://gitlab.haskell.org/bgamari/ghc-utils"
|
||||
},
|
||||
"original": {
|
||||
"type": "git",
|
||||
"url": "https://gitlab.haskell.org/bgamari/ghc-utils"
|
||||
}
|
||||
},
|
||||
"gomod2nix": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1627572165,
|
||||
"narHash": "sha256-MFpwnkvQpauj799b4QTBJQFEddbD02+Ln5k92QyHOSk=",
|
||||
"owner": "tweag",
|
||||
"repo": "gomod2nix",
|
||||
"rev": "67f22dd738d092c6ba88e420350ada0ed4992ae8",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "tweag",
|
||||
"repo": "gomod2nix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"mach-nix": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1634711045,
|
||||
"narHash": "sha256-m5A2Ty88NChLyFhXucECj6+AuiMZPHXNbw+9Kcs7F6Y=",
|
||||
"owner": "DavHau",
|
||||
"repo": "mach-nix",
|
||||
"rev": "4433f74a97b94b596fa6cd9b9c0402104aceef5d",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"id": "mach-nix",
|
||||
"type": "indirect"
|
||||
}
|
||||
},
|
||||
"mk-node-package": {
|
||||
"inputs": {
|
||||
"flake-utils": [
|
||||
|
@ -263,29 +139,62 @@
|
|||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nix-pypi-fetcher": {
|
||||
"flake": false,
|
||||
"nix-github-actions": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"dream2nix",
|
||||
"nix-unit",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1669065297,
|
||||
"narHash": "sha256-UStjXjNIuIm7SzMOWvuYWIHBkPUKQ8Id63BMJjnIDoA=",
|
||||
"owner": "DavHau",
|
||||
"repo": "nix-pypi-fetcher",
|
||||
"rev": "a9885ac6a091576b5195d547ac743d45a2a615ac",
|
||||
"lastModified": 1688870561,
|
||||
"narHash": "sha256-4UYkifnPEw1nAzqqPOTL2MvWtm3sNGw1UTYTalkTcGY=",
|
||||
"owner": "nix-community",
|
||||
"repo": "nix-github-actions",
|
||||
"rev": "165b1650b753316aa7f1787f3005a8d2da0f5301",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "DavHau",
|
||||
"repo": "nix-pypi-fetcher",
|
||||
"owner": "nix-community",
|
||||
"repo": "nix-github-actions",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nix-unit": {
|
||||
"inputs": {
|
||||
"flake-parts": [
|
||||
"dream2nix",
|
||||
"flake-parts"
|
||||
],
|
||||
"nix-github-actions": "nix-github-actions",
|
||||
"nixpkgs": [
|
||||
"dream2nix",
|
||||
"nixpkgs"
|
||||
],
|
||||
"treefmt-nix": "treefmt-nix"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1690289081,
|
||||
"narHash": "sha256-PCXQAQt8+i2pkUym9P1JY4JGoeZJLzzxWBhprHDdItM=",
|
||||
"owner": "adisbladis",
|
||||
"repo": "nix-unit",
|
||||
"rev": "a9d6f33e50d4dcd9cfc0c92253340437bbae282b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "adisbladis",
|
||||
"repo": "nix-unit",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1669542132,
|
||||
"narHash": "sha256-DRlg++NJAwPh8io3ExBJdNW7Djs3plVI5jgYQ+iXAZQ=",
|
||||
"lastModified": 1695644571,
|
||||
"narHash": "sha256-asS9dCCdlt1lPq0DLwkVBbVoEKuEuz+Zi3DG7pR/RxA=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "a115bb9bd56831941be3776c8a94005867f316a7",
|
||||
"rev": "6500b4580c2a1f3d0f980d32d285739d8e156d92",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -295,24 +204,6 @@
|
|||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs-lib": {
|
||||
"locked": {
|
||||
"dir": "lib",
|
||||
"lastModified": 1665349835,
|
||||
"narHash": "sha256-UK4urM3iN80UXQ7EaOappDzcisYIuEURFRoGQ/yPkug=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "34c5293a71ffdb2fe054eb5288adc1882c1eb0b1",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"dir": "lib",
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"npmlock2nix": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
|
@ -347,29 +238,9 @@
|
|||
"type": "github"
|
||||
}
|
||||
},
|
||||
"poetry2nix": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1666918719,
|
||||
"narHash": "sha256-BkK42fjAku+2WgCOv2/1NrPa754eQPV7gPBmoKQBWlc=",
|
||||
"owner": "nix-community",
|
||||
"repo": "poetry2nix",
|
||||
"rev": "289efb187123656a116b915206e66852f038720e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"ref": "1.36.0",
|
||||
"repo": "poetry2nix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"pre-commit-hooks": {
|
||||
"inputs": {
|
||||
"flake-utils": [
|
||||
"dream2nix",
|
||||
"flake-utils-pre-commit"
|
||||
],
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": [
|
||||
"dream2nix",
|
||||
"nixpkgs"
|
||||
|
@ -392,25 +263,60 @@
|
|||
"root": {
|
||||
"inputs": {
|
||||
"dream2nix": "dream2nix",
|
||||
"flake-utils": "flake-utils",
|
||||
"flake-utils": "flake-utils_2",
|
||||
"mk-node-package": "mk-node-package",
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
},
|
||||
"rust-analyzer-src": {
|
||||
"flake": false,
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1657557289,
|
||||
"narHash": "sha256-PRW+nUwuqNTRAEa83SfX+7g+g8nQ+2MMbasQ9nt6+UM=",
|
||||
"owner": "rust-lang",
|
||||
"repo": "rust-analyzer",
|
||||
"rev": "caf23f29144b371035b864a1017dbc32573ad56d",
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "rust-lang",
|
||||
"ref": "nightly",
|
||||
"repo": "rust-analyzer",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"systems_2": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"treefmt-nix": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"dream2nix",
|
||||
"nix-unit",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1689620039,
|
||||
"narHash": "sha256-BtNwghr05z7k5YMdq+6nbue+nEalvDepuA7qdQMAKoQ=",
|
||||
"owner": "numtide",
|
||||
"repo": "treefmt-nix",
|
||||
"rev": "719c2977f958c41fa60a928e2fbc50af14844114",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "treefmt-nix",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -159,6 +159,7 @@
|
|||
}
|
||||
];
|
||||
systems = [system];
|
||||
autoProjects = true;
|
||||
})
|
||||
.packages
|
||||
."${system}"
|
||||
|
@ -171,7 +172,7 @@
|
|||
buildInputs = [];
|
||||
|
||||
buildPhase = ''
|
||||
yarn run build
|
||||
pnpm run build
|
||||
'';
|
||||
|
||||
installPhase = ''
|
||||
|
@ -213,6 +214,7 @@
|
|||
copyToRoot = pkgs.buildEnv {
|
||||
name = "image-root";
|
||||
paths = [
|
||||
bash
|
||||
dockerTools.fakeNss
|
||||
caCertificates
|
||||
caddy
|
||||
|
@ -221,11 +223,9 @@
|
|||
coreutils
|
||||
mysql80
|
||||
nodejs-18_x
|
||||
nodePackages.npm
|
||||
nodePackages.pnpm
|
||||
nodePackages.yarn
|
||||
php81WithExtensions
|
||||
postgresql_14
|
||||
postgresql_15
|
||||
];
|
||||
pathsToLink = ["/bin" "/etc"];
|
||||
};
|
||||
|
|
87
package.json
87
package.json
|
@ -1,9 +1,11 @@
|
|||
{
|
||||
"name": "@pterodactyl/panel",
|
||||
"version": "1.0.0",
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
"packageManager": "pnpm@8.7.6",
|
||||
"engines": {
|
||||
"node": ">=16.0"
|
||||
"node": ">=16.13"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "vite build",
|
||||
|
@ -39,15 +41,15 @@
|
|||
"@codemirror/view": "^6.0.0",
|
||||
"@floating-ui/react-dom-interactions": "0.13.3",
|
||||
"@flyyer/use-fit-text": "3.0.1",
|
||||
"@fortawesome/fontawesome-svg-core": "6.2.1",
|
||||
"@fortawesome/free-brands-svg-icons": "6.2.1",
|
||||
"@fortawesome/free-solid-svg-icons": "6.2.1",
|
||||
"@fortawesome/fontawesome-svg-core": "6.3.0",
|
||||
"@fortawesome/free-brands-svg-icons": "6.3.0",
|
||||
"@fortawesome/free-solid-svg-icons": "6.3.0",
|
||||
"@fortawesome/react-fontawesome": "0.2.0",
|
||||
"@headlessui/react": "1.7.5",
|
||||
"@headlessui/react": "1.7.11",
|
||||
"@heroicons/react": "1.0.6",
|
||||
"@lezer/common": "1.0.2",
|
||||
"@lezer/highlight": "1.1.3",
|
||||
"@preact/signals-react": "1.2.1",
|
||||
"@preact/signals-react": "1.2.2",
|
||||
"axios": "0.27.2",
|
||||
"boring-avatars": "1.7.0",
|
||||
"chart.js": "3.9.1",
|
||||
|
@ -55,75 +57,76 @@
|
|||
"copy-to-clipboard": "3.3.3",
|
||||
"date-fns": "2.29.3",
|
||||
"debounce": "1.2.1",
|
||||
"deepmerge-ts": "4.2.2",
|
||||
"deepmerge-ts": "4.3.0",
|
||||
"easy-peasy": "5.2.0",
|
||||
"events": "3.3.0",
|
||||
"formik": "2.2.9",
|
||||
"framer-motion": "7.7.2",
|
||||
"i18next": "22.4.3",
|
||||
"i18next-http-backend": "2.1.0",
|
||||
"framer-motion": "9.1.6",
|
||||
"i18next": "22.4.10",
|
||||
"i18next-http-backend": "2.1.1",
|
||||
"i18next-multiload-backend-adapter": "2.2.0",
|
||||
"nanoid": "4.0.0",
|
||||
"nanoid": "4.0.1",
|
||||
"qrcode.react": "3.1.0",
|
||||
"react": "18.2.0",
|
||||
"react-chartjs-2": "4.3.1",
|
||||
"react-dom": "18.2.0",
|
||||
"react-fast-compare": "3.2.0",
|
||||
"react-i18next": "12.1.1",
|
||||
"react-router-dom": "6.4.5",
|
||||
"react-i18next": "12.2.0",
|
||||
"react-router-dom": "6.8.1",
|
||||
"react-select": "5.7.0",
|
||||
"reaptcha": "1.12.1",
|
||||
"sockette": "2.0.6",
|
||||
"styled-components": "5.3.6",
|
||||
"styled-components-breakpoint": "3.0.0-preview.20",
|
||||
"swr": "1.3.0",
|
||||
"xterm": "5.0.0",
|
||||
"xterm-addon-fit": "0.6.0",
|
||||
"xterm-addon-search": "0.10.0",
|
||||
"swr": "2.0.3",
|
||||
"xterm": "5.1.0",
|
||||
"xterm-addon-fit": "0.7.0",
|
||||
"xterm-addon-search": "0.11.0",
|
||||
"xterm-addon-search-bar": "0.2.0",
|
||||
"xterm-addon-web-links": "0.7.0",
|
||||
"yup": "0.32.11"
|
||||
"xterm-addon-web-links": "0.8.0",
|
||||
"yup": "1.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/forms": "0.5.3",
|
||||
"@tailwindcss/line-clamp": "0.4.2",
|
||||
"@testing-library/dom": "8.19.0",
|
||||
"@testing-library/react": "13.4.0",
|
||||
"@testing-library/dom": "9.0.0",
|
||||
"@testing-library/react": "14.0.0",
|
||||
"@testing-library/user-event": "14.4.3",
|
||||
"@types/debounce": "1.2.1",
|
||||
"@types/events": "3.0.0",
|
||||
"@types/node": "18.11.13",
|
||||
"@types/react": "18.0.26",
|
||||
"@types/react-dom": "18.0.9",
|
||||
"@types/node": "18.14.1",
|
||||
"@types/react": "18.0.28",
|
||||
"@types/react-dom": "18.0.11",
|
||||
"@types/styled-components": "5.1.26",
|
||||
"@typescript-eslint/eslint-plugin": "5.46.1",
|
||||
"@typescript-eslint/parser": "5.46.1",
|
||||
"@vitejs/plugin-react": "3.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "5.53.0",
|
||||
"@typescript-eslint/parser": "5.53.0",
|
||||
"@vitejs/plugin-react": "3.1.0",
|
||||
"autoprefixer": "10.4.13",
|
||||
"babel-plugin-styled-components": "2.0.7",
|
||||
"babel-plugin-twin": "1.1.0",
|
||||
"cross-env": "7.0.3",
|
||||
"eslint": "8.29.0",
|
||||
"eslint-config-prettier": "8.5.0",
|
||||
"eslint": "8.34.0",
|
||||
"eslint-config-prettier": "8.6.0",
|
||||
"eslint-plugin-node": "11.1.0",
|
||||
"eslint-plugin-prettier": "4.2.1",
|
||||
"eslint-plugin-react": "7.31.11",
|
||||
"eslint-plugin-react": "7.32.2",
|
||||
"eslint-plugin-react-hooks": "4.6.0",
|
||||
"happy-dom": "8.1.1",
|
||||
"laravel-vite-plugin": "0.7.3",
|
||||
"pathe": "1.0.0",
|
||||
"postcss": "8.4.20",
|
||||
"postcss-nesting": "10.2.0",
|
||||
"postcss-preset-env": "7.8.3",
|
||||
"prettier": "2.8.1",
|
||||
"prettier-plugin-tailwindcss": "0.2.1",
|
||||
"happy-dom": "8.7.2",
|
||||
"laravel-vite-plugin": "0.7.4",
|
||||
"pathe": "1.1.0",
|
||||
"postcss": "8.4.21",
|
||||
"postcss-import": "15.1.0",
|
||||
"postcss-nesting": "11.2.1",
|
||||
"postcss-preset-env": "8.0.1",
|
||||
"prettier": "2.8.4",
|
||||
"prettier-plugin-tailwindcss": "0.2.3",
|
||||
"rimraf": "3.0.2",
|
||||
"tailwindcss": "3.2.4",
|
||||
"tailwindcss": "3.2.7",
|
||||
"ts-essentials": "9.3.0",
|
||||
"twin.macro": "2.8.2",
|
||||
"typescript": "4.9.4",
|
||||
"vite": "4.0.3",
|
||||
"vitest": "0.26.2"
|
||||
"typescript": "4.9.5",
|
||||
"vite": "4.1.4",
|
||||
"vitest": "0.28.5"
|
||||
},
|
||||
"browserslist": [
|
||||
"> 0.5%",
|
||||
|
|
|
@ -4,9 +4,8 @@
|
|||
xsi:noNamespaceSchemaLocation="./vendor/phpunit/phpunit/phpunit.xsd"
|
||||
bootstrap="bootstrap/tests.php"
|
||||
colors="true"
|
||||
printerClass="NunoMaduro\Collision\Adapters\Phpunit\Printer"
|
||||
>
|
||||
<coverage processUncoveredFiles="true">
|
||||
<coverage>
|
||||
<include>
|
||||
<directory suffix=".php">./app</directory>
|
||||
</include>
|
||||
|
|
5803
pnpm-lock.yaml
Normal file
5803
pnpm-lock.yaml
Normal file
File diff suppressed because it is too large
Load diff
|
@ -56,7 +56,7 @@ export interface EggVariable extends Model {
|
|||
* A standard API response with the minimum viable details for the frontend
|
||||
* to correctly render a egg.
|
||||
*/
|
||||
type LoadedEgg = WithRelationships<Egg, 'nest' | 'variables'>;
|
||||
export type LoadedEgg = WithRelationships<Egg, 'nest' | 'variables'>;
|
||||
|
||||
/**
|
||||
* Gets a single egg from the database and returns it.
|
||||
|
|
|
@ -14,13 +14,13 @@ export default (egg: Partial<Egg2>): Promise<Egg> => {
|
|||
config_files: egg.configFiles,
|
||||
config_startup: egg.configStartup,
|
||||
config_stop: egg.configStop,
|
||||
config_from: egg.configFrom,
|
||||
startup: egg.startup,
|
||||
script_container: egg.scriptContainer,
|
||||
copy_script_from: egg.copyScriptFrom,
|
||||
script_entry: egg.scriptEntry,
|
||||
script_is_privileged: egg.scriptIsPrivileged,
|
||||
script_install: egg.scriptInstall,
|
||||
// config_from: egg.configFrom,
|
||||
// copy_script_from: egg.copyScriptFrom,
|
||||
// script_is_privileged: egg.scriptIsPrivileged,
|
||||
})
|
||||
.then(({ data }) => resolve(rawDataToEgg(data)))
|
||||
.catch(reject);
|
||||
|
|
|
@ -43,15 +43,15 @@ export interface Egg {
|
|||
configFiles: Record<string, any> | null;
|
||||
configStartup: Record<string, any> | null;
|
||||
configStop: string | null;
|
||||
configFrom: number | null;
|
||||
startup: string;
|
||||
scriptContainer: string;
|
||||
copyScriptFrom: number | null;
|
||||
scriptEntry: string;
|
||||
scriptIsPrivileged: boolean;
|
||||
scriptInstall: string | null;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
// configFrom: number | null;
|
||||
// copyScriptFrom: number | null;
|
||||
// scriptIsPrivileged: boolean;
|
||||
|
||||
relations: {
|
||||
nest?: Nest;
|
||||
|
|
|
@ -17,7 +17,7 @@ export interface UpdateUserValues {
|
|||
}
|
||||
|
||||
const filters = ['id', 'uuid', 'external_id', 'username', 'email'] as const;
|
||||
type Filters = typeof filters[number];
|
||||
type Filters = (typeof filters)[number];
|
||||
|
||||
const useGetUsers = (
|
||||
params?: QueryBuilderParams<Filters>,
|
||||
|
|
|
@ -47,7 +47,7 @@ export const rawDataToFileObject = (data: FractalResponseData): FileObject => ({
|
|||
isEditable: function () {
|
||||
if (this.isArchiveType() || !this.isFile) return false;
|
||||
|
||||
const matches = ['application/jar', 'application/octet-stream', 'inode/directory', /^image\//];
|
||||
const matches = ['application/jar', 'application/octet-stream', 'inode/directory', /^image\/(?!svg\+xml)/];
|
||||
|
||||
return matches.every(m => !this.mimetype.match(m));
|
||||
},
|
||||
|
|
|
@ -11,16 +11,16 @@ import Select from '@/components/elements/Select';
|
|||
interface Props {
|
||||
nestId?: number;
|
||||
selectedEggId?: number;
|
||||
onEggSelect: (egg: Egg | null) => void;
|
||||
onEggSelect: (egg: WithRelationships<Egg, 'variables'> | undefined) => void;
|
||||
}
|
||||
|
||||
export default ({ nestId, selectedEggId, onEggSelect }: Props) => {
|
||||
const [, , { setValue, setTouched }] = useField<Record<string, string | undefined>>('environment');
|
||||
const [eggs, setEggs] = useState<WithRelationships<Egg, 'variables'>[] | null>(null);
|
||||
const [eggs, setEggs] = useState<WithRelationships<Egg, 'variables'>[] | undefined>(undefined);
|
||||
|
||||
const selectEgg = (egg: Egg | null) => {
|
||||
if (egg === null) {
|
||||
onEggSelect(null);
|
||||
const selectEgg = (egg: WithRelationships<Egg, 'variables'> | undefined) => {
|
||||
if (egg === undefined) {
|
||||
onEggSelect(undefined);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -40,26 +40,29 @@ export default ({ nestId, selectedEggId, onEggSelect }: Props) => {
|
|||
|
||||
useEffect(() => {
|
||||
if (!nestId) {
|
||||
setEggs(null);
|
||||
setEggs(undefined);
|
||||
return;
|
||||
}
|
||||
|
||||
searchEggs(nestId, {})
|
||||
.then(eggs => {
|
||||
setEggs(eggs);
|
||||
selectEgg(eggs[0] || null);
|
||||
.then(_eggs => {
|
||||
setEggs(_eggs);
|
||||
|
||||
// If the currently selected egg is in the selected nest, use it instead of picking the first egg on the nest.
|
||||
const egg = _eggs.find(egg => egg.id === selectedEggId) ?? _eggs[0];
|
||||
selectEgg(egg);
|
||||
})
|
||||
.catch(error => console.error(error));
|
||||
}, [nestId]);
|
||||
|
||||
const onSelectChange = (e: ChangeEvent<HTMLSelectElement>) => {
|
||||
selectEgg(eggs?.find(egg => egg.id.toString() === e.currentTarget.value) || null);
|
||||
const onSelectChange = (event: ChangeEvent<HTMLSelectElement>) => {
|
||||
selectEgg(eggs?.find(egg => egg.id.toString() === event.currentTarget.value) ?? undefined);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Label>Egg</Label>
|
||||
<Select id={'eggId'} name={'eggId'} defaultValue={selectedEggId} onChange={onSelectChange}>
|
||||
<Select id={'eggId'} name={'eggId'} value={selectedEggId} onChange={onSelectChange}>
|
||||
{!eggs ? (
|
||||
<option disabled>Loading...</option>
|
||||
) : (
|
||||
|
|
|
@ -30,6 +30,7 @@ import SpinnerOverlay from '@/components/elements/SpinnerOverlay';
|
|||
import FlashMessageRender from '@/components/FlashMessageRender';
|
||||
import useFlash from '@/plugins/useFlash';
|
||||
import AdminContentBlock from '@/components/admin/AdminContentBlock';
|
||||
import { WithRelationships } from '@/api/admin';
|
||||
|
||||
function InternalForm() {
|
||||
const {
|
||||
|
@ -39,12 +40,12 @@ function InternalForm() {
|
|||
values: { environment },
|
||||
} = useFormikContext<CreateServerRequest>();
|
||||
|
||||
const [egg, setEgg] = useState<Egg | null>(null);
|
||||
const [node, setNode] = useState<Node | null>(null);
|
||||
const [allocations, setAllocations] = useState<Allocation[] | null>(null);
|
||||
const [egg, setEgg] = useState<WithRelationships<Egg, 'variables'> | undefined>(undefined);
|
||||
const [node, setNode] = useState<Node | undefined>(undefined);
|
||||
const [allocations, setAllocations] = useState<Allocation[] | undefined>(undefined);
|
||||
|
||||
useEffect(() => {
|
||||
if (egg === null) {
|
||||
if (egg === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -54,7 +55,7 @@ function InternalForm() {
|
|||
}, [egg]);
|
||||
|
||||
useEffect(() => {
|
||||
if (node === null) {
|
||||
if (node === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -64,11 +65,11 @@ function InternalForm() {
|
|||
|
||||
return (
|
||||
<Form>
|
||||
<div css={tw`grid grid-cols-2 gap-y-6 gap-x-8 mb-16`}>
|
||||
<div css={tw`grid grid-cols-1 gap-y-6 col-span-2 md:col-span-1`}>
|
||||
<div className="grid grid-cols-2 gap-y-6 gap-x-8 mb-16">
|
||||
<div className="grid grid-cols-1 gap-y-6 col-span-2 md:col-span-1">
|
||||
<BaseSettingsBox>
|
||||
<NodeSelect node={node} setNode={setNode} />
|
||||
<div css={tw`xl:col-span-2 bg-neutral-800 border border-neutral-900 shadow-inner p-4 rounded`}>
|
||||
<div className="xl:col-span-2 bg-neutral-800 border border-neutral-900 shadow-inner p-4 rounded">
|
||||
<FormikSwitch
|
||||
name={'startOnCompletion'}
|
||||
label={'Start after installation'}
|
||||
|
@ -77,20 +78,20 @@ function InternalForm() {
|
|||
</div>
|
||||
</BaseSettingsBox>
|
||||
<FeatureLimitsBox />
|
||||
<ServerServiceContainer egg={egg} setEgg={setEgg} nestId={0} />
|
||||
<ServerServiceContainer selectedEggId={egg?.id} setEgg={setEgg} nestId={0} />
|
||||
</div>
|
||||
<div css={tw`grid grid-cols-1 gap-y-6 col-span-2 md:col-span-1`}>
|
||||
<AdminBox icon={faNetworkWired} title={'Networking'} isLoading={isSubmitting}>
|
||||
<div css={tw`grid grid-cols-1 gap-4 lg:gap-6`}>
|
||||
<div className="grid grid-cols-1 gap-y-6 col-span-2 md:col-span-1">
|
||||
<AdminBox icon={faNetworkWired} title="Networking" isLoading={isSubmitting}>
|
||||
<div className="grid grid-cols-1 gap-4 lg:gap-6">
|
||||
<div>
|
||||
<Label htmlFor={'allocation.default'}>Primary Allocation</Label>
|
||||
<Select
|
||||
id={'allocation.default'}
|
||||
name={'allocation.default'}
|
||||
disabled={node === null}
|
||||
disabled={node === undefined}
|
||||
onChange={e => setFieldValue('allocation.default', Number(e.currentTarget.value))}
|
||||
>
|
||||
{node === null ? (
|
||||
{node === undefined ? (
|
||||
<option value="">Select a node...</option>
|
||||
) : (
|
||||
<option value="">Select an allocation...</option>
|
||||
|
@ -116,7 +117,7 @@ function InternalForm() {
|
|||
<ServerImageContainer />
|
||||
</div>
|
||||
|
||||
<AdminBox title={'Startup Command'} css={tw`relative w-full col-span-2`}>
|
||||
<AdminBox title={'Startup Command'} className="relative w-full col-span-2">
|
||||
<SpinnerOverlay visible={isSubmitting} />
|
||||
|
||||
<Field
|
||||
|
@ -131,7 +132,7 @@ function InternalForm() {
|
|||
/>
|
||||
</AdminBox>
|
||||
|
||||
<div css={tw`col-span-2 grid grid-cols-1 md:grid-cols-2 gap-y-6 gap-x-8`}>
|
||||
<div className="col-span-2 grid grid-cols-1 md:grid-cols-2 gap-y-6 gap-x-8">
|
||||
{/* This ensures that no variables are rendered unless the environment has a value for the variable. */}
|
||||
{egg?.relationships.variables
|
||||
?.filter(v => Object.keys(environment).find(e => e === v.environmentVariable) !== undefined)
|
||||
|
@ -140,9 +141,9 @@ function InternalForm() {
|
|||
))}
|
||||
</div>
|
||||
|
||||
<div css={tw`bg-neutral-700 rounded shadow-md px-4 py-3 col-span-2`}>
|
||||
<div css={tw`flex flex-row`}>
|
||||
<Button type="submit" size="small" css={tw`ml-auto`} disabled={isSubmitting || !isValid}>
|
||||
<div className="bg-neutral-700 rounded shadow-md px-4 py-3 col-span-2">
|
||||
<div className="flex flex-row">
|
||||
<Button type="submit" size="small" className="ml-auto" disabled={isSubmitting || !isValid}>
|
||||
Create Server
|
||||
</Button>
|
||||
</div>
|
||||
|
|
|
@ -3,11 +3,9 @@ import { useStoreActions } from 'easy-peasy';
|
|||
import type { FormikHelpers } from 'formik';
|
||||
import { Form, Formik, useField, useFormikContext } from 'formik';
|
||||
import { useEffect, useState } from 'react';
|
||||
import tw from 'twin.macro';
|
||||
import { object } from 'yup';
|
||||
|
||||
import type { InferModel } from '@/api/admin';
|
||||
import type { Egg, EggVariable } from '@/api/admin/egg';
|
||||
import type { Egg, EggVariable, LoadedEgg } from '@/api/admin/egg';
|
||||
import { getEgg } from '@/api/admin/egg';
|
||||
import type { Server } from '@/api/admin/server';
|
||||
import { useServerFromRoute } from '@/api/admin/server';
|
||||
|
@ -23,12 +21,13 @@ import Field from '@/components/elements/Field';
|
|||
import SpinnerOverlay from '@/components/elements/SpinnerOverlay';
|
||||
import Label from '@/components/elements/Label';
|
||||
import type { ApplicationStore } from '@/state';
|
||||
import { WithRelationships } from '@/api/admin';
|
||||
|
||||
function ServerStartupLineContainer({ egg, server }: { egg: Egg | null; server: Server }) {
|
||||
function ServerStartupLineContainer({ egg, server }: { egg?: Egg; server: Server }) {
|
||||
const { isSubmitting, setFieldValue } = useFormikContext();
|
||||
|
||||
useEffect(() => {
|
||||
if (egg === null) {
|
||||
if (egg === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -44,10 +43,10 @@ function ServerStartupLineContainer({ egg, server }: { egg: Egg | null; server:
|
|||
}, [egg]);
|
||||
|
||||
return (
|
||||
<AdminBox title={'Startup Command'} css={tw`relative w-full`}>
|
||||
<AdminBox title={'Startup Command'} className="relative w-full">
|
||||
<SpinnerOverlay visible={isSubmitting} />
|
||||
|
||||
<div css={tw`mb-6`}>
|
||||
<div className="mb-6">
|
||||
<Field
|
||||
id={'startup'}
|
||||
name={'startup'}
|
||||
|
@ -69,12 +68,12 @@ function ServerStartupLineContainer({ egg, server }: { egg: Egg | null; server:
|
|||
}
|
||||
|
||||
export function ServerServiceContainer({
|
||||
egg,
|
||||
selectedEggId,
|
||||
setEgg,
|
||||
nestId: _nestId,
|
||||
}: {
|
||||
egg: Egg | null;
|
||||
setEgg: (value: Egg | null) => void;
|
||||
selectedEggId?: number;
|
||||
setEgg: (value: WithRelationships<Egg, 'variables'> | undefined) => void;
|
||||
nestId: number;
|
||||
}) {
|
||||
const { isSubmitting } = useFormikContext();
|
||||
|
@ -82,14 +81,14 @@ export function ServerServiceContainer({
|
|||
const [nestId, setNestId] = useState<number>(_nestId);
|
||||
|
||||
return (
|
||||
<AdminBox title={'Service Configuration'} isLoading={isSubmitting} css={tw`w-full`}>
|
||||
<div css={tw`mb-6`}>
|
||||
<AdminBox title={'Service Configuration'} isLoading={isSubmitting} className="w-full">
|
||||
<div className="mb-6">
|
||||
<NestSelector selectedNestId={nestId} onNestSelect={setNestId} />
|
||||
</div>
|
||||
<div css={tw`mb-6`}>
|
||||
<EggSelect nestId={nestId} selectedEggId={egg?.id} onEggSelect={setEgg} />
|
||||
<div className="mb-6">
|
||||
<EggSelect nestId={nestId} selectedEggId={selectedEggId} onEggSelect={setEgg} />
|
||||
</div>
|
||||
<div css={tw`bg-neutral-800 border border-neutral-900 shadow-inner p-4 rounded`}>
|
||||
<div className="bg-neutral-800 border border-neutral-900 shadow-inner p-4 rounded">
|
||||
<FormikSwitch name={'skipScripts'} label={'Skip Egg Install Script'} description={'Soon™'} />
|
||||
</div>
|
||||
</AdminBox>
|
||||
|
@ -100,10 +99,10 @@ export function ServerImageContainer() {
|
|||
const { isSubmitting } = useFormikContext();
|
||||
|
||||
return (
|
||||
<AdminBox title={'Image Configuration'} css={tw`relative w-full`}>
|
||||
<AdminBox title={'Image Configuration'} className="relative w-full">
|
||||
<SpinnerOverlay visible={isSubmitting} />
|
||||
|
||||
<div css={tw`md:w-full md:flex md:flex-col`}>
|
||||
<div className="md:w-full md:flex md:flex-col">
|
||||
<div>
|
||||
{/* TODO: make this a proper select but allow a custom image to be specified if needed. */}
|
||||
<Field id={'image'} name={'image'} label={'Docker Image'} type={'text'} />
|
||||
|
@ -130,7 +129,7 @@ export function ServerVariableContainer({ variable, value }: { variable: EggVari
|
|||
}, [value]);
|
||||
|
||||
return (
|
||||
<AdminBox css={tw`relative w-full`} title={<p css={tw`text-sm uppercase`}>{variable.name}</p>}>
|
||||
<AdminBox className="relative w-full" title={<p className="text-sm uppercase">{variable.name}</p>}>
|
||||
<SpinnerOverlay visible={isSubmitting} />
|
||||
|
||||
<Field
|
||||
|
@ -145,12 +144,14 @@ export function ServerVariableContainer({ variable, value }: { variable: EggVari
|
|||
}
|
||||
|
||||
function ServerStartupForm({
|
||||
selectedEggId,
|
||||
egg,
|
||||
setEgg,
|
||||
server,
|
||||
}: {
|
||||
egg: Egg | null;
|
||||
setEgg: (value: Egg | null) => void;
|
||||
selectedEggId?: number;
|
||||
egg?: LoadedEgg;
|
||||
setEgg: (value: LoadedEgg | undefined) => void;
|
||||
server: Server;
|
||||
}) {
|
||||
const {
|
||||
|
@ -161,22 +162,22 @@ function ServerStartupForm({
|
|||
|
||||
return (
|
||||
<Form>
|
||||
<div css={tw`flex flex-col mb-16`}>
|
||||
<div css={tw`flex flex-row mb-6`}>
|
||||
<div className="flex flex-col mb-16">
|
||||
<div className="flex flex-row mb-6">
|
||||
<ServerStartupLineContainer egg={egg} server={server} />
|
||||
</div>
|
||||
|
||||
<div css={tw`grid grid-cols-1 md:grid-cols-2 gap-x-8 gap-y-6 mb-6`}>
|
||||
<div css={tw`flex`}>
|
||||
<ServerServiceContainer egg={egg} setEgg={setEgg} nestId={server.nestId} />
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-8 gap-y-6 mb-6">
|
||||
<div className="flex">
|
||||
<ServerServiceContainer selectedEggId={selectedEggId} setEgg={setEgg} nestId={server.nestId} />
|
||||
</div>
|
||||
|
||||
<div css={tw`flex`}>
|
||||
<div className="flex">
|
||||
<ServerImageContainer />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div css={tw`grid grid-cols-1 md:grid-cols-2 gap-y-6 gap-x-8`}>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-y-6 gap-x-8">
|
||||
{/* This ensures that no variables are rendered unless the environment has a value for the variable. */}
|
||||
{egg?.relationships.variables
|
||||
?.filter(v => Object.keys(environment).find(e => e === v.environmentVariable) !== undefined)
|
||||
|
@ -193,9 +194,9 @@ function ServerStartupForm({
|
|||
))}
|
||||
</div>
|
||||
|
||||
<div css={tw`bg-neutral-700 rounded shadow-md py-2 pr-6 mt-6`}>
|
||||
<div css={tw`flex flex-row`}>
|
||||
<Button type="submit" size="small" css={tw`ml-auto`} disabled={isSubmitting || !isValid}>
|
||||
<div className="bg-neutral-700 rounded shadow-md py-2 pr-6 mt-6">
|
||||
<div className="flex flex-row">
|
||||
<Button type="submit" size="small" className="ml-auto" disabled={isSubmitting || !isValid}>
|
||||
Save Changes
|
||||
</Button>
|
||||
</div>
|
||||
|
@ -210,10 +211,12 @@ export default () => {
|
|||
const { clearFlashes, clearAndAddHttpError } = useStoreActions(
|
||||
(actions: Actions<ApplicationStore>) => actions.flashes,
|
||||
);
|
||||
const [egg, setEgg] = useState<InferModel<typeof getEgg> | null>(null);
|
||||
const [egg, setEgg] = useState<LoadedEgg | undefined>(undefined);
|
||||
|
||||
useEffect(() => {
|
||||
if (!server) return;
|
||||
if (!server) {
|
||||
return;
|
||||
}
|
||||
|
||||
getEgg(server.eggId)
|
||||
.then(egg => setEgg(egg))
|
||||
|
@ -249,10 +252,10 @@ export default () => {
|
|||
validationSchema={object().shape({})}
|
||||
>
|
||||
<ServerStartupForm
|
||||
selectedEggId={egg?.id ?? server.eggId}
|
||||
egg={egg}
|
||||
// @ts-expect-error fix this
|
||||
setEgg={setEgg}
|
||||
server={server}
|
||||
server={server as Server}
|
||||
/>
|
||||
</Formik>
|
||||
);
|
||||
|
|
|
@ -4,7 +4,7 @@ import { useState } from 'react';
|
|||
import Checkbox from '@/components/elements/inputs/Checkbox';
|
||||
import { Dropdown } from '@/components/elements/dropdown';
|
||||
import { Dialog } from '@/components/elements/dialog';
|
||||
import { User } from '@definitions/admin';
|
||||
import type { User } from '@definitions/admin';
|
||||
|
||||
interface Props {
|
||||
user: User;
|
||||
|
@ -12,7 +12,7 @@ interface Props {
|
|||
onRowChange: (user: User, selected: boolean) => void;
|
||||
}
|
||||
|
||||
const UserTableRow = ({ user, selected, onRowChange }: Props) => {
|
||||
function UserTableRow({ user, selected, onRowChange }: Props) {
|
||||
const [visible, setVisible] = useState(false);
|
||||
|
||||
return (
|
||||
|
@ -56,12 +56,14 @@ const UserTableRow = ({ user, selected, onRowChange }: Props) => {
|
|||
</span>
|
||||
)}
|
||||
</td>
|
||||
<td className={'whitespace-nowrap px-6 py-4'}>
|
||||
<td className="whitespace-nowrap px-6 py-4">
|
||||
<Dropdown>
|
||||
<Dropdown.Button className={'px-2'}>
|
||||
<Dropdown.Button className="px-2">
|
||||
<DotsVerticalIcon />
|
||||
</Dropdown.Button>
|
||||
<Dropdown.Item icon={<PencilIcon />}>Edit</Dropdown.Item>
|
||||
<Dropdown.Item to={`/admin/users/${user.id}`} icon={<PencilIcon />}>
|
||||
Edit
|
||||
</Dropdown.Item>
|
||||
<Dropdown.Item icon={<SupportIcon />}>Reset Password</Dropdown.Item>
|
||||
<Dropdown.Item icon={<LockOpenIcon />} disabled={!user.isUsingTwoFactor}>
|
||||
Disable 2-FA
|
||||
|
@ -76,6 +78,6 @@ const UserTableRow = ({ user, selected, onRowChange }: Props) => {
|
|||
</tr>
|
||||
</>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
export default UserTableRow;
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { LockOpenIcon, PlusIcon, SupportIcon, TrashIcon } from '@heroicons/react/solid';
|
||||
import { Fragment, useEffect, useState } from 'react';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
|
||||
import { useGetUsers } from '@/api/admin/users';
|
||||
import type { UUID } from '@/api/definitions';
|
||||
|
@ -16,7 +17,7 @@ import { Shape } from '@/components/elements/button/types';
|
|||
|
||||
const filters = ['id', 'uuid', 'external_id', 'username', 'email'] as const;
|
||||
|
||||
const UsersContainer = () => {
|
||||
function UsersContainer() {
|
||||
const [search, setSearch] = useDebouncedState('', 500);
|
||||
const [selected, setSelected] = useState<UUID[]>([]);
|
||||
const { data: users } = useGetUsers(
|
||||
|
@ -42,13 +43,16 @@ const UsersContainer = () => {
|
|||
|
||||
return (
|
||||
<div>
|
||||
<div className={'mb-4 flex justify-end'}>
|
||||
<Button className={'shadow focus:ring-offset-2 focus:ring-offset-neutral-800'}>
|
||||
Add User <PlusIcon className={'ml-2 h-5 w-5'} />
|
||||
<div className="mb-4 flex justify-end">
|
||||
<NavLink to="/admin/users/new">
|
||||
<Button className="shadow focus:ring-offset-2 focus:ring-offset-neutral-800">
|
||||
Add User <PlusIcon className="ml-2 h-5 w-5" />
|
||||
</Button>
|
||||
</NavLink>
|
||||
</div>
|
||||
<div className={'relative flex items-center rounded-t bg-neutral-700 px-4 py-2'}>
|
||||
<div className={'mr-6'}>
|
||||
|
||||
<div className="relative flex items-center rounded-t bg-neutral-700 px-4 py-2">
|
||||
<div className="mr-6">
|
||||
<Checkbox
|
||||
checked={selectAllChecked}
|
||||
disabled={!users?.items.length}
|
||||
|
@ -56,22 +60,18 @@ const UsersContainer = () => {
|
|||
onChange={onSelectAll}
|
||||
/>
|
||||
</div>
|
||||
<div className={'flex-1'}>
|
||||
<div className="flex-1">
|
||||
<InputField
|
||||
type={'text'}
|
||||
name={'filter'}
|
||||
placeholder={'Begin typing to filter...'}
|
||||
className={'w-56 focus:w-96'}
|
||||
type="text"
|
||||
name="filter"
|
||||
placeholder="Begin typing to filter..."
|
||||
className="w-56 focus:w-96"
|
||||
onChange={e => setSearch(e.currentTarget.value)}
|
||||
/>
|
||||
</div>
|
||||
<Transition.Fade as={Fragment} show={selected.length > 0} duration={'duration-75'}>
|
||||
<div
|
||||
className={
|
||||
'absolute top-0 left-0 flex h-full w-full items-center justify-end space-x-4 rounded-t bg-neutral-700 px-4'
|
||||
}
|
||||
>
|
||||
<div className={'flex-1'}>
|
||||
<Transition.Fade as={Fragment} show={selected.length > 0} duration="duration-75">
|
||||
<div className="absolute top-0 left-0 flex h-full w-full items-center justify-end space-x-4 rounded-t bg-neutral-700 px-4">
|
||||
<div className="flex-1">
|
||||
<Checkbox
|
||||
checked={selectAllChecked}
|
||||
indeterminate={selected.length !== users?.items.length}
|
||||
|
@ -79,26 +79,26 @@ const UsersContainer = () => {
|
|||
/>
|
||||
</div>
|
||||
<Button.Text shape={Shape.IconSquare}>
|
||||
<SupportIcon className={'h-4 w-4'} />
|
||||
<SupportIcon className="h-4 w-4" />
|
||||
</Button.Text>
|
||||
<Button.Text shape={Shape.IconSquare}>
|
||||
<LockOpenIcon className={'h-4 w-4'} />
|
||||
<LockOpenIcon className="h-4 w-4" />
|
||||
</Button.Text>
|
||||
<Button.Text shape={Shape.IconSquare}>
|
||||
<TrashIcon className={'h-4 w-4'} />
|
||||
<TrashIcon className="h-4 w-4" />
|
||||
</Button.Text>
|
||||
</div>
|
||||
</Transition.Fade>
|
||||
</div>
|
||||
<table className={'min-w-full rounded bg-neutral-700'}>
|
||||
<thead className={'bg-neutral-900'}>
|
||||
<table className="min-w-full rounded bg-neutral-700">
|
||||
<thead className="bg-neutral-900">
|
||||
<tr>
|
||||
<th scope={'col'} className={'w-8'} />
|
||||
<th scope={'col'} className={'w-full px-6 py-2 text-left'}>
|
||||
<th scope="col" className="w-8" />
|
||||
<th scope="col" className="w-full px-6 py-2 text-left">
|
||||
Email
|
||||
</th>
|
||||
<th scope={'col'} />
|
||||
<th scope={'col'} />
|
||||
<th scope="col" />
|
||||
<th scope="col" />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
@ -111,10 +111,10 @@ const UsersContainer = () => {
|
|||
/>
|
||||
))}
|
||||
</tbody>
|
||||
{users && <TFootPaginated span={4} pagination={users.pagination} />}
|
||||
{users ? <TFootPaginated span={4} pagination={users.pagination} /> : null}
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
export default UsersContainer;
|
||||
|
|
|
@ -40,7 +40,7 @@ const inputStyle = css<Props>`
|
|||
// Reset to normal styling.
|
||||
resize: none;
|
||||
${tw`appearance-none outline-none w-full min-w-0`};
|
||||
${tw`p-3 border-2 rounded text-sm transition-all duration-150`};
|
||||
${tw`py-2.5 px-3 border-2 rounded text-sm transition-all duration-150`};
|
||||
${tw`bg-neutral-600 border-neutral-500 hover:border-neutral-400 text-neutral-200 shadow-none focus:ring-0`};
|
||||
|
||||
& + .input-help {
|
||||
|
|
|
@ -1,27 +1,29 @@
|
|||
import { ElementType, forwardRef, useMemo } from 'react';
|
||||
import * as React from 'react';
|
||||
import { Menu, Transition } from '@headlessui/react';
|
||||
import styles from './style.module.css';
|
||||
import classNames from 'classnames';
|
||||
import DropdownItem from '@/components/elements/dropdown/DropdownItem';
|
||||
import DropdownButton from '@/components/elements/dropdown/DropdownButton';
|
||||
import type { ElementType, ReactNode } from 'react';
|
||||
import { Children as ReactChildren } from 'react';
|
||||
import { forwardRef, useMemo } from 'react';
|
||||
|
||||
import { DropdownButton } from '@/components/elements/dropdown/DropdownButton';
|
||||
import { DropdownItem } from '@/components/elements/dropdown/DropdownItem';
|
||||
import styles from './style.module.css';
|
||||
|
||||
interface Props {
|
||||
as?: ElementType;
|
||||
children: React.ReactNode;
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
const DropdownGap = ({ invisible }: { invisible?: boolean }) => (
|
||||
<div className={classNames('m-2 border', { 'border-neutral-700': !invisible, 'border-transparent': invisible })} />
|
||||
);
|
||||
|
||||
type TypedChild = (React.ReactChild | React.ReactFragment | React.ReactPortal) & {
|
||||
type TypedChild = ReactNode & {
|
||||
type?: JSX.Element;
|
||||
};
|
||||
|
||||
const Dropdown = forwardRef<typeof Menu, Props>(({ as, children }, ref) => {
|
||||
const [Button, items] = useMemo(() => {
|
||||
const list = React.Children.toArray(children) as unknown as TypedChild[];
|
||||
const list = ReactChildren.toArray(children) as unknown as TypedChild[];
|
||||
|
||||
return [
|
||||
list.filter(child => child.type === DropdownButton),
|
||||
|
@ -34,18 +36,18 @@ const Dropdown = forwardRef<typeof Menu, Props>(({ as, children }, ref) => {
|
|||
}
|
||||
|
||||
return (
|
||||
<Menu as={as || 'div'} className={styles.menu} ref={ref}>
|
||||
<Menu as={as ?? 'div'} className={styles.menu} ref={ref}>
|
||||
{Button}
|
||||
<Transition
|
||||
enter={'transition duration-100 ease-out'}
|
||||
enterFrom={'transition scale-95 opacity-0'}
|
||||
enterTo={'transform scale-100 opacity-100'}
|
||||
leave={'transition duration-75 ease-out'}
|
||||
leaveFrom={'transform scale-100 opacity-100'}
|
||||
leaveTo={'transform scale-95 opacity-0'}
|
||||
enter="transition duration-100 ease-out"
|
||||
enterFrom="transition scale-95 opacity-0"
|
||||
enterTo="transform scale-100 opacity-100"
|
||||
leave="transition duration-75 ease-out"
|
||||
leaveFrom="transform scale-100 opacity-100"
|
||||
leaveTo="transform scale-95 opacity-0"
|
||||
>
|
||||
<Menu.Items className={classNames(styles.items_container, 'w-56')}>
|
||||
<div className={'px-1 py-1'}>{items}</div>
|
||||
<div className="px-1 py-1">{items}</div>
|
||||
</Menu.Items>
|
||||
</Transition>
|
||||
</Menu>
|
||||
|
|
|
@ -1,24 +1,29 @@
|
|||
import classNames from 'classnames';
|
||||
import styles from '@/components/elements/dropdown/style.module.css';
|
||||
import { ChevronDownIcon } from '@heroicons/react/solid';
|
||||
import { Menu } from '@headlessui/react';
|
||||
import * as React from 'react';
|
||||
import { ChevronDownIcon } from '@heroicons/react/solid';
|
||||
import classNames from 'classnames';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
import styles from './style.module.css';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
animate?: boolean;
|
||||
children: React.ReactNode;
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export default ({ className, animate = true, children }: Props) => (
|
||||
<Menu.Button className={classNames(styles.button, className || 'px-4')}>
|
||||
function DropdownButton({ className, animate = true, children }: Props) {
|
||||
return (
|
||||
<Menu.Button className={classNames(styles.button, className ?? 'px-4')}>
|
||||
{typeof children === 'string' ? (
|
||||
<>
|
||||
<span className={'mr-2'}>{children}</span>
|
||||
<ChevronDownIcon aria-hidden={'true'} data-animated={animate.toString()} />
|
||||
<span className="mr-2">{children}</span>
|
||||
<ChevronDownIcon aria-hidden="true" data-animated={animate.toString()} />
|
||||
</>
|
||||
) : (
|
||||
children
|
||||
)}
|
||||
</Menu.Button>
|
||||
);
|
||||
}
|
||||
|
||||
export { DropdownButton };
|
||||
|
|
|
@ -1,26 +1,32 @@
|
|||
import { forwardRef } from 'react';
|
||||
import * as React from 'react';
|
||||
import { Menu } from '@headlessui/react';
|
||||
import styles from './style.module.css';
|
||||
import classNames from 'classnames';
|
||||
import type { MouseEvent, ReactNode, Ref } from 'react';
|
||||
import { forwardRef } from 'react';
|
||||
import type { NavLinkProps } from 'react-router-dom';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
|
||||
import styles from './style.module.css';
|
||||
|
||||
interface Props {
|
||||
children: React.ReactNode | ((opts: { active: boolean; disabled: boolean }) => JSX.Element);
|
||||
children: ReactNode | ((opts: { active: boolean; disabled: boolean }) => JSX.Element);
|
||||
danger?: boolean;
|
||||
disabled?: boolean;
|
||||
className?: string;
|
||||
icon?: JSX.Element;
|
||||
onClick?: (e: React.MouseEvent) => void;
|
||||
onClick?: (e: MouseEvent) => void;
|
||||
}
|
||||
|
||||
const DropdownItem = forwardRef<HTMLAnchorElement, Props>(
|
||||
({ disabled, danger, className, onClick, children, icon: IconComponent }, ref) => {
|
||||
const DropdownItem = forwardRef<HTMLAnchorElement | HTMLButtonElement, Props & Partial<Omit<NavLinkProps, 'children'>>>(
|
||||
({ disabled, danger, className, onClick, children, icon: IconComponent, ...props }, ref) => {
|
||||
return (
|
||||
<Menu.Item disabled={disabled}>
|
||||
{({ disabled, active }) => (
|
||||
<a
|
||||
ref={ref}
|
||||
href={'#'}
|
||||
<>
|
||||
{'to' in props && props.to !== undefined ? (
|
||||
<NavLink
|
||||
{...props}
|
||||
to={props.to}
|
||||
ref={ref as unknown as Ref<HTMLAnchorElement>}
|
||||
className={classNames(
|
||||
styles.menu_item,
|
||||
{
|
||||
|
@ -33,11 +39,30 @@ const DropdownItem = forwardRef<HTMLAnchorElement, Props>(
|
|||
>
|
||||
{IconComponent}
|
||||
{typeof children === 'function' ? children({ disabled, active }) : children}
|
||||
</a>
|
||||
</NavLink>
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
ref={ref as unknown as Ref<HTMLButtonElement>}
|
||||
className={classNames(
|
||||
styles.menu_item,
|
||||
{
|
||||
[styles.danger]: danger,
|
||||
[styles.disabled]: disabled,
|
||||
},
|
||||
className,
|
||||
)}
|
||||
onClick={onClick}
|
||||
>
|
||||
{IconComponent}
|
||||
{typeof children === 'function' ? children({ disabled, active }) : children}
|
||||
</button>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Menu.Item>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
export default DropdownItem;
|
||||
export { DropdownItem };
|
||||
|
|
|
@ -119,14 +119,14 @@ export default ({ database, className }: Props) => {
|
|||
<Can action={'database.view_password'}>
|
||||
<div css={tw`mt-6`}>
|
||||
<Label>Password</Label>
|
||||
<CopyOnClick text={database.password}>
|
||||
<CopyOnClick text={database.password} showInNotification={false}>
|
||||
<Input type={'text'} readOnly value={database.password} />
|
||||
</CopyOnClick>
|
||||
</div>
|
||||
</Can>
|
||||
<div css={tw`mt-6`}>
|
||||
<Label>JDBC Connection String</Label>
|
||||
<CopyOnClick text={jdbcConnectionString}>
|
||||
<CopyOnClick text={jdbcConnectionString} showInNotification={false}>
|
||||
<Input type={'text'} readOnly value={jdbcConnectionString} />
|
||||
</CopyOnClick>
|
||||
</div>
|
||||
|
|
|
@ -18,11 +18,12 @@ import { ServerContext } from '@/state/server';
|
|||
import styles from './style.module.css';
|
||||
|
||||
function Clickable({ file, children }: { file: FileObject; children: ReactNode }) {
|
||||
const [canRead] = usePermissions(['file.read']);
|
||||
const [canReadContents] = usePermissions(['file.read-content']);
|
||||
const id = ServerContext.useStoreState(state => state.server.data!.id);
|
||||
const directory = ServerContext.useStoreState(state => state.files.directory);
|
||||
|
||||
return !canReadContents || (file.isFile && !file.isEditable()) ? (
|
||||
return (file.isFile && (!file.isEditable() || !canReadContents)) || (!file.isFile && !canRead) ? (
|
||||
<div className={styles.details}>{children}</div>
|
||||
) : (
|
||||
<NavLink
|
||||
|
|
|
@ -93,7 +93,7 @@ const MassActionsBar = () => {
|
|||
/>
|
||||
)}
|
||||
<Portal>
|
||||
<div className="fixed bottom-0 z-50 mb-6 flex w-full justify-center">
|
||||
<div className="pointer-events-none fixed bottom-0 z-50 mb-6 flex w-full justify-center">
|
||||
<FadeTransition duration="duration-75" show={selectedFiles.length > 0} appear unmount>
|
||||
<div className="pointer-events-auto flex items-center space-x-4 rounded bg-black/50 p-4">
|
||||
<Button onClick={() => setShowMove(true)}>Move</Button>
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import type { ComponentType } from 'react';
|
||||
import { lazy } from 'react';
|
||||
|
||||
import ServerConsole from '@/components/server/console/ServerConsoleContainer';
|
||||
import DatabasesContainer from '@/components/server/databases/DatabasesContainer';
|
||||
import ScheduleContainer from '@/components/server/schedules/ScheduleContainer';
|
||||
import UsersContainer from '@/components/server/users/UsersContainer';
|
||||
|
@ -21,6 +20,7 @@ import ServerActivityLogContainer from '@/components/server/ServerActivityLogCon
|
|||
//
|
||||
// These specific lazy loaded routes are to avoid loading in heavy screens
|
||||
// for the server dashboard when they're only needed for specific instances.
|
||||
const ServerConsoleContainer = lazy(() => import('@/components/server/console/ServerConsoleContainer'));
|
||||
const FileEditContainer = lazy(() => import('@/components/server/files/FileEditContainer'));
|
||||
const ScheduleEditContainer = lazy(() => import('@/components/server/schedules/ScheduleEditContainer'));
|
||||
|
||||
|
@ -86,7 +86,7 @@ export default {
|
|||
path: '',
|
||||
permission: null,
|
||||
name: 'Console',
|
||||
component: ServerConsole,
|
||||
component: ServerConsoleContainer,
|
||||
end: true,
|
||||
},
|
||||
{
|
||||
|
|
|
@ -9,7 +9,13 @@ with pkgs;
|
|||
alejandra
|
||||
composer
|
||||
nodejs-18_x
|
||||
nodePackages.yarn
|
||||
nodePackages.pnpm
|
||||
php81WithExtensions
|
||||
|
||||
docker-compose
|
||||
];
|
||||
|
||||
shellHook = ''
|
||||
PATH="$PATH:${pkgs.docker-compose}/libexec/docker/cli-plugins"
|
||||
'';
|
||||
}
|
||||
|
|
|
@ -255,7 +255,7 @@ class UserControllerTest extends ApplicationApiIntegrationTestCase
|
|||
* Endpoints that should return a 403 error when the key does not have write
|
||||
* permissions for user management.
|
||||
*/
|
||||
public function userWriteEndpointsDataProvider(): array
|
||||
public static function userWriteEndpointsDataProvider(): array
|
||||
{
|
||||
return [
|
||||
['postJson', '/api/application/users'],
|
||||
|
|
|
@ -241,7 +241,7 @@ class ApiKeyControllerTest extends ClientApiIntegrationTestCase
|
|||
* Provides some different IP address combinations that can be used when
|
||||
* testing that we accept the expected IP values.
|
||||
*/
|
||||
public function validIPAddressDataProvider(): array
|
||||
public static function validIPAddressDataProvider(): array
|
||||
{
|
||||
return [
|
||||
[[]],
|
||||
|
|
|
@ -331,7 +331,7 @@ class ClientControllerTest extends ClientApiIntegrationTestCase
|
|||
$response->assertJsonPath('data.0.attributes.relationships.allocations.data.0.attributes.notes', null);
|
||||
}
|
||||
|
||||
public function filterTypeDataProvider(): array
|
||||
public static function filterTypeDataProvider(): array
|
||||
{
|
||||
return [['admin'], ['admin-all']];
|
||||
}
|
||||
|
|
|
@ -46,7 +46,7 @@ class AllocationAuthorizationTest extends ClientApiIntegrationTestCase
|
|||
$this->actingAs($user)->json($method, $this->link($server3, '/network/allocations/' . $allocation3->id . $endpoint))->assertNotFound();
|
||||
}
|
||||
|
||||
public function methodDataProvider(): array
|
||||
public static function methodDataProvider(): array
|
||||
{
|
||||
return [
|
||||
['POST', ''],
|
||||
|
|
|
@ -86,7 +86,7 @@ class CreateNewAllocationTest extends ClientApiIntegrationTestCase
|
|||
->assertJsonPath('errors.0.detail', 'Cannot assign additional allocations to this server: limit has been reached.');
|
||||
}
|
||||
|
||||
public function permissionDataProvider(): array
|
||||
public static function permissionDataProvider(): array
|
||||
{
|
||||
return [[[Permission::ACTION_ALLOCATION_CREATE]], [[]]];
|
||||
}
|
||||
|
|
|
@ -98,10 +98,7 @@ class DeleteAllocationTest extends ClientApiIntegrationTestCase
|
|||
$this->actingAs($user)->deleteJson($this->link($server, "/network/allocations/{$server2->allocation_id}"))->assertNotFound();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function permissionDataProvider()
|
||||
public static function permissionDataProvider(): array
|
||||
{
|
||||
return [[[Permission::ACTION_ALLOCATION_DELETE]], [[]]];
|
||||
}
|
||||
|
|
|
@ -54,7 +54,7 @@ class BackupAuthorizationTest extends ClientApiIntegrationTestCase
|
|||
$this->actingAs($user)->json($method, $this->link($server3, '/backups/' . $backup3->uuid . $endpoint))->assertNotFound();
|
||||
}
|
||||
|
||||
public function methodDataProvider(): array
|
||||
public static function methodDataProvider(): array
|
||||
{
|
||||
return [
|
||||
['GET', ''],
|
||||
|
|
|
@ -58,7 +58,7 @@ class DatabaseAuthorizationTest extends ClientApiIntegrationTestCase
|
|||
$this->actingAs($user)->json($method, $this->link($server3, '/databases/' . $hashids->encode($database3->id) . $endpoint))->assertNotFound();
|
||||
}
|
||||
|
||||
public function methodDataProvider(): array
|
||||
public static function methodDataProvider(): array
|
||||
{
|
||||
return [
|
||||
['POST', '/rotate-password'],
|
||||
|
|
|
@ -133,7 +133,7 @@ class NetworkAllocationControllerTest extends ClientApiIntegrationTestCase
|
|||
->assertForbidden();
|
||||
}
|
||||
|
||||
public function updatePermissionsDataProvider(): array
|
||||
public static function updatePermissionsDataProvider(): array
|
||||
{
|
||||
return [[[]], [[Permission::ACTION_ALLOCATION_UPDATE]]];
|
||||
}
|
||||
|
|
|
@ -72,7 +72,7 @@ class PowerControllerTest extends ClientApiIntegrationTestCase
|
|||
/**
|
||||
* Returns invalid permission combinations for a given power action.
|
||||
*/
|
||||
public function invalidPermissionDataProvider(): array
|
||||
public static function invalidPermissionDataProvider(): array
|
||||
{
|
||||
return [
|
||||
['start', [Permission::ACTION_CONTROL_STOP, Permission::ACTION_CONTROL_RESTART]],
|
||||
|
@ -83,7 +83,7 @@ class PowerControllerTest extends ClientApiIntegrationTestCase
|
|||
];
|
||||
}
|
||||
|
||||
public function validPowerActionDataProvider(): array
|
||||
public static function validPowerActionDataProvider(): array
|
||||
{
|
||||
return [
|
||||
['start', Permission::ACTION_CONTROL_START],
|
||||
|
|
|
@ -89,7 +89,7 @@ class CreateServerScheduleTest extends ClientApiIntegrationTestCase
|
|||
->assertForbidden();
|
||||
}
|
||||
|
||||
public function permissionsDataProvider(): array
|
||||
public static function permissionsDataProvider(): array
|
||||
{
|
||||
return [[[]], [[Permission::ACTION_SCHEDULE_CREATE]]];
|
||||
}
|
||||
|
|
|
@ -77,7 +77,7 @@ class DeleteServerScheduleTest extends ClientApiIntegrationTestCase
|
|||
$this->assertDatabaseHas('schedules', ['id' => $schedule->id]);
|
||||
}
|
||||
|
||||
public function permissionsDataProvider(): array
|
||||
public static function permissionsDataProvider(): array
|
||||
{
|
||||
return [[[]], [[Permission::ACTION_SCHEDULE_DELETE]]];
|
||||
}
|
||||
|
|
|
@ -64,7 +64,7 @@ class ExecuteScheduleTest extends ClientApiIntegrationTestCase
|
|||
$this->actingAs($user)->postJson($this->link($schedule, '/execute'))->assertForbidden();
|
||||
}
|
||||
|
||||
public function permissionsDataProvider(): array
|
||||
public static function permissionsDataProvider(): array
|
||||
{
|
||||
return [[[]], [[Permission::ACTION_SCHEDULE_UPDATE]]];
|
||||
}
|
||||
|
|
|
@ -89,7 +89,7 @@ class GetServerSchedulesTest extends ClientApiIntegrationTestCase
|
|||
->assertForbidden();
|
||||
}
|
||||
|
||||
public function permissionsDataProvider(): array
|
||||
public static function permissionsDataProvider(): array
|
||||
{
|
||||
return [
|
||||
[[], false],
|
||||
|
|
|
@ -54,7 +54,7 @@ class ScheduleAuthorizationTest extends ClientApiIntegrationTestCase
|
|||
$this->actingAs($user)->json($method, $this->link($server3, '/schedules/' . $schedule3->id . $endpoint))->assertNotFound();
|
||||
}
|
||||
|
||||
public function methodDataProvider(): array
|
||||
public static function methodDataProvider(): array
|
||||
{
|
||||
return [
|
||||
['GET', ''],
|
||||
|
|
|
@ -109,7 +109,7 @@ class UpdateServerScheduleTest extends ClientApiIntegrationTestCase
|
|||
$this->assertFalse($schedule->is_processing);
|
||||
}
|
||||
|
||||
public function permissionsDataProvider(): array
|
||||
public static function permissionsDataProvider(): array
|
||||
{
|
||||
return [[[]], [[Permission::ACTION_SCHEDULE_UPDATE]]];
|
||||
}
|
||||
|
|
|
@ -170,7 +170,7 @@ class CreateServerScheduleTaskTest extends ClientApiIntegrationTestCase
|
|||
->assertForbidden();
|
||||
}
|
||||
|
||||
public function permissionsDataProvider(): array
|
||||
public static function permissionsDataProvider(): array
|
||||
{
|
||||
return [[[]], [[Permission::ACTION_SCHEDULE_UPDATE]]];
|
||||
}
|
||||
|
|
|
@ -112,12 +112,12 @@ class SettingsControllerTest extends ClientApiIntegrationTestCase
|
|||
$this->assertTrue($server->isInstalled());
|
||||
}
|
||||
|
||||
public function renamePermissionsDataProvider(): array
|
||||
public static function renamePermissionsDataProvider(): array
|
||||
{
|
||||
return [[[]], [[Permission::ACTION_SETTINGS_RENAME]]];
|
||||
}
|
||||
|
||||
public function reinstallPermissionsDataProvider(): array
|
||||
public static function reinstallPermissionsDataProvider(): array
|
||||
{
|
||||
return [[[]], [[Permission::ACTION_SETTINGS_REINSTALL]]];
|
||||
}
|
||||
|
|
|
@ -58,7 +58,7 @@ class GetStartupAndVariablesTest extends ClientApiIntegrationTestCase
|
|||
$this->actingAs($user2)->getJson($this->link($server) . '/startup')->assertNotFound();
|
||||
}
|
||||
|
||||
public function permissionsDataProvider(): array
|
||||
public static function permissionsDataProvider(): array
|
||||
{
|
||||
return [[[]], [[Permission::ACTION_STARTUP_READ]]];
|
||||
}
|
||||
|
|
|
@ -149,7 +149,7 @@ class UpdateStartupVariableTest extends ClientApiIntegrationTestCase
|
|||
$this->actingAs($user2)->putJson($this->link($server) . '/startup/variable')->assertNotFound();
|
||||
}
|
||||
|
||||
public function permissionsDataProvider(): array
|
||||
public static function permissionsDataProvider(): array
|
||||
{
|
||||
return [[[]], [[Permission::ACTION_STARTUP_UPDATE]]];
|
||||
}
|
||||
|
|
|
@ -155,7 +155,7 @@ class CreateServerSubuserTest extends ClientApiIntegrationTestCase
|
|||
$response->assertJsonPath('errors.0.detail', 'A user with that email address is already assigned as a subuser for this server.');
|
||||
}
|
||||
|
||||
public function permissionsDataProvider(): array
|
||||
public static function permissionsDataProvider(): array
|
||||
{
|
||||
return [[[]], [[Permission::ACTION_USER_CREATE]]];
|
||||
}
|
||||
|
|
|
@ -49,7 +49,7 @@ class SubuserAuthorizationTest extends ClientApiIntegrationTestCase
|
|||
$this->actingAs($user)->json($method, $this->link($server3, '/users/' . $internal->uuid))->assertNotFound();
|
||||
}
|
||||
|
||||
public function methodDataProvider(): array
|
||||
public static function methodDataProvider(): array
|
||||
{
|
||||
return [['GET'], ['POST'], ['DELETE']];
|
||||
}
|
||||
|
|
|
@ -213,7 +213,7 @@ class SftpAuthenticationControllerTest extends IntegrationTestCase
|
|||
$this->post('/api/remote/sftp/auth', $data)->assertForbidden();
|
||||
}
|
||||
|
||||
public function authorizationTypeDataProvider(): array
|
||||
public static function authorizationTypeDataProvider(): array
|
||||
{
|
||||
return [
|
||||
'password auth' => ['password'],
|
||||
|
@ -221,7 +221,7 @@ class SftpAuthenticationControllerTest extends IntegrationTestCase
|
|||
];
|
||||
}
|
||||
|
||||
public function serverStateDataProvider(): array
|
||||
public static function serverStateDataProvider(): array
|
||||
{
|
||||
return [
|
||||
'installing' => [Server::STATUS_INSTALLING],
|
||||
|
|
|
@ -37,7 +37,7 @@ class RunTaskJobTest extends IntegrationTestCase
|
|||
|
||||
$job = new RunTaskJob($task);
|
||||
|
||||
Bus::dispatchNow($job);
|
||||
Bus::dispatchSync($job);
|
||||
|
||||
$task->refresh();
|
||||
$schedule->refresh();
|
||||
|
@ -61,7 +61,7 @@ class RunTaskJobTest extends IntegrationTestCase
|
|||
|
||||
$this->expectException(\InvalidArgumentException::class);
|
||||
$this->expectExceptionMessage('Invalid task action provided: foobar');
|
||||
Bus::dispatchNow($job);
|
||||
Bus::dispatchSync($job);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -95,7 +95,7 @@ class RunTaskJobTest extends IntegrationTestCase
|
|||
}))->andReturnSelf();
|
||||
$mock->expects('send')->with('start')->andReturn(new Response());
|
||||
|
||||
Bus::dispatchNow(new RunTaskJob($task, $isManualRun));
|
||||
Bus::dispatchSync(new RunTaskJob($task, $isManualRun));
|
||||
|
||||
$task->refresh();
|
||||
$schedule->refresh();
|
||||
|
@ -133,7 +133,7 @@ class RunTaskJobTest extends IntegrationTestCase
|
|||
$this->expectException(DaemonConnectionException::class);
|
||||
}
|
||||
|
||||
Bus::dispatchNow(new RunTaskJob($task));
|
||||
Bus::dispatchSync(new RunTaskJob($task));
|
||||
|
||||
if ($continueOnFailure) {
|
||||
$task->refresh();
|
||||
|
@ -165,7 +165,7 @@ class RunTaskJobTest extends IntegrationTestCase
|
|||
'payload' => 'start',
|
||||
]);
|
||||
|
||||
Bus::dispatchNow(new RunTaskJob($task));
|
||||
Bus::dispatchSync(new RunTaskJob($task));
|
||||
|
||||
$task->refresh();
|
||||
$schedule->refresh();
|
||||
|
@ -175,7 +175,7 @@ class RunTaskJobTest extends IntegrationTestCase
|
|||
$this->assertTrue(Carbon::now()->isSameAs(\DateTimeInterface::ATOM, $schedule->last_run_at));
|
||||
}
|
||||
|
||||
public function isManualRunDataProvider(): array
|
||||
public static function isManualRunDataProvider(): array
|
||||
{
|
||||
return [[true], [false]];
|
||||
}
|
||||
|
|
|
@ -194,7 +194,7 @@ class DatabaseManagementServiceTest extends IntegrationTestCase
|
|||
$this->assertDatabaseMissing('databases', ['server_id' => $server->id]);
|
||||
}
|
||||
|
||||
public function invalidDataDataProvider(): array
|
||||
public static function invalidDataDataProvider(): array
|
||||
{
|
||||
return [
|
||||
[[]],
|
||||
|
|
|
@ -141,7 +141,7 @@ class DeployServerDatabaseServiceTest extends IntegrationTestCase
|
|||
$this->assertInstanceOf(Database::class, $response);
|
||||
}
|
||||
|
||||
public function invalidDataProvider(): array
|
||||
public static function invalidDataProvider(): array
|
||||
{
|
||||
return [
|
||||
[['remote' => '%']],
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue