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
|
REDIS_PORT=6379
|
||||||
|
|
||||||
CACHE_DRIVER=file
|
CACHE_DRIVER=file
|
||||||
QUEUE_CONNECTION=sync
|
QUEUE_CONNECTION=redis
|
||||||
SESSION_DRIVER=file
|
SESSION_DRIVER=file
|
||||||
|
|
||||||
HASHIDS_SALT=
|
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.
|
Run the following command to collect logs on your system.
|
||||||
|
|
||||||
Wings: `sudo wings diagnostics`
|
Wings: `sudo wings diagnostics`
|
||||||
Panel: `tail -n 100 /var/www/pterodactyl/storage/logs/laravel-$(date +%F).log | nc bin.ptdl.co 99`
|
Panel: `tail -n 150 /var/www/pterodactyl/storage/logs/laravel-$(date +%F).log | nc pteropaste.com 99`
|
||||||
placeholder: "https://bin.ptdl.co/a1h6z"
|
placeholder: "https://pteropaste.com/a1h6z"
|
||||||
render: bash
|
render: bash
|
||||||
validations:
|
validations:
|
||||||
required: false
|
required: false
|
||||||
|
|
16
.github/workflows/docker.yaml
vendored
16
.github/workflows/docker.yaml
vendored
|
@ -16,14 +16,13 @@ on:
|
||||||
jobs:
|
jobs:
|
||||||
push:
|
push:
|
||||||
name: Push
|
name: Push
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-22.04
|
||||||
if: "!contains(github.ref, 'develop') || (!contains(github.event.head_commit.message, 'skip docker') && !contains(github.event.head_commit.message, 'docker skip'))"
|
|
||||||
steps:
|
steps:
|
||||||
- name: Code checkout
|
- name: Code checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Docker metadata
|
- name: Fetch metadata
|
||||||
id: docker_meta
|
id: metadata
|
||||||
uses: docker/metadata-action@v4
|
uses: docker/metadata-action@v4
|
||||||
with:
|
with:
|
||||||
images: ghcr.io/pterodactyl/panel
|
images: ghcr.io/pterodactyl/panel
|
||||||
|
@ -42,6 +41,7 @@ jobs:
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@v2
|
uses: docker/login-action@v2
|
||||||
|
if: "github.event_name != 'pull_request'"
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
|
@ -55,13 +55,13 @@ jobs:
|
||||||
sed -i "s/ 'version' => 'canary',/ 'version' => '${REF:1}',/" config/app.php
|
sed -i "s/ 'version' => 'canary',/ 'version' => '${REF:1}',/" config/app.php
|
||||||
|
|
||||||
- name: Build and Push
|
- name: Build and Push
|
||||||
uses: docker/build-push-action@v3
|
uses: docker/build-push-action@v4
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: ./Containerfile
|
file: ./Containerfile
|
||||||
push: ${{ github.event_name != 'pull_request' }}
|
push: ${{ github.event_name != 'pull_request' }}
|
||||||
platforms: linux/amd64 #,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
labels: ${{ steps.docker_meta.outputs.labels }}
|
labels: ${{ steps.metadata.outputs.labels }}
|
||||||
tags: ${{ steps.docker_meta.outputs.tags }}
|
tags: ${{ steps.metadata.outputs.tags }}
|
||||||
cache-from: type=gha
|
cache-from: type=gha
|
||||||
cache-to: type=gha,mode=max
|
cache-to: type=gha,mode=max
|
||||||
|
|
13
.github/workflows/laravel.yaml
vendored
13
.github/workflows/laravel.yaml
vendored
|
@ -13,7 +13,7 @@ on:
|
||||||
jobs:
|
jobs:
|
||||||
analysis:
|
analysis:
|
||||||
name: Static Analysis
|
name: Static Analysis
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-22.04
|
||||||
env:
|
env:
|
||||||
APP_ENV: testing
|
APP_ENV: testing
|
||||||
APP_DEBUG: "true"
|
APP_DEBUG: "true"
|
||||||
|
@ -42,7 +42,7 @@ jobs:
|
||||||
|
|
||||||
lint:
|
lint:
|
||||||
name: Lint
|
name: Lint
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- name: Code checkout
|
- name: Code checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
|
@ -50,6 +50,7 @@ jobs:
|
||||||
- name: Setup PHP
|
- name: Setup PHP
|
||||||
uses: shivammathur/setup-php@v2
|
uses: shivammathur/setup-php@v2
|
||||||
with:
|
with:
|
||||||
|
# TODO: Update to 8.2 once php-cs-fixer supports it
|
||||||
php-version: 8.1
|
php-version: 8.1
|
||||||
extensions: bcmath, curl, gd, mbstring, mysql, openssl, pdo, tokenizer, xml, zip
|
extensions: bcmath, curl, gd, mbstring, mysql, openssl, pdo, tokenizer, xml, zip
|
||||||
tools: composer:v2
|
tools: composer:v2
|
||||||
|
@ -63,11 +64,11 @@ jobs:
|
||||||
|
|
||||||
mysql:
|
mysql:
|
||||||
name: Tests
|
name: Tests
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-22.04
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
php: [8.1]
|
php: [8.1, 8.2]
|
||||||
database: ["mariadb:10.2", "mariadb:10.9", "mysql:8"]
|
database: ["mariadb:10.2", "mariadb:10.9", "mysql:8"]
|
||||||
services:
|
services:
|
||||||
database:
|
database:
|
||||||
|
@ -135,12 +136,12 @@ jobs:
|
||||||
|
|
||||||
postgres:
|
postgres:
|
||||||
name: Tests
|
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')"
|
if: "!contains(github.event.head_commit.message, 'skip ci') && !contains(github.event.head_commit.message, 'ci skip')"
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
php: [8.1]
|
php: [8.1, 8.2]
|
||||||
database: ["postgres:13", "postgres:14", "postgres:15"]
|
database: ["postgres:13", "postgres:14", "postgres:15"]
|
||||||
services:
|
services:
|
||||||
database:
|
database:
|
||||||
|
|
11
.github/workflows/release.yaml
vendored
11
.github/workflows/release.yaml
vendored
|
@ -8,22 +8,25 @@ on:
|
||||||
jobs:
|
jobs:
|
||||||
release:
|
release:
|
||||||
name: Release
|
name: Release
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- name: Code checkout
|
- name: Code checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Install pnpm
|
||||||
|
uses: pnpm/action-setup@v2
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: 18
|
node-version: 18
|
||||||
cache: yarn
|
cache: pnpm
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: yarn install --frozen-lockfile
|
run: pnpm install
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: yarn build
|
run: pnpm run build
|
||||||
|
|
||||||
- name: Create release branch and bump version
|
- name: Create release branch and bump version
|
||||||
env:
|
env:
|
||||||
|
|
24
.github/workflows/ui.yaml
vendored
24
.github/workflows/ui.yaml
vendored
|
@ -13,26 +13,29 @@ on:
|
||||||
jobs:
|
jobs:
|
||||||
lint:
|
lint:
|
||||||
name: Lint
|
name: Lint
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- name: Code checkout
|
- name: Code checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Install pnpm
|
||||||
|
uses: pnpm/action-setup@v2
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: 18
|
node-version: 18
|
||||||
cache: yarn
|
cache: pnpm
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: yarn install --frozen-lockfile
|
run: pnpm install
|
||||||
|
|
||||||
- name: Lint
|
- name: Lint
|
||||||
run: yarn run lint
|
run: pnpm run lint
|
||||||
|
|
||||||
tests:
|
tests:
|
||||||
name: Tests
|
name: Tests
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-22.04
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
|
@ -41,17 +44,20 @@ jobs:
|
||||||
- name: Code checkout
|
- name: Code checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Install pnpm
|
||||||
|
uses: pnpm/action-setup@v2
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node }}
|
node-version: ${{ matrix.node }}
|
||||||
cache: yarn
|
cache: pnpm
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: yarn install --frozen-lockfile
|
run: pnpm install
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: yarn run build
|
run: pnpm run build
|
||||||
|
|
||||||
- name: Tests
|
- 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.
|
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
|
## v1.11.2
|
||||||
### Changed
|
### Changed
|
||||||
* Telemetry no longer sends a map of Egg and Nest UUIDs to the number of servers using them.
|
* 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
|
# 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
|
WORKDIR /var/www/pterodactyl
|
||||||
|
|
||||||
COPY --chown=1001:0 public ./public
|
COPY --chown=1001:0 public ./public
|
||||||
COPY --chown=1001:0 resources/scripts ./resources/scripts
|
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 \
|
RUN /opt/app-root/src/.npm-global/bin/pnpm install \
|
||||||
&& /opt/app-root/src/.npm-global/bin/yarn build \
|
&& /opt/app-root/src/.npm-global/bin/pnpm build \
|
||||||
&& rm -rf resources/scripts .eslintignore .eslintrc.yml .yarnrc.yml package.json tailwind.config.js tsconfig.json vite.config.ts yarn.lock node_modules
|
&& 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 app ./app
|
||||||
COPY --chown=1001:0 bootstrap ./bootstrap
|
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 \
|
&& rpm --install https://rpms.remirepo.net/enterprise/remi-release-9.rpm \
|
||||||
&& microdnf update -y \
|
&& microdnf update -y \
|
||||||
&& microdnf install -y ca-certificates shadow-utils tar tzdata unzip wget \
|
&& 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 reset php \
|
||||||
&& microdnf module -y enable php:remi-8.1 \
|
&& microdnf module -y enable php:remi-8.2 \
|
||||||
&& 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 \
|
&& 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 \
|
||||||
&& curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer \
|
|
||||||
&& rm /etc/php-fpm.d/www.conf \
|
&& rm /etc/php-fpm.d/www.conf \
|
||||||
&& useradd --home-dir /var/lib/caddy --create-home caddy \
|
&& useradd --home-dir /var/lib/caddy --create-home caddy \
|
||||||
&& mkdir /etc/caddy \
|
&& mkdir /etc/caddy \
|
||||||
|
@ -66,7 +67,7 @@ RUN composer install --no-dev --optimize-autoloader \
|
||||||
&& rm -rf bootstrap/cache/*.php \
|
&& rm -rf bootstrap/cache/*.php \
|
||||||
&& rm -rf .env storage/logs/*.log
|
&& 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/Caddyfile /etc/caddy/Caddyfile
|
||||||
COPY .github/docker/php-fpm.conf /etc/php-fpm.conf
|
COPY .github/docker/php-fpm.conf /etc/php-fpm.conf
|
||||||
COPY .github/docker/supervisord.conf /etc/supervisord.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
|
## 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)
|
[Interested in becoming a sponsor?](https://github.com/sponsors/matthewpi)
|
||||||
|
|
||||||
| Company | About |
|
| Company | About |
|
||||||
|-----------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|-----------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
| [**WISP**](https://wisp.gg) | Extra features. |
|
| [**WISP**](https://wisp.gg) | Extra features. |
|
||||||
| [**RocketNode**](https://rocketnode.com/) | Innovative game server hosting combined with a straightforward control panel, affordable prices, and Rocket-Fast support. |
|
|
||||||
| [**Aussie Server Hosts**](https://aussieserverhosts.com/) | No frills Australian Owned and operated High Performance Server hosting for some of the most demanding games serving Australia and New Zealand. |
|
| [**Aussie Server Hosts**](https://aussieserverhosts.com/) | No frills Australian Owned and operated High Performance Server hosting for some of the most demanding games serving Australia and New Zealand. |
|
||||||
|
| [**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. |
|
| [**BisectHosting**](https://www.bisecthosting.com/) | BisectHosting provides Minecraft, Valheim and other server hosting services with the highest reliability and lightning fast support since 2012. |
|
||||||
| [**MineStrator**](https://minestrator.com/) | Looking for the most highend French hosting company for your minecraft server? More than 24,000 members on our discord trust us. Give us a try! |
|
| [**MineStrator**](https://minestrator.com/) | Looking for the most highend French hosting company for your minecraft server? More than 24,000 members on our discord trust us. Give us a try! |
|
||||||
| [**Skynode**](https://www.skynode.pro/) | Skynode provides blazing fast game servers along with a top-notch user experience. Whatever our clients are looking for, we're able to provide it! |
|
|
||||||
| [**VibeGAMES**](https://vibegames.net/) | VibeGAMES is a game server provider that specializes in DDOS protection for the games we offer. We have multiple locations in the US, Brazil, France, Germany, Singapore, Australia and South Africa. |
|
| [**VibeGAMES**](https://vibegames.net/) | VibeGAMES is a game server provider that specializes in DDOS protection for the games we offer. We have multiple locations in the US, Brazil, France, Germany, Singapore, Australia and South Africa. |
|
||||||
| [**Pterodactyl Market**](https://pterodactylmarket.com/) | Pterodactyl Market is a one-and-stop shop for Pterodactyl. In our market, you can find Add-ons, Themes, Eggs, and more for Pterodactyl. |
|
| [**DutchIS**](https://dutchis.net?ref=pterodactyl) | DutchIS provides instant infrastructure such as pay per use VPS hosting. Start your game hosting journey on DutchIS. |
|
||||||
| [**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. |
|
| [**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. |
|
||||||
| [**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. | |
|
| [**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
|
### Supported Games
|
||||||
|
|
||||||
|
|
|
@ -44,7 +44,7 @@ class EmailSettingsCommand extends Command
|
||||||
trans('command/messages.environment.mail.ask_driver'),
|
trans('command/messages.environment.mail.ask_driver'),
|
||||||
[
|
[
|
||||||
'smtp' => 'SMTP Server',
|
'smtp' => 'SMTP Server',
|
||||||
'mail' => 'PHP\'s Internal Mail Function',
|
'sendmail' => 'sendmail Binary',
|
||||||
'mailgun' => 'Mailgun Transactional Email',
|
'mailgun' => 'Mailgun Transactional Email',
|
||||||
'mandrill' => 'Mandrill Transactional Email',
|
'mandrill' => 'Mandrill Transactional Email',
|
||||||
'postmark' => 'Postmark Transactional Email',
|
'postmark' => 'Postmark Transactional Email',
|
||||||
|
|
|
@ -10,7 +10,7 @@ class PruneOrphanedBackupsCommand extends Command
|
||||||
{
|
{
|
||||||
protected $signature = 'p:maintenance:prune-backups {--prune-age=}';
|
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.
|
* PruneOrphanedBackupsCommand constructor.
|
||||||
|
@ -38,7 +38,7 @@ class PruneOrphanedBackupsCommand extends Command
|
||||||
return;
|
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([
|
$query->update([
|
||||||
'is_successful' => false,
|
'is_successful' => false,
|
||||||
|
|
|
@ -18,7 +18,7 @@ class Kernel extends ConsoleKernel
|
||||||
/**
|
/**
|
||||||
* Register the commands for the application.
|
* Register the commands for the application.
|
||||||
*/
|
*/
|
||||||
protected function commands()
|
protected function commands(): void
|
||||||
{
|
{
|
||||||
$this->load(__DIR__ . '/Commands');
|
$this->load(__DIR__ . '/Commands');
|
||||||
}
|
}
|
||||||
|
@ -26,8 +26,11 @@ class Kernel extends ConsoleKernel
|
||||||
/**
|
/**
|
||||||
* Define the application's command schedule.
|
* 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.
|
// Execute scheduled commands for servers every minute, as if there was a normal cron running.
|
||||||
$schedule->command(ProcessRunnableCommand::class)->everyMinute()->withoutOverlapping();
|
$schedule->command(ProcessRunnableCommand::class)->everyMinute()->withoutOverlapping();
|
||||||
$schedule->command(CleanServiceBackupFilesCommand::class)->daily();
|
$schedule->command(CleanServiceBackupFilesCommand::class)->daily();
|
||||||
|
|
|
@ -73,7 +73,7 @@ final class Handler extends ExceptionHandler
|
||||||
*
|
*
|
||||||
* @noinspection PhpUnusedLocalVariableInspection
|
* @noinspection PhpUnusedLocalVariableInspection
|
||||||
*/
|
*/
|
||||||
public function register()
|
public function register(): void
|
||||||
{
|
{
|
||||||
if (config('app.exceptions.report_all', false)) {
|
if (config('app.exceptions.report_all', false)) {
|
||||||
$this->dontReport = [];
|
$this->dontReport = [];
|
||||||
|
|
|
@ -15,8 +15,6 @@ final class Time
|
||||||
*/
|
*/
|
||||||
public static function getMySQLTimezoneOffset(string $timezone): string
|
public static function getMySQLTimezoneOffset(string $timezone): string
|
||||||
{
|
{
|
||||||
$offset = round(CarbonImmutable::now($timezone)->getTimezone()->getOffset(CarbonImmutable::now('UTC')) / 3600);
|
return CarbonImmutable::now($timezone)->getTimezone()->toOffsetName();
|
||||||
|
|
||||||
return sprintf('%s%s:00', $offset > 0 ? '+' : '-', str_pad((string) abs($offset), 2, '0', STR_PAD_LEFT));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,8 +27,6 @@ class EggController extends ApplicationApiController
|
||||||
public function __construct(private EggExporterService $eggExporterService)
|
public function __construct(private EggExporterService $eggExporterService)
|
||||||
{
|
{
|
||||||
parent::__construct();
|
parent::__construct();
|
||||||
|
|
||||||
$this->eggExporterService = $eggExporterService;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -18,6 +18,7 @@ use Pterodactyl\Transformers\Api\Client\BackupTransformer;
|
||||||
use Pterodactyl\Http\Controllers\Api\Client\ClientApiController;
|
use Pterodactyl\Http\Controllers\Api\Client\ClientApiController;
|
||||||
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
||||||
use Pterodactyl\Http\Requests\Api\Client\Servers\Backups\StoreBackupRequest;
|
use Pterodactyl\Http\Requests\Api\Client\Servers\Backups\StoreBackupRequest;
|
||||||
|
use Pterodactyl\Http\Requests\Api\Client\Servers\Backups\RestoreBackupRequest;
|
||||||
|
|
||||||
class BackupController extends ClientApiController
|
class BackupController extends ClientApiController
|
||||||
{
|
{
|
||||||
|
@ -188,12 +189,8 @@ class BackupController extends ClientApiController
|
||||||
*
|
*
|
||||||
* @throws \Throwable
|
* @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
|
// Cannot restore a backup unless a server is fully installed and not currently
|
||||||
// processing a different backup restoration request.
|
// processing a different backup restoration request.
|
||||||
if (!is_null($server->status)) {
|
if (!is_null($server->status)) {
|
||||||
|
|
|
@ -9,6 +9,7 @@ use Pterodactyl\Models\Schedule;
|
||||||
use Illuminate\Http\JsonResponse;
|
use Illuminate\Http\JsonResponse;
|
||||||
use Pterodactyl\Facades\Activity;
|
use Pterodactyl\Facades\Activity;
|
||||||
use Pterodactyl\Models\Permission;
|
use Pterodactyl\Models\Permission;
|
||||||
|
use Illuminate\Database\ConnectionInterface;
|
||||||
use Pterodactyl\Repositories\Eloquent\TaskRepository;
|
use Pterodactyl\Repositories\Eloquent\TaskRepository;
|
||||||
use Pterodactyl\Exceptions\Http\HttpForbiddenException;
|
use Pterodactyl\Exceptions\Http\HttpForbiddenException;
|
||||||
use Pterodactyl\Transformers\Api\Client\TaskTransformer;
|
use Pterodactyl\Transformers\Api\Client\TaskTransformer;
|
||||||
|
@ -23,8 +24,10 @@ class ScheduleTaskController extends ClientApiController
|
||||||
/**
|
/**
|
||||||
* ScheduleTaskController constructor.
|
* ScheduleTaskController constructor.
|
||||||
*/
|
*/
|
||||||
public function __construct(private TaskRepository $repository)
|
public function __construct(
|
||||||
{
|
private ConnectionInterface $connection,
|
||||||
|
private TaskRepository $repository
|
||||||
|
) {
|
||||||
parent::__construct();
|
parent::__construct();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,14 +52,35 @@ class ScheduleTaskController extends ClientApiController
|
||||||
$lastTask = $schedule->tasks()->orderByDesc('sequence_id')->first();
|
$lastTask = $schedule->tasks()->orderByDesc('sequence_id')->first();
|
||||||
|
|
||||||
/** @var \Pterodactyl\Models\Task $task */
|
/** @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,
|
'schedule_id' => $schedule->id,
|
||||||
'sequence_id' => ($lastTask->sequence_id ?? 0) + 1,
|
'sequence_id' => $sequenceId,
|
||||||
'action' => $request->input('action'),
|
'action' => $request->input('action'),
|
||||||
'payload' => $request->input('payload') ?? '',
|
'payload' => $request->input('payload') ?? '',
|
||||||
'time_offset' => $request->input('time_offset'),
|
'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')
|
Activity::event('server:task.create')
|
||||||
->subject($schedule, $task)
|
->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.");
|
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, [
|
$this->repository->update($task->id, [
|
||||||
|
'sequence_id' => $sequenceId,
|
||||||
'action' => $request->input('action'),
|
'action' => $request->input('action'),
|
||||||
'payload' => $request->input('payload') ?? '',
|
'payload' => $request->input('payload') ?? '',
|
||||||
'time_offset' => $request->input('time_offset'),
|
'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')
|
Activity::event('server:task.update')
|
||||||
->subject($schedule, $task)
|
->subject($schedule, $task)
|
||||||
|
@ -117,10 +163,9 @@ class ScheduleTaskController extends ClientApiController
|
||||||
throw new HttpForbiddenException('You do not have permission to perform this action.');
|
throw new HttpForbiddenException('You do not have permission to perform this action.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$schedule->tasks()->where('sequence_id', '>', $task->sequence_id)->update([
|
$schedule->tasks()
|
||||||
'sequence_id' => $schedule->tasks()->getConnection()->raw('(sequence_id - 1)'),
|
->where('sequence_id', '>', $task->sequence_id)
|
||||||
]);
|
->decrement('sequence_id');
|
||||||
|
|
||||||
$task->delete();
|
$task->delete();
|
||||||
|
|
||||||
Activity::event('server:task.delete')->subject($schedule, $task)->property('name', $schedule->name)->log();
|
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
|
public function rename(RenameServerRequest $request, Server $server): JsonResponse
|
||||||
{
|
{
|
||||||
$name = $request->input('name');
|
$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, [
|
$this->repository->update($server->id, [
|
||||||
'name' => $name,
|
'name' => $name,
|
||||||
'description' => $description,
|
'description' => $description,
|
||||||
|
|
|
@ -86,7 +86,7 @@ class Kernel extends HttpKernel
|
||||||
/**
|
/**
|
||||||
* The application's route middleware.
|
* The application's route middleware.
|
||||||
*/
|
*/
|
||||||
protected $routeMiddleware = [
|
protected $middlewareAliases = [
|
||||||
'auth' => Authenticate::class,
|
'auth' => Authenticate::class,
|
||||||
'auth.basic' => AuthenticateWithBasicAuth::class,
|
'auth.basic' => AuthenticateWithBasicAuth::class,
|
||||||
'auth.session' => AuthenticateSession::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',
|
'payload' => 'required_unless:action,backup|string|nullable',
|
||||||
'time_offset' => 'required|numeric|min:0|max:900',
|
'time_offset' => 'required|numeric|min:0|max:900',
|
||||||
'sequence_id' => 'sometimes|required|numeric|min:1',
|
'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 array|null $allowed_ips
|
||||||
* @property string|null $memo
|
* @property string|null $memo
|
||||||
* @property \Illuminate\Support\Carbon|null $last_used_at
|
* @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 $created_at
|
||||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||||
* @property int $r_servers
|
* @property int $r_servers
|
||||||
|
@ -97,6 +98,10 @@ class ApiKey extends Model
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
'allowed_ips' => 'array',
|
'allowed_ips' => 'array',
|
||||||
'user_id' => 'int',
|
'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_USERS => 'int',
|
||||||
'r_' . AdminAcl::RESOURCE_ALLOCATIONS => 'int',
|
'r_' . AdminAcl::RESOURCE_ALLOCATIONS => 'int',
|
||||||
'r_' . AdminAcl::RESOURCE_DATABASE_HOSTS => 'int',
|
'r_' . AdminAcl::RESOURCE_DATABASE_HOSTS => 'int',
|
||||||
|
@ -117,6 +122,7 @@ class ApiKey extends Model
|
||||||
'allowed_ips',
|
'allowed_ips',
|
||||||
'memo',
|
'memo',
|
||||||
'last_used_at',
|
'last_used_at',
|
||||||
|
'expires_at',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -137,6 +143,7 @@ class ApiKey extends Model
|
||||||
'allowed_ips' => 'nullable|array',
|
'allowed_ips' => 'nullable|array',
|
||||||
'allowed_ips.*' => 'string',
|
'allowed_ips.*' => 'string',
|
||||||
'last_used_at' => 'nullable|date',
|
'last_used_at' => 'nullable|date',
|
||||||
|
'expires_at' => 'nullable|date',
|
||||||
'r_' . AdminAcl::RESOURCE_USERS => 'integer|min:0|max:3',
|
'r_' . AdminAcl::RESOURCE_USERS => 'integer|min:0|max:3',
|
||||||
'r_' . AdminAcl::RESOURCE_ALLOCATIONS => 'integer|min:0|max:3',
|
'r_' . AdminAcl::RESOURCE_ALLOCATIONS => 'integer|min:0|max:3',
|
||||||
'r_' . AdminAcl::RESOURCE_DATABASE_HOSTS => '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',
|
'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.
|
* Returns the user this token is assigned to.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -42,10 +42,7 @@ class Backup extends Model
|
||||||
'is_locked' => 'bool',
|
'is_locked' => 'bool',
|
||||||
'ignored_files' => 'array',
|
'ignored_files' => 'array',
|
||||||
'bytes' => 'int',
|
'bytes' => 'int',
|
||||||
];
|
'completed_at' => 'datetime',
|
||||||
|
|
||||||
protected $dates = [
|
|
||||||
'completed_at',
|
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $attributes = [
|
protected $attributes = [
|
||||||
|
|
|
@ -78,6 +78,9 @@ class Egg extends Model
|
||||||
* Fields that are not mass assignable.
|
* Fields that are not mass assignable.
|
||||||
*/
|
*/
|
||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
|
'nest_id',
|
||||||
|
'author',
|
||||||
|
'uuid',
|
||||||
'name',
|
'name',
|
||||||
'description',
|
'description',
|
||||||
'features',
|
'features',
|
||||||
|
|
|
@ -71,14 +71,8 @@ class Schedule extends Model
|
||||||
'is_active' => 'boolean',
|
'is_active' => 'boolean',
|
||||||
'is_processing' => 'boolean',
|
'is_processing' => 'boolean',
|
||||||
'only_when_online' => 'boolean',
|
'only_when_online' => 'boolean',
|
||||||
];
|
'last_run_at' => 'datetime',
|
||||||
|
'next_run_at' => 'datetime',
|
||||||
/**
|
|
||||||
* Columns to mutate to a date.
|
|
||||||
*/
|
|
||||||
protected $dates = [
|
|
||||||
'last_run_at',
|
|
||||||
'next_run_at',
|
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $attributes = [
|
protected $attributes = [
|
||||||
|
|
|
@ -140,11 +140,6 @@ class Server extends Model
|
||||||
*/
|
*/
|
||||||
protected $with = ['allocation'];
|
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.
|
* Fields that are not mass assignable.
|
||||||
*/
|
*/
|
||||||
|
@ -167,7 +162,7 @@ class Server extends Model
|
||||||
'allocation_id' => 'required|bail|unique:servers|exists:allocations,id',
|
'allocation_id' => 'required|bail|unique:servers|exists:allocations,id',
|
||||||
'nest_id' => 'required|exists:nests,id',
|
'nest_id' => 'required|exists:nests,id',
|
||||||
'egg_id' => 'required|exists:eggs,id',
|
'egg_id' => 'required|exists:eggs,id',
|
||||||
'startup' => 'required|string',
|
'startup' => 'nullable|string',
|
||||||
'skip_scripts' => 'sometimes|boolean',
|
'skip_scripts' => 'sometimes|boolean',
|
||||||
'image' => 'required|string|max:191',
|
'image' => 'required|string|max:191',
|
||||||
'database_limit' => 'present|nullable|integer|min:0',
|
'database_limit' => 'present|nullable|integer|min:0',
|
||||||
|
@ -194,6 +189,10 @@ class Server extends Model
|
||||||
'database_limit' => 'integer',
|
'database_limit' => 'integer',
|
||||||
'allocation_limit' => 'integer',
|
'allocation_limit' => 'integer',
|
||||||
'backup_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',
|
'id' => 'integer',
|
||||||
'task_id' => 'integer',
|
'task_id' => 'integer',
|
||||||
'run_status' => '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',
|
'root_admin' => 'boolean',
|
||||||
'use_totp' => 'boolean',
|
'use_totp' => 'boolean',
|
||||||
'gravatar' => 'boolean',
|
'gravatar' => 'boolean',
|
||||||
|
'totp_authenticated_at' => 'datetime',
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $dates = ['totp_authenticated_at'];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The attributes excluded from the model's JSON form.
|
* The attributes excluded from the model's JSON form.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -15,7 +15,7 @@ class AppServiceProvider extends ServiceProvider
|
||||||
/**
|
/**
|
||||||
* Bootstrap any application services.
|
* Bootstrap any application services.
|
||||||
*/
|
*/
|
||||||
public function boot()
|
public function boot(): void
|
||||||
{
|
{
|
||||||
Schema::defaultStringLength(191);
|
Schema::defaultStringLength(191);
|
||||||
|
|
||||||
|
@ -48,7 +48,7 @@ class AppServiceProvider extends ServiceProvider
|
||||||
/**
|
/**
|
||||||
* Register application service providers.
|
* Register application service providers.
|
||||||
*/
|
*/
|
||||||
public function register()
|
public function register(): void
|
||||||
{
|
{
|
||||||
// Only load the settings service provider if the environment
|
// Only load the settings service provider if the environment
|
||||||
// is configured to allow it.
|
// is configured to allow it.
|
||||||
|
|
|
@ -17,14 +17,12 @@ class AuthServiceProvider extends ServiceProvider
|
||||||
Server::class => ServerPolicy::class,
|
Server::class => ServerPolicy::class,
|
||||||
];
|
];
|
||||||
|
|
||||||
public function boot()
|
public function boot(): void
|
||||||
{
|
{
|
||||||
Sanctum::usePersonalAccessTokenModel(ApiKey::class);
|
Sanctum::usePersonalAccessTokenModel(ApiKey::class);
|
||||||
|
|
||||||
$this->registerPolicies();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function register()
|
public function register(): void
|
||||||
{
|
{
|
||||||
Sanctum::ignoreMigrations();
|
Sanctum::ignoreMigrations();
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ class BackupsServiceProvider extends ServiceProvider implements DeferrableProvid
|
||||||
/**
|
/**
|
||||||
* Register the S3 backup disk.
|
* Register the S3 backup disk.
|
||||||
*/
|
*/
|
||||||
public function register()
|
public function register(): void
|
||||||
{
|
{
|
||||||
$this->app->singleton(BackupManager::class, function ($app) {
|
$this->app->singleton(BackupManager::class, function ($app) {
|
||||||
return new BackupManager($app);
|
return new BackupManager($app);
|
||||||
|
|
|
@ -9,7 +9,7 @@ class BladeServiceProvider extends ServiceProvider
|
||||||
/**
|
/**
|
||||||
* Perform post-registration booting of services.
|
* Perform post-registration booting of services.
|
||||||
*/
|
*/
|
||||||
public function boot()
|
public function boot(): void
|
||||||
{
|
{
|
||||||
$this->app->make('blade.compiler')
|
$this->app->make('blade.compiler')
|
||||||
->directive('datetimeHuman', function ($expression) {
|
->directive('datetimeHuman', function ($expression) {
|
||||||
|
|
|
@ -10,7 +10,7 @@ class BroadcastServiceProvider extends ServiceProvider
|
||||||
/**
|
/**
|
||||||
* Bootstrap any application services.
|
* Bootstrap any application services.
|
||||||
*/
|
*/
|
||||||
public function boot()
|
public function boot(): void
|
||||||
{
|
{
|
||||||
Broadcast::routes();
|
Broadcast::routes();
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@ class HashidsServiceProvider extends ServiceProvider
|
||||||
/**
|
/**
|
||||||
* Register the ability to use Hashids.
|
* Register the ability to use Hashids.
|
||||||
*/
|
*/
|
||||||
public function register()
|
public function register(): void
|
||||||
{
|
{
|
||||||
$this->app->singleton(HashidsInterface::class, function () {
|
$this->app->singleton(HashidsInterface::class, function () {
|
||||||
return new Hashids(
|
return new Hashids(
|
||||||
|
|
|
@ -43,7 +43,7 @@ class RepositoryServiceProvider extends ServiceProvider
|
||||||
/**
|
/**
|
||||||
* Register all the repository bindings.
|
* Register all the repository bindings.
|
||||||
*/
|
*/
|
||||||
public function register()
|
public function register(): void
|
||||||
{
|
{
|
||||||
// Eloquent Repositories
|
// Eloquent Repositories
|
||||||
$this->app->bind(AllocationRepositoryInterface::class, AllocationRepository::class);
|
$this->app->bind(AllocationRepositoryInterface::class, AllocationRepository::class);
|
||||||
|
|
|
@ -19,7 +19,7 @@ class RouteServiceProvider extends ServiceProvider
|
||||||
/**
|
/**
|
||||||
* Define your route model bindings, pattern filters, etc.
|
* Define your route model bindings, pattern filters, etc.
|
||||||
*/
|
*/
|
||||||
public function boot()
|
public function boot(): void
|
||||||
{
|
{
|
||||||
$this->configureRateLimiting();
|
$this->configureRateLimiting();
|
||||||
|
|
||||||
|
@ -68,7 +68,7 @@ class RouteServiceProvider extends ServiceProvider
|
||||||
/**
|
/**
|
||||||
* Configure the rate limiters for the application.
|
* 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
|
// 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
|
// 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.
|
* 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
|
// Only set the email driver settings from the database if we
|
||||||
// are configured using SMTP as the driver.
|
// are configured using SMTP as the driver.
|
||||||
|
|
|
@ -10,7 +10,7 @@ class ViewComposerServiceProvider extends ServiceProvider
|
||||||
/**
|
/**
|
||||||
* Register bindings in the container.
|
* Register bindings in the container.
|
||||||
*/
|
*/
|
||||||
public function boot()
|
public function boot(): void
|
||||||
{
|
{
|
||||||
$this->app->make('view')->composer('*', AssetComposer::class);
|
$this->app->make('view')->composer('*', AssetComposer::class);
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,7 +38,9 @@ class DeployServerDatabaseService
|
||||||
throw new NoSuitableDatabaseHostException();
|
throw new NoSuitableDatabaseHostException();
|
||||||
}
|
}
|
||||||
|
|
||||||
$databaseHostId = $hosts->random()->id;
|
/** @var \Pterodactyl\Models\DatabaseHost $databaseHost */
|
||||||
|
$databaseHost = $hosts->random();
|
||||||
|
$databaseHostId = $databaseHost->id;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->managementService->create($server, [
|
return $this->managementService->create($server, [
|
||||||
|
|
|
@ -17,54 +17,54 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"require": {
|
"require": {
|
||||||
"php": "^8.0.2 || ^8.1 || ^8.2",
|
"php": "^8.1 || ^8.2",
|
||||||
"ext-json": "*",
|
"ext-json": "*",
|
||||||
"ext-mbstring": "*",
|
"ext-mbstring": "*",
|
||||||
"ext-pdo": "*",
|
"ext-pdo": "*",
|
||||||
"ext-pdo_mysql": "*",
|
"ext-pdo_mysql": "*",
|
||||||
"ext-posix": "*",
|
"ext-posix": "*",
|
||||||
"ext-zip": "*",
|
"ext-zip": "*",
|
||||||
"aws/aws-sdk-php": "~3.253",
|
"aws/aws-sdk-php": "~3.260.1",
|
||||||
"doctrine/dbal": "~3.5",
|
"doctrine/dbal": "~3.6.0",
|
||||||
"guzzlehttp/guzzle": "~7.5",
|
"guzzlehttp/guzzle": "~7.5.0",
|
||||||
"hashids/hashids": "~4.1",
|
"hashids/hashids": "~5.0.0",
|
||||||
"laracasts/utilities": "~3.2",
|
"laracasts/utilities": "~3.2.2",
|
||||||
"laravel/framework": "~9.43",
|
"laravel/framework": "~10.1.3",
|
||||||
"laravel/helpers": "~1.5",
|
"laravel/helpers": "~1.6.0",
|
||||||
"laravel/sanctum": "~3.0",
|
"laravel/sanctum": "~3.2.1",
|
||||||
"laravel/tinker": "~2.7",
|
"laravel/tinker": "~2.8.1",
|
||||||
"laravel/ui": "~4.1",
|
"laravel/ui": "~4.2.1",
|
||||||
"lcobucci/jwt": "~4.2",
|
"lcobucci/jwt": "~4.3.0",
|
||||||
"league/flysystem-aws-s3-v3": "~3.10",
|
"league/flysystem-aws-s3-v3": "~3.12.2",
|
||||||
"league/flysystem-memory": "~3.10",
|
"league/flysystem-memory": "~3.10.3",
|
||||||
"matriphe/iso-639": "~1.2",
|
"matriphe/iso-639": "~1.2",
|
||||||
"phpseclib/phpseclib": "~3.0",
|
"phpseclib/phpseclib": "~3.0.18",
|
||||||
"pragmarx/google2fa": "~8.0",
|
"pragmarx/google2fa": "~8.0.0",
|
||||||
"predis/predis": "~2.0",
|
"predis/predis": "~2.1.1",
|
||||||
"psr/cache": "~3.0",
|
"prologue/alerts": "~1.1.0",
|
||||||
"s1lentium/iptools": "~1.1",
|
"psr/cache": "~3.0.0",
|
||||||
"spatie/laravel-fractal": "~6.0",
|
"s1lentium/iptools": "~1.2.0",
|
||||||
"spatie/laravel-query-builder": "~5.1",
|
"spatie/laravel-fractal": "~6.0.3",
|
||||||
"staudenmeir/belongs-to-through": "~2.12",
|
"spatie/laravel-query-builder": "~5.1.2",
|
||||||
"symfony/http-client": "~6.0",
|
"staudenmeir/belongs-to-through": "~2.13",
|
||||||
"symfony/mailgun-mailer": "~6.0",
|
"symfony/http-client": "~6.2.6",
|
||||||
"symfony/postmark-mailer": "~6.0",
|
"symfony/mailgun-mailer": "~6.2.5",
|
||||||
"symfony/yaml": "~6.0",
|
"symfony/postmark-mailer": "~6.2.5",
|
||||||
"webmozart/assert": "~1.11"
|
"symfony/yaml": "~6.2.5",
|
||||||
|
"webmozart/assert": "~1.11.0"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"barryvdh/laravel-ide-helper": "~2.12.3",
|
"barryvdh/laravel-ide-helper": "~2.13.0",
|
||||||
"fakerphp/faker": "~1.20",
|
"fakerphp/faker": "~1.21.0",
|
||||||
"friendsofphp/php-cs-fixer": "~3.11",
|
"friendsofphp/php-cs-fixer": "~3.14.4",
|
||||||
"itsgoingd/clockwork": "~5.1",
|
"itsgoingd/clockwork": "~5.1.12",
|
||||||
"laravel/sail": "~1.16",
|
"laravel/sail": "~1.21.0",
|
||||||
"mockery/mockery": "~1.5",
|
"mockery/mockery": "~1.5.1",
|
||||||
"nunomaduro/collision": "~6.3",
|
"nunomaduro/collision": "~7.0.5",
|
||||||
"nunomaduro/larastan": "^2.0",
|
"nunomaduro/larastan": "~2.4.1",
|
||||||
"phpstan/phpstan": "~1.9",
|
"phpstan/phpstan": "~1.10.1",
|
||||||
"php-mock/php-mock-phpunit": "~2.6",
|
"phpunit/phpunit": "~10.0.11",
|
||||||
"phpunit/phpunit": "~9.5",
|
"spatie/laravel-ignition": "~2.0.0"
|
||||||
"spatie/laravel-ignition": "~1.5"
|
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"files": [
|
"files": [
|
||||||
|
@ -84,24 +84,25 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"cs:fix": "php-cs-fixer fix",
|
"cs:fix": "php-cs-fixer fix",
|
||||||
"cs:check": "php-cs-fixer fix --dry-run --diff --verbose",
|
"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": [
|
"post-root-package-install": [
|
||||||
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
|
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
|
||||||
],
|
],
|
||||||
"post-create-project-cmd": [
|
"post-create-project-cmd": [
|
||||||
"@php artisan key:generate"
|
"@php artisan key:generate --ansi"
|
||||||
],
|
|
||||||
"post-autoload-dump": [
|
|
||||||
"Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
|
|
||||||
"@php artisan package:discover || true"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"prefer-stable": true,
|
|
||||||
"config": {
|
"config": {
|
||||||
"optimize-autoloader": true,
|
"optimize-autoloader": true,
|
||||||
"preferred-install": "dist",
|
"preferred-install": "dist",
|
||||||
"sort-packages": true,
|
"sort-packages": true,
|
||||||
"platform": {
|
"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
|
| 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.
|
| 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"
|
| "postmark", "log", "array", "failover"
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -34,7 +34,7 @@ class EggVariableFactory extends Factory
|
||||||
/**
|
/**
|
||||||
* Indicate that the egg variable is viewable.
|
* Indicate that the egg variable is viewable.
|
||||||
*/
|
*/
|
||||||
public function viewable(): Factory
|
public function viewable(): static
|
||||||
{
|
{
|
||||||
return $this->state(function (array $attributes) {
|
return $this->state(function (array $attributes) {
|
||||||
return [
|
return [
|
||||||
|
@ -46,7 +46,7 @@ class EggVariableFactory extends Factory
|
||||||
/**
|
/**
|
||||||
* Indicate that the egg variable is editable.
|
* Indicate that the egg variable is editable.
|
||||||
*/
|
*/
|
||||||
public function editable(): Factory
|
public function editable(): static
|
||||||
{
|
{
|
||||||
return $this->state(function (array $attributes) {
|
return $this->state(function (array $attributes) {
|
||||||
return [
|
return [
|
||||||
|
|
|
@ -41,7 +41,7 @@ class UserFactory extends Factory
|
||||||
/**
|
/**
|
||||||
* Indicate that the user is an admin.
|
* Indicate that the user is an admin.
|
||||||
*/
|
*/
|
||||||
public function admin(): Factory
|
public function admin(): static
|
||||||
{
|
{
|
||||||
return $this->state(['root_admin' => true]);
|
return $this->state(['root_admin' => true]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,21 +1,21 @@
|
||||||
{
|
{
|
||||||
"_comment": "DO NOT EDIT: FILE GENERATED AUTOMATICALLY BY PTERODACTYL PANEL - PTERODACTYL.IO",
|
"_comment": "DO NOT EDIT: FILE GENERATED AUTOMATICALLY BY PTERODACTYL PANEL - PTERODACTYL.IO",
|
||||||
"meta": {
|
"meta": {
|
||||||
"version": "PTDL_v1",
|
"version": "PTDL_v2",
|
||||||
"update_url": null
|
"update_url": null
|
||||||
},
|
},
|
||||||
"exported_at": "2022-01-18T11:44:55-05:00",
|
"exported_at": "2023-03-25T13:37:00+00:00",
|
||||||
"name": "Rust",
|
"name": "Rust",
|
||||||
"author": "support@pterodactyl.io",
|
"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.",
|
"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": [
|
"features": [
|
||||||
"steam_disk_space"
|
"steam_disk_space"
|
||||||
],
|
],
|
||||||
"images": [
|
"docker_images": {
|
||||||
"quay.io\/pterodactyl\/core:rust"
|
"quay.io\/pterodactyl\/core:rust": "quay.io\/pterodactyl\/core:rust"
|
||||||
],
|
},
|
||||||
"file_denylist": [],
|
"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": {
|
"config": {
|
||||||
"files": "{}",
|
"files": "{}",
|
||||||
"startup": "{\r\n \"done\": \"Server startup complete\"\r\n}",
|
"startup": "{\r\n \"done\": \"Server startup complete\"\r\n}",
|
||||||
|
@ -37,16 +37,18 @@
|
||||||
"default_value": "A Rust Server",
|
"default_value": "A Rust Server",
|
||||||
"user_viewable": true,
|
"user_viewable": true,
|
||||||
"user_editable": true,
|
"user_editable": true,
|
||||||
"rules": "required|string|max:60"
|
"rules": "required|string|max:60",
|
||||||
|
"field_type": "text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "OxideMod",
|
"name": "Modding Framework",
|
||||||
"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.",
|
"description": "The modding framework to be used: carbon, oxide, vanilla.\r\nDefaults to \"vanilla\" for a non-modded server installation.",
|
||||||
"env_variable": "OXIDE",
|
"env_variable": "FRAMEWORK",
|
||||||
"default_value": "0",
|
"default_value": "vanilla",
|
||||||
"user_viewable": true,
|
"user_viewable": true,
|
||||||
"user_editable": true,
|
"user_editable": true,
|
||||||
"rules": "required|boolean"
|
"rules": "required|in:carbon,oxide,vanilla",
|
||||||
|
"field_type": "text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Level",
|
"name": "Level",
|
||||||
|
@ -55,7 +57,8 @@
|
||||||
"default_value": "Procedural Map",
|
"default_value": "Procedural Map",
|
||||||
"user_viewable": true,
|
"user_viewable": true,
|
||||||
"user_editable": true,
|
"user_editable": true,
|
||||||
"rules": "required|string|max:20"
|
"rules": "required|string|max:20",
|
||||||
|
"field_type": "text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Description",
|
"name": "Description",
|
||||||
|
@ -64,7 +67,8 @@
|
||||||
"default_value": "Powered by Pterodactyl",
|
"default_value": "Powered by Pterodactyl",
|
||||||
"user_viewable": true,
|
"user_viewable": true,
|
||||||
"user_editable": true,
|
"user_editable": true,
|
||||||
"rules": "required|string"
|
"rules": "required|string",
|
||||||
|
"field_type": "text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "URL",
|
"name": "URL",
|
||||||
|
@ -73,7 +77,8 @@
|
||||||
"default_value": "http:\/\/pterodactyl.io",
|
"default_value": "http:\/\/pterodactyl.io",
|
||||||
"user_viewable": true,
|
"user_viewable": true,
|
||||||
"user_editable": true,
|
"user_editable": true,
|
||||||
"rules": "nullable|url"
|
"rules": "nullable|url",
|
||||||
|
"field_type": "text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "World Size",
|
"name": "World Size",
|
||||||
|
@ -82,7 +87,8 @@
|
||||||
"default_value": "3000",
|
"default_value": "3000",
|
||||||
"user_viewable": true,
|
"user_viewable": true,
|
||||||
"user_editable": true,
|
"user_editable": true,
|
||||||
"rules": "required|integer"
|
"rules": "required|integer",
|
||||||
|
"field_type": "text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "World Seed",
|
"name": "World Seed",
|
||||||
|
@ -91,7 +97,8 @@
|
||||||
"default_value": "",
|
"default_value": "",
|
||||||
"user_viewable": true,
|
"user_viewable": true,
|
||||||
"user_editable": true,
|
"user_editable": true,
|
||||||
"rules": "nullable|string"
|
"rules": "nullable|string",
|
||||||
|
"field_type": "text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Max Players",
|
"name": "Max Players",
|
||||||
|
@ -100,7 +107,8 @@
|
||||||
"default_value": "40",
|
"default_value": "40",
|
||||||
"user_viewable": true,
|
"user_viewable": true,
|
||||||
"user_editable": true,
|
"user_editable": true,
|
||||||
"rules": "required|integer"
|
"rules": "required|integer",
|
||||||
|
"field_type": "text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Server Image",
|
"name": "Server Image",
|
||||||
|
@ -109,7 +117,18 @@
|
||||||
"default_value": "",
|
"default_value": "",
|
||||||
"user_viewable": true,
|
"user_viewable": true,
|
||||||
"user_editable": 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",
|
"name": "RCON Port",
|
||||||
|
@ -118,16 +137,18 @@
|
||||||
"default_value": "28016",
|
"default_value": "28016",
|
||||||
"user_viewable": true,
|
"user_viewable": true,
|
||||||
"user_editable": false,
|
"user_editable": false,
|
||||||
"rules": "required|integer"
|
"rules": "required|integer",
|
||||||
|
"field_type": "text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "RCON Password",
|
"name": "RCON Password",
|
||||||
"description": "RCON access password.",
|
"description": "RCON access password.",
|
||||||
"env_variable": "RCON_PASS",
|
"env_variable": "RCON_PASS",
|
||||||
"default_value": "CHANGEME",
|
"default_value": "",
|
||||||
"user_viewable": true,
|
"user_viewable": true,
|
||||||
"user_editable": true,
|
"user_editable": true,
|
||||||
"rules": "required|regex:\/^[\\w.-]*$\/|max:64"
|
"rules": "required|regex:\/^[\\w.-]*$\/|max:64",
|
||||||
|
"field_type": "text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Save Interval",
|
"name": "Save Interval",
|
||||||
|
@ -136,7 +157,8 @@
|
||||||
"default_value": "60",
|
"default_value": "60",
|
||||||
"user_viewable": true,
|
"user_viewable": true,
|
||||||
"user_editable": true,
|
"user_editable": true,
|
||||||
"rules": "required|integer"
|
"rules": "required|integer",
|
||||||
|
"field_type": "text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Additional Arguments",
|
"name": "Additional Arguments",
|
||||||
|
@ -145,7 +167,8 @@
|
||||||
"default_value": "",
|
"default_value": "",
|
||||||
"user_viewable": true,
|
"user_viewable": true,
|
||||||
"user_editable": true,
|
"user_editable": true,
|
||||||
"rules": "nullable|string"
|
"rules": "nullable|string",
|
||||||
|
"field_type": "text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "App Port",
|
"name": "App Port",
|
||||||
|
@ -154,7 +177,8 @@
|
||||||
"default_value": "28082",
|
"default_value": "28082",
|
||||||
"user_viewable": true,
|
"user_viewable": true,
|
||||||
"user_editable": false,
|
"user_editable": false,
|
||||||
"rules": "required|integer"
|
"rules": "required|integer",
|
||||||
|
"field_type": "text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Server Logo",
|
"name": "Server Logo",
|
||||||
|
@ -163,7 +187,8 @@
|
||||||
"default_value": "",
|
"default_value": "",
|
||||||
"user_viewable": true,
|
"user_viewable": true,
|
||||||
"user_editable": true,
|
"user_editable": true,
|
||||||
"rules": "nullable|url"
|
"rules": "nullable|url",
|
||||||
|
"field_type": "text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Custom Map URL",
|
"name": "Custom Map URL",
|
||||||
|
@ -172,7 +197,8 @@
|
||||||
"default_value": "",
|
"default_value": "",
|
||||||
"user_viewable": true,
|
"user_viewable": true,
|
||||||
"user_editable": 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": {
|
"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": {
|
"devshell": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
|
@ -74,28 +18,21 @@
|
||||||
},
|
},
|
||||||
"dream2nix": {
|
"dream2nix": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"alejandra": "alejandra",
|
|
||||||
"all-cabal-json": "all-cabal-json",
|
|
||||||
"crane": "crane",
|
|
||||||
"devshell": "devshell",
|
"devshell": "devshell",
|
||||||
|
"flake-compat": "flake-compat",
|
||||||
"flake-parts": "flake-parts",
|
"flake-parts": "flake-parts",
|
||||||
"flake-utils-pre-commit": "flake-utils-pre-commit",
|
"nix-unit": "nix-unit",
|
||||||
"ghc-utils": "ghc-utils",
|
|
||||||
"gomod2nix": "gomod2nix",
|
|
||||||
"mach-nix": "mach-nix",
|
|
||||||
"nix-pypi-fetcher": "nix-pypi-fetcher",
|
|
||||||
"nixpkgs": [
|
"nixpkgs": [
|
||||||
"nixpkgs"
|
"nixpkgs"
|
||||||
],
|
],
|
||||||
"poetry2nix": "poetry2nix",
|
|
||||||
"pre-commit-hooks": "pre-commit-hooks"
|
"pre-commit-hooks": "pre-commit-hooks"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1669743839,
|
"lastModified": 1695717405,
|
||||||
"narHash": "sha256-zxnaRaWfCJxy0JlORD4Kmtzd0pfpcGLnyaCIJY8OlIo=",
|
"narHash": "sha256-MvHrU3h0Bw57s2p+wCUnSZliR4wvvPi3xkW+MRWB5HU=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "dream2nix",
|
"repo": "dream2nix",
|
||||||
"rev": "b6af93946130748f72671dfd2ab84a5aeaf1f191",
|
"rev": "6dbd59e4a47bd916a655c4425a3e730c6aeae033",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -104,39 +41,35 @@
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"fenix": {
|
"flake-compat": {
|
||||||
"inputs": {
|
"flake": false,
|
||||||
"nixpkgs": [
|
|
||||||
"dream2nix",
|
|
||||||
"alejandra",
|
|
||||||
"nixpkgs"
|
|
||||||
],
|
|
||||||
"rust-analyzer-src": "rust-analyzer-src"
|
|
||||||
},
|
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1657607339,
|
"lastModified": 1673956053,
|
||||||
"narHash": "sha256-HaqoAwlbVVZH2n4P3jN2FFPMpVuhxDy1poNOR7kzODc=",
|
"narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=",
|
||||||
"owner": "nix-community",
|
"owner": "edolstra",
|
||||||
"repo": "fenix",
|
"repo": "flake-compat",
|
||||||
"rev": "b814c83d9e6aa5a28d0cf356ecfdafb2505ad37d",
|
"rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "nix-community",
|
"owner": "edolstra",
|
||||||
"repo": "fenix",
|
"repo": "flake-compat",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"flake-parts": {
|
"flake-parts": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"nixpkgs-lib": "nixpkgs-lib"
|
"nixpkgs-lib": [
|
||||||
|
"dream2nix",
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1668450977,
|
"lastModified": 1675933616,
|
||||||
"narHash": "sha256-cfLhMhnvXn6x1vPm+Jow3RiFAUSCw/l1utktCw5rVA4=",
|
"narHash": "sha256-/rczJkJHtx16IFxMmAWu5nNYcSXNg1YYXTHoGjLrLUA=",
|
||||||
"owner": "hercules-ci",
|
"owner": "hercules-ci",
|
||||||
"repo": "flake-parts",
|
"repo": "flake-parts",
|
||||||
"rev": "d591857e9d7dd9ddbfba0ea02b43b927c3c0f1fa",
|
"rev": "47478a4a003e745402acf63be7f9a092d51b83d7",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -146,12 +79,15 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"flake-utils": {
|
"flake-utils": {
|
||||||
|
"inputs": {
|
||||||
|
"systems": "systems"
|
||||||
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1667395993,
|
"lastModified": 1689068808,
|
||||||
"narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=",
|
"narHash": "sha256-6ixXo3wt24N/melDWjq70UuHQLxGV8jZvooRanIHXw0=",
|
||||||
"owner": "numtide",
|
"owner": "numtide",
|
||||||
"repo": "flake-utils",
|
"repo": "flake-utils",
|
||||||
"rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f",
|
"rev": "919d646de7be200f3bf08cb76ae1f09402b6f9b4",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -160,13 +96,16 @@
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"flake-utils-pre-commit": {
|
"flake-utils_2": {
|
||||||
|
"inputs": {
|
||||||
|
"systems": "systems_2"
|
||||||
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1644229661,
|
"lastModified": 1694529238,
|
||||||
"narHash": "sha256-1YdnJAsNy69bpcjuoKdOYQX0YxZBiCYZo4Twxerqv7k=",
|
"narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=",
|
||||||
"owner": "numtide",
|
"owner": "numtide",
|
||||||
"repo": "flake-utils",
|
"repo": "flake-utils",
|
||||||
"rev": "3cecb5b042f7f209c56ffd8371b2711a290ec797",
|
"rev": "ff7b65b44d01cf9ba6a71320833626af21126384",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -175,69 +114,6 @@
|
||||||
"type": "github"
|
"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": {
|
"mk-node-package": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"flake-utils": [
|
"flake-utils": [
|
||||||
|
@ -263,29 +139,62 @@
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nix-pypi-fetcher": {
|
"nix-github-actions": {
|
||||||
"flake": false,
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"dream2nix",
|
||||||
|
"nix-unit",
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1669065297,
|
"lastModified": 1688870561,
|
||||||
"narHash": "sha256-UStjXjNIuIm7SzMOWvuYWIHBkPUKQ8Id63BMJjnIDoA=",
|
"narHash": "sha256-4UYkifnPEw1nAzqqPOTL2MvWtm3sNGw1UTYTalkTcGY=",
|
||||||
"owner": "DavHau",
|
"owner": "nix-community",
|
||||||
"repo": "nix-pypi-fetcher",
|
"repo": "nix-github-actions",
|
||||||
"rev": "a9885ac6a091576b5195d547ac743d45a2a615ac",
|
"rev": "165b1650b753316aa7f1787f3005a8d2da0f5301",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "DavHau",
|
"owner": "nix-community",
|
||||||
"repo": "nix-pypi-fetcher",
|
"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"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1669542132,
|
"lastModified": 1695644571,
|
||||||
"narHash": "sha256-DRlg++NJAwPh8io3ExBJdNW7Djs3plVI5jgYQ+iXAZQ=",
|
"narHash": "sha256-asS9dCCdlt1lPq0DLwkVBbVoEKuEuz+Zi3DG7pR/RxA=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "a115bb9bd56831941be3776c8a94005867f316a7",
|
"rev": "6500b4580c2a1f3d0f980d32d285739d8e156d92",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -295,24 +204,6 @@
|
||||||
"type": "github"
|
"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": {
|
"npmlock2nix": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
|
@ -347,29 +238,9 @@
|
||||||
"type": "github"
|
"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": {
|
"pre-commit-hooks": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"flake-utils": [
|
"flake-utils": "flake-utils",
|
||||||
"dream2nix",
|
|
||||||
"flake-utils-pre-commit"
|
|
||||||
],
|
|
||||||
"nixpkgs": [
|
"nixpkgs": [
|
||||||
"dream2nix",
|
"dream2nix",
|
||||||
"nixpkgs"
|
"nixpkgs"
|
||||||
|
@ -392,25 +263,60 @@
|
||||||
"root": {
|
"root": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"dream2nix": "dream2nix",
|
"dream2nix": "dream2nix",
|
||||||
"flake-utils": "flake-utils",
|
"flake-utils": "flake-utils_2",
|
||||||
"mk-node-package": "mk-node-package",
|
"mk-node-package": "mk-node-package",
|
||||||
"nixpkgs": "nixpkgs"
|
"nixpkgs": "nixpkgs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"rust-analyzer-src": {
|
"systems": {
|
||||||
"flake": false,
|
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1657557289,
|
"lastModified": 1681028828,
|
||||||
"narHash": "sha256-PRW+nUwuqNTRAEa83SfX+7g+g8nQ+2MMbasQ9nt6+UM=",
|
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||||
"owner": "rust-lang",
|
"owner": "nix-systems",
|
||||||
"repo": "rust-analyzer",
|
"repo": "default",
|
||||||
"rev": "caf23f29144b371035b864a1017dbc32573ad56d",
|
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "rust-lang",
|
"owner": "nix-systems",
|
||||||
"ref": "nightly",
|
"repo": "default",
|
||||||
"repo": "rust-analyzer",
|
"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"
|
"type": "github"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -159,6 +159,7 @@
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
systems = [system];
|
systems = [system];
|
||||||
|
autoProjects = true;
|
||||||
})
|
})
|
||||||
.packages
|
.packages
|
||||||
."${system}"
|
."${system}"
|
||||||
|
@ -171,7 +172,7 @@
|
||||||
buildInputs = [];
|
buildInputs = [];
|
||||||
|
|
||||||
buildPhase = ''
|
buildPhase = ''
|
||||||
yarn run build
|
pnpm run build
|
||||||
'';
|
'';
|
||||||
|
|
||||||
installPhase = ''
|
installPhase = ''
|
||||||
|
@ -213,6 +214,7 @@
|
||||||
copyToRoot = pkgs.buildEnv {
|
copyToRoot = pkgs.buildEnv {
|
||||||
name = "image-root";
|
name = "image-root";
|
||||||
paths = [
|
paths = [
|
||||||
|
bash
|
||||||
dockerTools.fakeNss
|
dockerTools.fakeNss
|
||||||
caCertificates
|
caCertificates
|
||||||
caddy
|
caddy
|
||||||
|
@ -221,11 +223,9 @@
|
||||||
coreutils
|
coreutils
|
||||||
mysql80
|
mysql80
|
||||||
nodejs-18_x
|
nodejs-18_x
|
||||||
nodePackages.npm
|
|
||||||
nodePackages.pnpm
|
nodePackages.pnpm
|
||||||
nodePackages.yarn
|
|
||||||
php81WithExtensions
|
php81WithExtensions
|
||||||
postgresql_14
|
postgresql_15
|
||||||
];
|
];
|
||||||
pathsToLink = ["/bin" "/etc"];
|
pathsToLink = ["/bin" "/etc"];
|
||||||
};
|
};
|
||||||
|
|
87
package.json
87
package.json
|
@ -1,9 +1,11 @@
|
||||||
{
|
{
|
||||||
"name": "@pterodactyl/panel",
|
"name": "@pterodactyl/panel",
|
||||||
|
"version": "1.0.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
"packageManager": "pnpm@8.7.6",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16.0"
|
"node": ">=16.13"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
|
@ -39,15 +41,15 @@
|
||||||
"@codemirror/view": "^6.0.0",
|
"@codemirror/view": "^6.0.0",
|
||||||
"@floating-ui/react-dom-interactions": "0.13.3",
|
"@floating-ui/react-dom-interactions": "0.13.3",
|
||||||
"@flyyer/use-fit-text": "3.0.1",
|
"@flyyer/use-fit-text": "3.0.1",
|
||||||
"@fortawesome/fontawesome-svg-core": "6.2.1",
|
"@fortawesome/fontawesome-svg-core": "6.3.0",
|
||||||
"@fortawesome/free-brands-svg-icons": "6.2.1",
|
"@fortawesome/free-brands-svg-icons": "6.3.0",
|
||||||
"@fortawesome/free-solid-svg-icons": "6.2.1",
|
"@fortawesome/free-solid-svg-icons": "6.3.0",
|
||||||
"@fortawesome/react-fontawesome": "0.2.0",
|
"@fortawesome/react-fontawesome": "0.2.0",
|
||||||
"@headlessui/react": "1.7.5",
|
"@headlessui/react": "1.7.11",
|
||||||
"@heroicons/react": "1.0.6",
|
"@heroicons/react": "1.0.6",
|
||||||
"@lezer/common": "1.0.2",
|
"@lezer/common": "1.0.2",
|
||||||
"@lezer/highlight": "1.1.3",
|
"@lezer/highlight": "1.1.3",
|
||||||
"@preact/signals-react": "1.2.1",
|
"@preact/signals-react": "1.2.2",
|
||||||
"axios": "0.27.2",
|
"axios": "0.27.2",
|
||||||
"boring-avatars": "1.7.0",
|
"boring-avatars": "1.7.0",
|
||||||
"chart.js": "3.9.1",
|
"chart.js": "3.9.1",
|
||||||
|
@ -55,75 +57,76 @@
|
||||||
"copy-to-clipboard": "3.3.3",
|
"copy-to-clipboard": "3.3.3",
|
||||||
"date-fns": "2.29.3",
|
"date-fns": "2.29.3",
|
||||||
"debounce": "1.2.1",
|
"debounce": "1.2.1",
|
||||||
"deepmerge-ts": "4.2.2",
|
"deepmerge-ts": "4.3.0",
|
||||||
"easy-peasy": "5.2.0",
|
"easy-peasy": "5.2.0",
|
||||||
"events": "3.3.0",
|
"events": "3.3.0",
|
||||||
"formik": "2.2.9",
|
"formik": "2.2.9",
|
||||||
"framer-motion": "7.7.2",
|
"framer-motion": "9.1.6",
|
||||||
"i18next": "22.4.3",
|
"i18next": "22.4.10",
|
||||||
"i18next-http-backend": "2.1.0",
|
"i18next-http-backend": "2.1.1",
|
||||||
"i18next-multiload-backend-adapter": "2.2.0",
|
"i18next-multiload-backend-adapter": "2.2.0",
|
||||||
"nanoid": "4.0.0",
|
"nanoid": "4.0.1",
|
||||||
"qrcode.react": "3.1.0",
|
"qrcode.react": "3.1.0",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-chartjs-2": "4.3.1",
|
"react-chartjs-2": "4.3.1",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
"react-fast-compare": "3.2.0",
|
"react-fast-compare": "3.2.0",
|
||||||
"react-i18next": "12.1.1",
|
"react-i18next": "12.2.0",
|
||||||
"react-router-dom": "6.4.5",
|
"react-router-dom": "6.8.1",
|
||||||
"react-select": "5.7.0",
|
"react-select": "5.7.0",
|
||||||
"reaptcha": "1.12.1",
|
"reaptcha": "1.12.1",
|
||||||
"sockette": "2.0.6",
|
"sockette": "2.0.6",
|
||||||
"styled-components": "5.3.6",
|
"styled-components": "5.3.6",
|
||||||
"styled-components-breakpoint": "3.0.0-preview.20",
|
"styled-components-breakpoint": "3.0.0-preview.20",
|
||||||
"swr": "1.3.0",
|
"swr": "2.0.3",
|
||||||
"xterm": "5.0.0",
|
"xterm": "5.1.0",
|
||||||
"xterm-addon-fit": "0.6.0",
|
"xterm-addon-fit": "0.7.0",
|
||||||
"xterm-addon-search": "0.10.0",
|
"xterm-addon-search": "0.11.0",
|
||||||
"xterm-addon-search-bar": "0.2.0",
|
"xterm-addon-search-bar": "0.2.0",
|
||||||
"xterm-addon-web-links": "0.7.0",
|
"xterm-addon-web-links": "0.8.0",
|
||||||
"yup": "0.32.11"
|
"yup": "1.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tailwindcss/forms": "0.5.3",
|
"@tailwindcss/forms": "0.5.3",
|
||||||
"@tailwindcss/line-clamp": "0.4.2",
|
"@tailwindcss/line-clamp": "0.4.2",
|
||||||
"@testing-library/dom": "8.19.0",
|
"@testing-library/dom": "9.0.0",
|
||||||
"@testing-library/react": "13.4.0",
|
"@testing-library/react": "14.0.0",
|
||||||
"@testing-library/user-event": "14.4.3",
|
"@testing-library/user-event": "14.4.3",
|
||||||
"@types/debounce": "1.2.1",
|
"@types/debounce": "1.2.1",
|
||||||
"@types/events": "3.0.0",
|
"@types/events": "3.0.0",
|
||||||
"@types/node": "18.11.13",
|
"@types/node": "18.14.1",
|
||||||
"@types/react": "18.0.26",
|
"@types/react": "18.0.28",
|
||||||
"@types/react-dom": "18.0.9",
|
"@types/react-dom": "18.0.11",
|
||||||
"@types/styled-components": "5.1.26",
|
"@types/styled-components": "5.1.26",
|
||||||
"@typescript-eslint/eslint-plugin": "5.46.1",
|
"@typescript-eslint/eslint-plugin": "5.53.0",
|
||||||
"@typescript-eslint/parser": "5.46.1",
|
"@typescript-eslint/parser": "5.53.0",
|
||||||
"@vitejs/plugin-react": "3.0.0",
|
"@vitejs/plugin-react": "3.1.0",
|
||||||
"autoprefixer": "10.4.13",
|
"autoprefixer": "10.4.13",
|
||||||
"babel-plugin-styled-components": "2.0.7",
|
"babel-plugin-styled-components": "2.0.7",
|
||||||
"babel-plugin-twin": "1.1.0",
|
"babel-plugin-twin": "1.1.0",
|
||||||
"cross-env": "7.0.3",
|
"cross-env": "7.0.3",
|
||||||
"eslint": "8.29.0",
|
"eslint": "8.34.0",
|
||||||
"eslint-config-prettier": "8.5.0",
|
"eslint-config-prettier": "8.6.0",
|
||||||
"eslint-plugin-node": "11.1.0",
|
"eslint-plugin-node": "11.1.0",
|
||||||
"eslint-plugin-prettier": "4.2.1",
|
"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",
|
"eslint-plugin-react-hooks": "4.6.0",
|
||||||
"happy-dom": "8.1.1",
|
"happy-dom": "8.7.2",
|
||||||
"laravel-vite-plugin": "0.7.3",
|
"laravel-vite-plugin": "0.7.4",
|
||||||
"pathe": "1.0.0",
|
"pathe": "1.1.0",
|
||||||
"postcss": "8.4.20",
|
"postcss": "8.4.21",
|
||||||
"postcss-nesting": "10.2.0",
|
"postcss-import": "15.1.0",
|
||||||
"postcss-preset-env": "7.8.3",
|
"postcss-nesting": "11.2.1",
|
||||||
"prettier": "2.8.1",
|
"postcss-preset-env": "8.0.1",
|
||||||
"prettier-plugin-tailwindcss": "0.2.1",
|
"prettier": "2.8.4",
|
||||||
|
"prettier-plugin-tailwindcss": "0.2.3",
|
||||||
"rimraf": "3.0.2",
|
"rimraf": "3.0.2",
|
||||||
"tailwindcss": "3.2.4",
|
"tailwindcss": "3.2.7",
|
||||||
"ts-essentials": "9.3.0",
|
"ts-essentials": "9.3.0",
|
||||||
"twin.macro": "2.8.2",
|
"twin.macro": "2.8.2",
|
||||||
"typescript": "4.9.4",
|
"typescript": "4.9.5",
|
||||||
"vite": "4.0.3",
|
"vite": "4.1.4",
|
||||||
"vitest": "0.26.2"
|
"vitest": "0.28.5"
|
||||||
},
|
},
|
||||||
"browserslist": [
|
"browserslist": [
|
||||||
"> 0.5%",
|
"> 0.5%",
|
||||||
|
|
|
@ -4,9 +4,8 @@
|
||||||
xsi:noNamespaceSchemaLocation="./vendor/phpunit/phpunit/phpunit.xsd"
|
xsi:noNamespaceSchemaLocation="./vendor/phpunit/phpunit/phpunit.xsd"
|
||||||
bootstrap="bootstrap/tests.php"
|
bootstrap="bootstrap/tests.php"
|
||||||
colors="true"
|
colors="true"
|
||||||
printerClass="NunoMaduro\Collision\Adapters\Phpunit\Printer"
|
|
||||||
>
|
>
|
||||||
<coverage processUncoveredFiles="true">
|
<coverage>
|
||||||
<include>
|
<include>
|
||||||
<directory suffix=".php">./app</directory>
|
<directory suffix=".php">./app</directory>
|
||||||
</include>
|
</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
|
* A standard API response with the minimum viable details for the frontend
|
||||||
* to correctly render a egg.
|
* 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.
|
* 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_files: egg.configFiles,
|
||||||
config_startup: egg.configStartup,
|
config_startup: egg.configStartup,
|
||||||
config_stop: egg.configStop,
|
config_stop: egg.configStop,
|
||||||
config_from: egg.configFrom,
|
|
||||||
startup: egg.startup,
|
startup: egg.startup,
|
||||||
script_container: egg.scriptContainer,
|
script_container: egg.scriptContainer,
|
||||||
copy_script_from: egg.copyScriptFrom,
|
|
||||||
script_entry: egg.scriptEntry,
|
script_entry: egg.scriptEntry,
|
||||||
script_is_privileged: egg.scriptIsPrivileged,
|
|
||||||
script_install: egg.scriptInstall,
|
script_install: egg.scriptInstall,
|
||||||
|
// config_from: egg.configFrom,
|
||||||
|
// copy_script_from: egg.copyScriptFrom,
|
||||||
|
// script_is_privileged: egg.scriptIsPrivileged,
|
||||||
})
|
})
|
||||||
.then(({ data }) => resolve(rawDataToEgg(data)))
|
.then(({ data }) => resolve(rawDataToEgg(data)))
|
||||||
.catch(reject);
|
.catch(reject);
|
||||||
|
|
|
@ -43,15 +43,15 @@ export interface Egg {
|
||||||
configFiles: Record<string, any> | null;
|
configFiles: Record<string, any> | null;
|
||||||
configStartup: Record<string, any> | null;
|
configStartup: Record<string, any> | null;
|
||||||
configStop: string | null;
|
configStop: string | null;
|
||||||
configFrom: number | null;
|
|
||||||
startup: string;
|
startup: string;
|
||||||
scriptContainer: string;
|
scriptContainer: string;
|
||||||
copyScriptFrom: number | null;
|
|
||||||
scriptEntry: string;
|
scriptEntry: string;
|
||||||
scriptIsPrivileged: boolean;
|
|
||||||
scriptInstall: string | null;
|
scriptInstall: string | null;
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
updatedAt: Date;
|
updatedAt: Date;
|
||||||
|
// configFrom: number | null;
|
||||||
|
// copyScriptFrom: number | null;
|
||||||
|
// scriptIsPrivileged: boolean;
|
||||||
|
|
||||||
relations: {
|
relations: {
|
||||||
nest?: Nest;
|
nest?: Nest;
|
||||||
|
|
|
@ -17,7 +17,7 @@ export interface UpdateUserValues {
|
||||||
}
|
}
|
||||||
|
|
||||||
const filters = ['id', 'uuid', 'external_id', 'username', 'email'] as const;
|
const filters = ['id', 'uuid', 'external_id', 'username', 'email'] as const;
|
||||||
type Filters = typeof filters[number];
|
type Filters = (typeof filters)[number];
|
||||||
|
|
||||||
const useGetUsers = (
|
const useGetUsers = (
|
||||||
params?: QueryBuilderParams<Filters>,
|
params?: QueryBuilderParams<Filters>,
|
||||||
|
|
|
@ -47,7 +47,7 @@ export const rawDataToFileObject = (data: FractalResponseData): FileObject => ({
|
||||||
isEditable: function () {
|
isEditable: function () {
|
||||||
if (this.isArchiveType() || !this.isFile) return false;
|
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));
|
return matches.every(m => !this.mimetype.match(m));
|
||||||
},
|
},
|
||||||
|
|
|
@ -11,16 +11,16 @@ import Select from '@/components/elements/Select';
|
||||||
interface Props {
|
interface Props {
|
||||||
nestId?: number;
|
nestId?: number;
|
||||||
selectedEggId?: number;
|
selectedEggId?: number;
|
||||||
onEggSelect: (egg: Egg | null) => void;
|
onEggSelect: (egg: WithRelationships<Egg, 'variables'> | undefined) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ({ nestId, selectedEggId, onEggSelect }: Props) => {
|
export default ({ nestId, selectedEggId, onEggSelect }: Props) => {
|
||||||
const [, , { setValue, setTouched }] = useField<Record<string, string | undefined>>('environment');
|
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) => {
|
const selectEgg = (egg: WithRelationships<Egg, 'variables'> | undefined) => {
|
||||||
if (egg === null) {
|
if (egg === undefined) {
|
||||||
onEggSelect(null);
|
onEggSelect(undefined);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,26 +40,29 @@ export default ({ nestId, selectedEggId, onEggSelect }: Props) => {
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!nestId) {
|
if (!nestId) {
|
||||||
setEggs(null);
|
setEggs(undefined);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
searchEggs(nestId, {})
|
searchEggs(nestId, {})
|
||||||
.then(eggs => {
|
.then(_eggs => {
|
||||||
setEggs(eggs);
|
setEggs(_eggs);
|
||||||
selectEgg(eggs[0] || null);
|
|
||||||
|
// 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));
|
.catch(error => console.error(error));
|
||||||
}, [nestId]);
|
}, [nestId]);
|
||||||
|
|
||||||
const onSelectChange = (e: ChangeEvent<HTMLSelectElement>) => {
|
const onSelectChange = (event: ChangeEvent<HTMLSelectElement>) => {
|
||||||
selectEgg(eggs?.find(egg => egg.id.toString() === e.currentTarget.value) || null);
|
selectEgg(eggs?.find(egg => egg.id.toString() === event.currentTarget.value) ?? undefined);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Label>Egg</Label>
|
<Label>Egg</Label>
|
||||||
<Select id={'eggId'} name={'eggId'} defaultValue={selectedEggId} onChange={onSelectChange}>
|
<Select id={'eggId'} name={'eggId'} value={selectedEggId} onChange={onSelectChange}>
|
||||||
{!eggs ? (
|
{!eggs ? (
|
||||||
<option disabled>Loading...</option>
|
<option disabled>Loading...</option>
|
||||||
) : (
|
) : (
|
||||||
|
|
|
@ -30,6 +30,7 @@ import SpinnerOverlay from '@/components/elements/SpinnerOverlay';
|
||||||
import FlashMessageRender from '@/components/FlashMessageRender';
|
import FlashMessageRender from '@/components/FlashMessageRender';
|
||||||
import useFlash from '@/plugins/useFlash';
|
import useFlash from '@/plugins/useFlash';
|
||||||
import AdminContentBlock from '@/components/admin/AdminContentBlock';
|
import AdminContentBlock from '@/components/admin/AdminContentBlock';
|
||||||
|
import { WithRelationships } from '@/api/admin';
|
||||||
|
|
||||||
function InternalForm() {
|
function InternalForm() {
|
||||||
const {
|
const {
|
||||||
|
@ -39,12 +40,12 @@ function InternalForm() {
|
||||||
values: { environment },
|
values: { environment },
|
||||||
} = useFormikContext<CreateServerRequest>();
|
} = useFormikContext<CreateServerRequest>();
|
||||||
|
|
||||||
const [egg, setEgg] = useState<Egg | null>(null);
|
const [egg, setEgg] = useState<WithRelationships<Egg, 'variables'> | undefined>(undefined);
|
||||||
const [node, setNode] = useState<Node | null>(null);
|
const [node, setNode] = useState<Node | undefined>(undefined);
|
||||||
const [allocations, setAllocations] = useState<Allocation[] | null>(null);
|
const [allocations, setAllocations] = useState<Allocation[] | undefined>(undefined);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (egg === null) {
|
if (egg === undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,7 +55,7 @@ function InternalForm() {
|
||||||
}, [egg]);
|
}, [egg]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (node === null) {
|
if (node === undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,11 +65,11 @@ function InternalForm() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form>
|
<Form>
|
||||||
<div css={tw`grid grid-cols-2 gap-y-6 gap-x-8 mb-16`}>
|
<div className="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-1 gap-y-6 col-span-2 md:col-span-1">
|
||||||
<BaseSettingsBox>
|
<BaseSettingsBox>
|
||||||
<NodeSelect node={node} setNode={setNode} />
|
<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
|
<FormikSwitch
|
||||||
name={'startOnCompletion'}
|
name={'startOnCompletion'}
|
||||||
label={'Start after installation'}
|
label={'Start after installation'}
|
||||||
|
@ -77,20 +78,20 @@ function InternalForm() {
|
||||||
</div>
|
</div>
|
||||||
</BaseSettingsBox>
|
</BaseSettingsBox>
|
||||||
<FeatureLimitsBox />
|
<FeatureLimitsBox />
|
||||||
<ServerServiceContainer egg={egg} setEgg={setEgg} nestId={0} />
|
<ServerServiceContainer selectedEggId={egg?.id} setEgg={setEgg} nestId={0} />
|
||||||
</div>
|
</div>
|
||||||
<div css={tw`grid grid-cols-1 gap-y-6 col-span-2 md:col-span-1`}>
|
<div className="grid grid-cols-1 gap-y-6 col-span-2 md:col-span-1">
|
||||||
<AdminBox icon={faNetworkWired} title={'Networking'} isLoading={isSubmitting}>
|
<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-4 lg:gap-6">
|
||||||
<div>
|
<div>
|
||||||
<Label htmlFor={'allocation.default'}>Primary Allocation</Label>
|
<Label htmlFor={'allocation.default'}>Primary Allocation</Label>
|
||||||
<Select
|
<Select
|
||||||
id={'allocation.default'}
|
id={'allocation.default'}
|
||||||
name={'allocation.default'}
|
name={'allocation.default'}
|
||||||
disabled={node === null}
|
disabled={node === undefined}
|
||||||
onChange={e => setFieldValue('allocation.default', Number(e.currentTarget.value))}
|
onChange={e => setFieldValue('allocation.default', Number(e.currentTarget.value))}
|
||||||
>
|
>
|
||||||
{node === null ? (
|
{node === undefined ? (
|
||||||
<option value="">Select a node...</option>
|
<option value="">Select a node...</option>
|
||||||
) : (
|
) : (
|
||||||
<option value="">Select an allocation...</option>
|
<option value="">Select an allocation...</option>
|
||||||
|
@ -116,7 +117,7 @@ function InternalForm() {
|
||||||
<ServerImageContainer />
|
<ServerImageContainer />
|
||||||
</div>
|
</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} />
|
<SpinnerOverlay visible={isSubmitting} />
|
||||||
|
|
||||||
<Field
|
<Field
|
||||||
|
@ -131,7 +132,7 @@ function InternalForm() {
|
||||||
/>
|
/>
|
||||||
</AdminBox>
|
</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. */}
|
{/* This ensures that no variables are rendered unless the environment has a value for the variable. */}
|
||||||
{egg?.relationships.variables
|
{egg?.relationships.variables
|
||||||
?.filter(v => Object.keys(environment).find(e => e === v.environmentVariable) !== undefined)
|
?.filter(v => Object.keys(environment).find(e => e === v.environmentVariable) !== undefined)
|
||||||
|
@ -140,9 +141,9 @@ function InternalForm() {
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div css={tw`bg-neutral-700 rounded shadow-md px-4 py-3 col-span-2`}>
|
<div className="bg-neutral-700 rounded shadow-md px-4 py-3 col-span-2">
|
||||||
<div css={tw`flex flex-row`}>
|
<div className="flex flex-row">
|
||||||
<Button type="submit" size="small" css={tw`ml-auto`} disabled={isSubmitting || !isValid}>
|
<Button type="submit" size="small" className="ml-auto" disabled={isSubmitting || !isValid}>
|
||||||
Create Server
|
Create Server
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -3,11 +3,9 @@ import { useStoreActions } from 'easy-peasy';
|
||||||
import type { FormikHelpers } from 'formik';
|
import type { FormikHelpers } from 'formik';
|
||||||
import { Form, Formik, useField, useFormikContext } from 'formik';
|
import { Form, Formik, useField, useFormikContext } from 'formik';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import tw from 'twin.macro';
|
|
||||||
import { object } from 'yup';
|
import { object } from 'yup';
|
||||||
|
|
||||||
import type { InferModel } from '@/api/admin';
|
import type { Egg, EggVariable, LoadedEgg } from '@/api/admin/egg';
|
||||||
import type { Egg, EggVariable } from '@/api/admin/egg';
|
|
||||||
import { getEgg } from '@/api/admin/egg';
|
import { getEgg } from '@/api/admin/egg';
|
||||||
import type { Server } from '@/api/admin/server';
|
import type { Server } from '@/api/admin/server';
|
||||||
import { useServerFromRoute } 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 SpinnerOverlay from '@/components/elements/SpinnerOverlay';
|
||||||
import Label from '@/components/elements/Label';
|
import Label from '@/components/elements/Label';
|
||||||
import type { ApplicationStore } from '@/state';
|
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();
|
const { isSubmitting, setFieldValue } = useFormikContext();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (egg === null) {
|
if (egg === undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,10 +43,10 @@ function ServerStartupLineContainer({ egg, server }: { egg: Egg | null; server:
|
||||||
}, [egg]);
|
}, [egg]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AdminBox title={'Startup Command'} css={tw`relative w-full`}>
|
<AdminBox title={'Startup Command'} className="relative w-full">
|
||||||
<SpinnerOverlay visible={isSubmitting} />
|
<SpinnerOverlay visible={isSubmitting} />
|
||||||
|
|
||||||
<div css={tw`mb-6`}>
|
<div className="mb-6">
|
||||||
<Field
|
<Field
|
||||||
id={'startup'}
|
id={'startup'}
|
||||||
name={'startup'}
|
name={'startup'}
|
||||||
|
@ -69,12 +68,12 @@ function ServerStartupLineContainer({ egg, server }: { egg: Egg | null; server:
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ServerServiceContainer({
|
export function ServerServiceContainer({
|
||||||
egg,
|
selectedEggId,
|
||||||
setEgg,
|
setEgg,
|
||||||
nestId: _nestId,
|
nestId: _nestId,
|
||||||
}: {
|
}: {
|
||||||
egg: Egg | null;
|
selectedEggId?: number;
|
||||||
setEgg: (value: Egg | null) => void;
|
setEgg: (value: WithRelationships<Egg, 'variables'> | undefined) => void;
|
||||||
nestId: number;
|
nestId: number;
|
||||||
}) {
|
}) {
|
||||||
const { isSubmitting } = useFormikContext();
|
const { isSubmitting } = useFormikContext();
|
||||||
|
@ -82,14 +81,14 @@ export function ServerServiceContainer({
|
||||||
const [nestId, setNestId] = useState<number>(_nestId);
|
const [nestId, setNestId] = useState<number>(_nestId);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AdminBox title={'Service Configuration'} isLoading={isSubmitting} css={tw`w-full`}>
|
<AdminBox title={'Service Configuration'} isLoading={isSubmitting} className="w-full">
|
||||||
<div css={tw`mb-6`}>
|
<div className="mb-6">
|
||||||
<NestSelector selectedNestId={nestId} onNestSelect={setNestId} />
|
<NestSelector selectedNestId={nestId} onNestSelect={setNestId} />
|
||||||
</div>
|
</div>
|
||||||
<div css={tw`mb-6`}>
|
<div className="mb-6">
|
||||||
<EggSelect nestId={nestId} selectedEggId={egg?.id} onEggSelect={setEgg} />
|
<EggSelect nestId={nestId} selectedEggId={selectedEggId} onEggSelect={setEgg} />
|
||||||
</div>
|
</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™'} />
|
<FormikSwitch name={'skipScripts'} label={'Skip Egg Install Script'} description={'Soon™'} />
|
||||||
</div>
|
</div>
|
||||||
</AdminBox>
|
</AdminBox>
|
||||||
|
@ -100,10 +99,10 @@ export function ServerImageContainer() {
|
||||||
const { isSubmitting } = useFormikContext();
|
const { isSubmitting } = useFormikContext();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AdminBox title={'Image Configuration'} css={tw`relative w-full`}>
|
<AdminBox title={'Image Configuration'} className="relative w-full">
|
||||||
<SpinnerOverlay visible={isSubmitting} />
|
<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>
|
<div>
|
||||||
{/* TODO: make this a proper select but allow a custom image to be specified if needed. */}
|
{/* 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'} />
|
<Field id={'image'} name={'image'} label={'Docker Image'} type={'text'} />
|
||||||
|
@ -130,7 +129,7 @@ export function ServerVariableContainer({ variable, value }: { variable: EggVari
|
||||||
}, [value]);
|
}, [value]);
|
||||||
|
|
||||||
return (
|
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} />
|
<SpinnerOverlay visible={isSubmitting} />
|
||||||
|
|
||||||
<Field
|
<Field
|
||||||
|
@ -145,12 +144,14 @@ export function ServerVariableContainer({ variable, value }: { variable: EggVari
|
||||||
}
|
}
|
||||||
|
|
||||||
function ServerStartupForm({
|
function ServerStartupForm({
|
||||||
|
selectedEggId,
|
||||||
egg,
|
egg,
|
||||||
setEgg,
|
setEgg,
|
||||||
server,
|
server,
|
||||||
}: {
|
}: {
|
||||||
egg: Egg | null;
|
selectedEggId?: number;
|
||||||
setEgg: (value: Egg | null) => void;
|
egg?: LoadedEgg;
|
||||||
|
setEgg: (value: LoadedEgg | undefined) => void;
|
||||||
server: Server;
|
server: Server;
|
||||||
}) {
|
}) {
|
||||||
const {
|
const {
|
||||||
|
@ -161,22 +162,22 @@ function ServerStartupForm({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form>
|
<Form>
|
||||||
<div css={tw`flex flex-col mb-16`}>
|
<div className="flex flex-col mb-16">
|
||||||
<div css={tw`flex flex-row mb-6`}>
|
<div className="flex flex-row mb-6">
|
||||||
<ServerStartupLineContainer egg={egg} server={server} />
|
<ServerStartupLineContainer egg={egg} server={server} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div css={tw`grid grid-cols-1 md:grid-cols-2 gap-x-8 gap-y-6 mb-6`}>
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-8 gap-y-6 mb-6">
|
||||||
<div css={tw`flex`}>
|
<div className="flex">
|
||||||
<ServerServiceContainer egg={egg} setEgg={setEgg} nestId={server.nestId} />
|
<ServerServiceContainer selectedEggId={selectedEggId} setEgg={setEgg} nestId={server.nestId} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div css={tw`flex`}>
|
<div className="flex">
|
||||||
<ServerImageContainer />
|
<ServerImageContainer />
|
||||||
</div>
|
</div>
|
||||||
</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. */}
|
{/* This ensures that no variables are rendered unless the environment has a value for the variable. */}
|
||||||
{egg?.relationships.variables
|
{egg?.relationships.variables
|
||||||
?.filter(v => Object.keys(environment).find(e => e === v.environmentVariable) !== undefined)
|
?.filter(v => Object.keys(environment).find(e => e === v.environmentVariable) !== undefined)
|
||||||
|
@ -193,9 +194,9 @@ function ServerStartupForm({
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div css={tw`bg-neutral-700 rounded shadow-md py-2 pr-6 mt-6`}>
|
<div className="bg-neutral-700 rounded shadow-md py-2 pr-6 mt-6">
|
||||||
<div css={tw`flex flex-row`}>
|
<div className="flex flex-row">
|
||||||
<Button type="submit" size="small" css={tw`ml-auto`} disabled={isSubmitting || !isValid}>
|
<Button type="submit" size="small" className="ml-auto" disabled={isSubmitting || !isValid}>
|
||||||
Save Changes
|
Save Changes
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -210,10 +211,12 @@ export default () => {
|
||||||
const { clearFlashes, clearAndAddHttpError } = useStoreActions(
|
const { clearFlashes, clearAndAddHttpError } = useStoreActions(
|
||||||
(actions: Actions<ApplicationStore>) => actions.flashes,
|
(actions: Actions<ApplicationStore>) => actions.flashes,
|
||||||
);
|
);
|
||||||
const [egg, setEgg] = useState<InferModel<typeof getEgg> | null>(null);
|
const [egg, setEgg] = useState<LoadedEgg | undefined>(undefined);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!server) return;
|
if (!server) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
getEgg(server.eggId)
|
getEgg(server.eggId)
|
||||||
.then(egg => setEgg(egg))
|
.then(egg => setEgg(egg))
|
||||||
|
@ -249,10 +252,10 @@ export default () => {
|
||||||
validationSchema={object().shape({})}
|
validationSchema={object().shape({})}
|
||||||
>
|
>
|
||||||
<ServerStartupForm
|
<ServerStartupForm
|
||||||
|
selectedEggId={egg?.id ?? server.eggId}
|
||||||
egg={egg}
|
egg={egg}
|
||||||
// @ts-expect-error fix this
|
|
||||||
setEgg={setEgg}
|
setEgg={setEgg}
|
||||||
server={server}
|
server={server as Server}
|
||||||
/>
|
/>
|
||||||
</Formik>
|
</Formik>
|
||||||
);
|
);
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { useState } from 'react';
|
||||||
import Checkbox from '@/components/elements/inputs/Checkbox';
|
import Checkbox from '@/components/elements/inputs/Checkbox';
|
||||||
import { Dropdown } from '@/components/elements/dropdown';
|
import { Dropdown } from '@/components/elements/dropdown';
|
||||||
import { Dialog } from '@/components/elements/dialog';
|
import { Dialog } from '@/components/elements/dialog';
|
||||||
import { User } from '@definitions/admin';
|
import type { User } from '@definitions/admin';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
user: User;
|
user: User;
|
||||||
|
@ -12,7 +12,7 @@ interface Props {
|
||||||
onRowChange: (user: User, selected: boolean) => void;
|
onRowChange: (user: User, selected: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const UserTableRow = ({ user, selected, onRowChange }: Props) => {
|
function UserTableRow({ user, selected, onRowChange }: Props) {
|
||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -56,12 +56,14 @@ const UserTableRow = ({ user, selected, onRowChange }: Props) => {
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</td>
|
</td>
|
||||||
<td className={'whitespace-nowrap px-6 py-4'}>
|
<td className="whitespace-nowrap px-6 py-4">
|
||||||
<Dropdown>
|
<Dropdown>
|
||||||
<Dropdown.Button className={'px-2'}>
|
<Dropdown.Button className="px-2">
|
||||||
<DotsVerticalIcon />
|
<DotsVerticalIcon />
|
||||||
</Dropdown.Button>
|
</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={<SupportIcon />}>Reset Password</Dropdown.Item>
|
||||||
<Dropdown.Item icon={<LockOpenIcon />} disabled={!user.isUsingTwoFactor}>
|
<Dropdown.Item icon={<LockOpenIcon />} disabled={!user.isUsingTwoFactor}>
|
||||||
Disable 2-FA
|
Disable 2-FA
|
||||||
|
@ -76,6 +78,6 @@ const UserTableRow = ({ user, selected, onRowChange }: Props) => {
|
||||||
</tr>
|
</tr>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
export default UserTableRow;
|
export default UserTableRow;
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { LockOpenIcon, PlusIcon, SupportIcon, TrashIcon } from '@heroicons/react/solid';
|
import { LockOpenIcon, PlusIcon, SupportIcon, TrashIcon } from '@heroicons/react/solid';
|
||||||
import { Fragment, useEffect, useState } from 'react';
|
import { Fragment, useEffect, useState } from 'react';
|
||||||
|
import { NavLink } from 'react-router-dom';
|
||||||
|
|
||||||
import { useGetUsers } from '@/api/admin/users';
|
import { useGetUsers } from '@/api/admin/users';
|
||||||
import type { UUID } from '@/api/definitions';
|
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 filters = ['id', 'uuid', 'external_id', 'username', 'email'] as const;
|
||||||
|
|
||||||
const UsersContainer = () => {
|
function UsersContainer() {
|
||||||
const [search, setSearch] = useDebouncedState('', 500);
|
const [search, setSearch] = useDebouncedState('', 500);
|
||||||
const [selected, setSelected] = useState<UUID[]>([]);
|
const [selected, setSelected] = useState<UUID[]>([]);
|
||||||
const { data: users } = useGetUsers(
|
const { data: users } = useGetUsers(
|
||||||
|
@ -42,13 +43,16 @@ const UsersContainer = () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className={'mb-4 flex justify-end'}>
|
<div className="mb-4 flex justify-end">
|
||||||
<Button className={'shadow focus:ring-offset-2 focus:ring-offset-neutral-800'}>
|
<NavLink to="/admin/users/new">
|
||||||
Add User <PlusIcon className={'ml-2 h-5 w-5'} />
|
<Button className="shadow focus:ring-offset-2 focus:ring-offset-neutral-800">
|
||||||
|
Add User <PlusIcon className="ml-2 h-5 w-5" />
|
||||||
</Button>
|
</Button>
|
||||||
|
</NavLink>
|
||||||
</div>
|
</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
|
<Checkbox
|
||||||
checked={selectAllChecked}
|
checked={selectAllChecked}
|
||||||
disabled={!users?.items.length}
|
disabled={!users?.items.length}
|
||||||
|
@ -56,22 +60,18 @@ const UsersContainer = () => {
|
||||||
onChange={onSelectAll}
|
onChange={onSelectAll}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className={'flex-1'}>
|
<div className="flex-1">
|
||||||
<InputField
|
<InputField
|
||||||
type={'text'}
|
type="text"
|
||||||
name={'filter'}
|
name="filter"
|
||||||
placeholder={'Begin typing to filter...'}
|
placeholder="Begin typing to filter..."
|
||||||
className={'w-56 focus:w-96'}
|
className="w-56 focus:w-96"
|
||||||
onChange={e => setSearch(e.currentTarget.value)}
|
onChange={e => setSearch(e.currentTarget.value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Transition.Fade as={Fragment} show={selected.length > 0} duration={'duration-75'}>
|
<Transition.Fade as={Fragment} show={selected.length > 0} duration="duration-75">
|
||||||
<div
|
<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">
|
||||||
className={
|
<div className="flex-1">
|
||||||
'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
|
<Checkbox
|
||||||
checked={selectAllChecked}
|
checked={selectAllChecked}
|
||||||
indeterminate={selected.length !== users?.items.length}
|
indeterminate={selected.length !== users?.items.length}
|
||||||
|
@ -79,26 +79,26 @@ const UsersContainer = () => {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Button.Text shape={Shape.IconSquare}>
|
<Button.Text shape={Shape.IconSquare}>
|
||||||
<SupportIcon className={'h-4 w-4'} />
|
<SupportIcon className="h-4 w-4" />
|
||||||
</Button.Text>
|
</Button.Text>
|
||||||
<Button.Text shape={Shape.IconSquare}>
|
<Button.Text shape={Shape.IconSquare}>
|
||||||
<LockOpenIcon className={'h-4 w-4'} />
|
<LockOpenIcon className="h-4 w-4" />
|
||||||
</Button.Text>
|
</Button.Text>
|
||||||
<Button.Text shape={Shape.IconSquare}>
|
<Button.Text shape={Shape.IconSquare}>
|
||||||
<TrashIcon className={'h-4 w-4'} />
|
<TrashIcon className="h-4 w-4" />
|
||||||
</Button.Text>
|
</Button.Text>
|
||||||
</div>
|
</div>
|
||||||
</Transition.Fade>
|
</Transition.Fade>
|
||||||
</div>
|
</div>
|
||||||
<table className={'min-w-full rounded bg-neutral-700'}>
|
<table className="min-w-full rounded bg-neutral-700">
|
||||||
<thead className={'bg-neutral-900'}>
|
<thead className="bg-neutral-900">
|
||||||
<tr>
|
<tr>
|
||||||
<th scope={'col'} className={'w-8'} />
|
<th scope="col" className="w-8" />
|
||||||
<th scope={'col'} className={'w-full px-6 py-2 text-left'}>
|
<th scope="col" className="w-full px-6 py-2 text-left">
|
||||||
Email
|
Email
|
||||||
</th>
|
</th>
|
||||||
<th scope={'col'} />
|
<th scope="col" />
|
||||||
<th scope={'col'} />
|
<th scope="col" />
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
@ -111,10 +111,10 @@ const UsersContainer = () => {
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
{users && <TFootPaginated span={4} pagination={users.pagination} />}
|
{users ? <TFootPaginated span={4} pagination={users.pagination} /> : null}
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
export default UsersContainer;
|
export default UsersContainer;
|
||||||
|
|
|
@ -40,7 +40,7 @@ const inputStyle = css<Props>`
|
||||||
// Reset to normal styling.
|
// Reset to normal styling.
|
||||||
resize: none;
|
resize: none;
|
||||||
${tw`appearance-none outline-none w-full min-w-0`};
|
${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`};
|
${tw`bg-neutral-600 border-neutral-500 hover:border-neutral-400 text-neutral-200 shadow-none focus:ring-0`};
|
||||||
|
|
||||||
& + .input-help {
|
& + .input-help {
|
||||||
|
|
|
@ -1,27 +1,29 @@
|
||||||
import { ElementType, forwardRef, useMemo } from 'react';
|
|
||||||
import * as React from 'react';
|
|
||||||
import { Menu, Transition } from '@headlessui/react';
|
import { Menu, Transition } from '@headlessui/react';
|
||||||
import styles from './style.module.css';
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import DropdownItem from '@/components/elements/dropdown/DropdownItem';
|
import type { ElementType, ReactNode } from 'react';
|
||||||
import DropdownButton from '@/components/elements/dropdown/DropdownButton';
|
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 {
|
interface Props {
|
||||||
as?: ElementType;
|
as?: ElementType;
|
||||||
children: React.ReactNode;
|
children: ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DropdownGap = ({ invisible }: { invisible?: boolean }) => (
|
const DropdownGap = ({ invisible }: { invisible?: boolean }) => (
|
||||||
<div className={classNames('m-2 border', { 'border-neutral-700': !invisible, 'border-transparent': invisible })} />
|
<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;
|
type?: JSX.Element;
|
||||||
};
|
};
|
||||||
|
|
||||||
const Dropdown = forwardRef<typeof Menu, Props>(({ as, children }, ref) => {
|
const Dropdown = forwardRef<typeof Menu, Props>(({ as, children }, ref) => {
|
||||||
const [Button, items] = useMemo(() => {
|
const [Button, items] = useMemo(() => {
|
||||||
const list = React.Children.toArray(children) as unknown as TypedChild[];
|
const list = ReactChildren.toArray(children) as unknown as TypedChild[];
|
||||||
|
|
||||||
return [
|
return [
|
||||||
list.filter(child => child.type === DropdownButton),
|
list.filter(child => child.type === DropdownButton),
|
||||||
|
@ -34,18 +36,18 @@ const Dropdown = forwardRef<typeof Menu, Props>(({ as, children }, ref) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Menu as={as || 'div'} className={styles.menu} ref={ref}>
|
<Menu as={as ?? 'div'} className={styles.menu} ref={ref}>
|
||||||
{Button}
|
{Button}
|
||||||
<Transition
|
<Transition
|
||||||
enter={'transition duration-100 ease-out'}
|
enter="transition duration-100 ease-out"
|
||||||
enterFrom={'transition scale-95 opacity-0'}
|
enterFrom="transition scale-95 opacity-0"
|
||||||
enterTo={'transform scale-100 opacity-100'}
|
enterTo="transform scale-100 opacity-100"
|
||||||
leave={'transition duration-75 ease-out'}
|
leave="transition duration-75 ease-out"
|
||||||
leaveFrom={'transform scale-100 opacity-100'}
|
leaveFrom="transform scale-100 opacity-100"
|
||||||
leaveTo={'transform scale-95 opacity-0'}
|
leaveTo="transform scale-95 opacity-0"
|
||||||
>
|
>
|
||||||
<Menu.Items className={classNames(styles.items_container, 'w-56')}>
|
<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>
|
</Menu.Items>
|
||||||
</Transition>
|
</Transition>
|
||||||
</Menu>
|
</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 { 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 {
|
interface Props {
|
||||||
className?: string;
|
className?: string;
|
||||||
animate?: boolean;
|
animate?: boolean;
|
||||||
children: React.ReactNode;
|
children: ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ({ className, animate = true, children }: Props) => (
|
function DropdownButton({ className, animate = true, children }: Props) {
|
||||||
<Menu.Button className={classNames(styles.button, className || 'px-4')}>
|
return (
|
||||||
|
<Menu.Button className={classNames(styles.button, className ?? 'px-4')}>
|
||||||
{typeof children === 'string' ? (
|
{typeof children === 'string' ? (
|
||||||
<>
|
<>
|
||||||
<span className={'mr-2'}>{children}</span>
|
<span className="mr-2">{children}</span>
|
||||||
<ChevronDownIcon aria-hidden={'true'} data-animated={animate.toString()} />
|
<ChevronDownIcon aria-hidden="true" data-animated={animate.toString()} />
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
children
|
children
|
||||||
)}
|
)}
|
||||||
</Menu.Button>
|
</Menu.Button>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { DropdownButton };
|
||||||
|
|
|
@ -1,26 +1,32 @@
|
||||||
import { forwardRef } from 'react';
|
|
||||||
import * as React from 'react';
|
|
||||||
import { Menu } from '@headlessui/react';
|
import { Menu } from '@headlessui/react';
|
||||||
import styles from './style.module.css';
|
|
||||||
import classNames from 'classnames';
|
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 {
|
interface Props {
|
||||||
children: React.ReactNode | ((opts: { active: boolean; disabled: boolean }) => JSX.Element);
|
children: ReactNode | ((opts: { active: boolean; disabled: boolean }) => JSX.Element);
|
||||||
danger?: boolean;
|
danger?: boolean;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
className?: string;
|
className?: string;
|
||||||
icon?: JSX.Element;
|
icon?: JSX.Element;
|
||||||
onClick?: (e: React.MouseEvent) => void;
|
onClick?: (e: MouseEvent) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DropdownItem = forwardRef<HTMLAnchorElement, Props>(
|
const DropdownItem = forwardRef<HTMLAnchorElement | HTMLButtonElement, Props & Partial<Omit<NavLinkProps, 'children'>>>(
|
||||||
({ disabled, danger, className, onClick, children, icon: IconComponent }, ref) => {
|
({ disabled, danger, className, onClick, children, icon: IconComponent, ...props }, ref) => {
|
||||||
return (
|
return (
|
||||||
<Menu.Item disabled={disabled}>
|
<Menu.Item disabled={disabled}>
|
||||||
{({ disabled, active }) => (
|
{({ disabled, active }) => (
|
||||||
<a
|
<>
|
||||||
ref={ref}
|
{'to' in props && props.to !== undefined ? (
|
||||||
href={'#'}
|
<NavLink
|
||||||
|
{...props}
|
||||||
|
to={props.to}
|
||||||
|
ref={ref as unknown as Ref<HTMLAnchorElement>}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
styles.menu_item,
|
styles.menu_item,
|
||||||
{
|
{
|
||||||
|
@ -33,11 +39,30 @@ const DropdownItem = forwardRef<HTMLAnchorElement, Props>(
|
||||||
>
|
>
|
||||||
{IconComponent}
|
{IconComponent}
|
||||||
{typeof children === 'function' ? children({ disabled, active }) : children}
|
{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>
|
</Menu.Item>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
export default DropdownItem;
|
export { DropdownItem };
|
||||||
|
|
|
@ -119,14 +119,14 @@ export default ({ database, className }: Props) => {
|
||||||
<Can action={'database.view_password'}>
|
<Can action={'database.view_password'}>
|
||||||
<div css={tw`mt-6`}>
|
<div css={tw`mt-6`}>
|
||||||
<Label>Password</Label>
|
<Label>Password</Label>
|
||||||
<CopyOnClick text={database.password}>
|
<CopyOnClick text={database.password} showInNotification={false}>
|
||||||
<Input type={'text'} readOnly value={database.password} />
|
<Input type={'text'} readOnly value={database.password} />
|
||||||
</CopyOnClick>
|
</CopyOnClick>
|
||||||
</div>
|
</div>
|
||||||
</Can>
|
</Can>
|
||||||
<div css={tw`mt-6`}>
|
<div css={tw`mt-6`}>
|
||||||
<Label>JDBC Connection String</Label>
|
<Label>JDBC Connection String</Label>
|
||||||
<CopyOnClick text={jdbcConnectionString}>
|
<CopyOnClick text={jdbcConnectionString} showInNotification={false}>
|
||||||
<Input type={'text'} readOnly value={jdbcConnectionString} />
|
<Input type={'text'} readOnly value={jdbcConnectionString} />
|
||||||
</CopyOnClick>
|
</CopyOnClick>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -18,11 +18,12 @@ import { ServerContext } from '@/state/server';
|
||||||
import styles from './style.module.css';
|
import styles from './style.module.css';
|
||||||
|
|
||||||
function Clickable({ file, children }: { file: FileObject; children: ReactNode }) {
|
function Clickable({ file, children }: { file: FileObject; children: ReactNode }) {
|
||||||
|
const [canRead] = usePermissions(['file.read']);
|
||||||
const [canReadContents] = usePermissions(['file.read-content']);
|
const [canReadContents] = usePermissions(['file.read-content']);
|
||||||
const id = ServerContext.useStoreState(state => state.server.data!.id);
|
const id = ServerContext.useStoreState(state => state.server.data!.id);
|
||||||
const directory = ServerContext.useStoreState(state => state.files.directory);
|
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>
|
<div className={styles.details}>{children}</div>
|
||||||
) : (
|
) : (
|
||||||
<NavLink
|
<NavLink
|
||||||
|
|
|
@ -93,7 +93,7 @@ const MassActionsBar = () => {
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<Portal>
|
<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>
|
<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">
|
<div className="pointer-events-auto flex items-center space-x-4 rounded bg-black/50 p-4">
|
||||||
<Button onClick={() => setShowMove(true)}>Move</Button>
|
<Button onClick={() => setShowMove(true)}>Move</Button>
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import type { ComponentType } from 'react';
|
import type { ComponentType } from 'react';
|
||||||
import { lazy } from 'react';
|
import { lazy } from 'react';
|
||||||
|
|
||||||
import ServerConsole from '@/components/server/console/ServerConsoleContainer';
|
|
||||||
import DatabasesContainer from '@/components/server/databases/DatabasesContainer';
|
import DatabasesContainer from '@/components/server/databases/DatabasesContainer';
|
||||||
import ScheduleContainer from '@/components/server/schedules/ScheduleContainer';
|
import ScheduleContainer from '@/components/server/schedules/ScheduleContainer';
|
||||||
import UsersContainer from '@/components/server/users/UsersContainer';
|
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
|
// These specific lazy loaded routes are to avoid loading in heavy screens
|
||||||
// for the server dashboard when they're only needed for specific instances.
|
// 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 FileEditContainer = lazy(() => import('@/components/server/files/FileEditContainer'));
|
||||||
const ScheduleEditContainer = lazy(() => import('@/components/server/schedules/ScheduleEditContainer'));
|
const ScheduleEditContainer = lazy(() => import('@/components/server/schedules/ScheduleEditContainer'));
|
||||||
|
|
||||||
|
@ -86,7 +86,7 @@ export default {
|
||||||
path: '',
|
path: '',
|
||||||
permission: null,
|
permission: null,
|
||||||
name: 'Console',
|
name: 'Console',
|
||||||
component: ServerConsole,
|
component: ServerConsoleContainer,
|
||||||
end: true,
|
end: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -9,7 +9,13 @@ with pkgs;
|
||||||
alejandra
|
alejandra
|
||||||
composer
|
composer
|
||||||
nodejs-18_x
|
nodejs-18_x
|
||||||
nodePackages.yarn
|
nodePackages.pnpm
|
||||||
php81WithExtensions
|
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
|
* Endpoints that should return a 403 error when the key does not have write
|
||||||
* permissions for user management.
|
* permissions for user management.
|
||||||
*/
|
*/
|
||||||
public function userWriteEndpointsDataProvider(): array
|
public static function userWriteEndpointsDataProvider(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
['postJson', '/api/application/users'],
|
['postJson', '/api/application/users'],
|
||||||
|
|
|
@ -241,7 +241,7 @@ class ApiKeyControllerTest extends ClientApiIntegrationTestCase
|
||||||
* Provides some different IP address combinations that can be used when
|
* Provides some different IP address combinations that can be used when
|
||||||
* testing that we accept the expected IP values.
|
* testing that we accept the expected IP values.
|
||||||
*/
|
*/
|
||||||
public function validIPAddressDataProvider(): array
|
public static function validIPAddressDataProvider(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
[[]],
|
[[]],
|
||||||
|
|
|
@ -331,7 +331,7 @@ class ClientControllerTest extends ClientApiIntegrationTestCase
|
||||||
$response->assertJsonPath('data.0.attributes.relationships.allocations.data.0.attributes.notes', null);
|
$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']];
|
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();
|
$this->actingAs($user)->json($method, $this->link($server3, '/network/allocations/' . $allocation3->id . $endpoint))->assertNotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function methodDataProvider(): array
|
public static function methodDataProvider(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
['POST', ''],
|
['POST', ''],
|
||||||
|
|
|
@ -86,7 +86,7 @@ class CreateNewAllocationTest extends ClientApiIntegrationTestCase
|
||||||
->assertJsonPath('errors.0.detail', 'Cannot assign additional allocations to this server: limit has been reached.');
|
->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]], [[]]];
|
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();
|
$this->actingAs($user)->deleteJson($this->link($server, "/network/allocations/{$server2->allocation_id}"))->assertNotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public static function permissionDataProvider(): array
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function permissionDataProvider()
|
|
||||||
{
|
{
|
||||||
return [[[Permission::ACTION_ALLOCATION_DELETE]], [[]]];
|
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();
|
$this->actingAs($user)->json($method, $this->link($server3, '/backups/' . $backup3->uuid . $endpoint))->assertNotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function methodDataProvider(): array
|
public static function methodDataProvider(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
['GET', ''],
|
['GET', ''],
|
||||||
|
|
|
@ -58,7 +58,7 @@ class DatabaseAuthorizationTest extends ClientApiIntegrationTestCase
|
||||||
$this->actingAs($user)->json($method, $this->link($server3, '/databases/' . $hashids->encode($database3->id) . $endpoint))->assertNotFound();
|
$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 [
|
return [
|
||||||
['POST', '/rotate-password'],
|
['POST', '/rotate-password'],
|
||||||
|
|
|
@ -133,7 +133,7 @@ class NetworkAllocationControllerTest extends ClientApiIntegrationTestCase
|
||||||
->assertForbidden();
|
->assertForbidden();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function updatePermissionsDataProvider(): array
|
public static function updatePermissionsDataProvider(): array
|
||||||
{
|
{
|
||||||
return [[[]], [[Permission::ACTION_ALLOCATION_UPDATE]]];
|
return [[[]], [[Permission::ACTION_ALLOCATION_UPDATE]]];
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,7 +72,7 @@ class PowerControllerTest extends ClientApiIntegrationTestCase
|
||||||
/**
|
/**
|
||||||
* Returns invalid permission combinations for a given power action.
|
* Returns invalid permission combinations for a given power action.
|
||||||
*/
|
*/
|
||||||
public function invalidPermissionDataProvider(): array
|
public static function invalidPermissionDataProvider(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
['start', [Permission::ACTION_CONTROL_STOP, Permission::ACTION_CONTROL_RESTART]],
|
['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 [
|
return [
|
||||||
['start', Permission::ACTION_CONTROL_START],
|
['start', Permission::ACTION_CONTROL_START],
|
||||||
|
|
|
@ -89,7 +89,7 @@ class CreateServerScheduleTest extends ClientApiIntegrationTestCase
|
||||||
->assertForbidden();
|
->assertForbidden();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function permissionsDataProvider(): array
|
public static function permissionsDataProvider(): array
|
||||||
{
|
{
|
||||||
return [[[]], [[Permission::ACTION_SCHEDULE_CREATE]]];
|
return [[[]], [[Permission::ACTION_SCHEDULE_CREATE]]];
|
||||||
}
|
}
|
||||||
|
|
|
@ -77,7 +77,7 @@ class DeleteServerScheduleTest extends ClientApiIntegrationTestCase
|
||||||
$this->assertDatabaseHas('schedules', ['id' => $schedule->id]);
|
$this->assertDatabaseHas('schedules', ['id' => $schedule->id]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function permissionsDataProvider(): array
|
public static function permissionsDataProvider(): array
|
||||||
{
|
{
|
||||||
return [[[]], [[Permission::ACTION_SCHEDULE_DELETE]]];
|
return [[[]], [[Permission::ACTION_SCHEDULE_DELETE]]];
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,7 +64,7 @@ class ExecuteScheduleTest extends ClientApiIntegrationTestCase
|
||||||
$this->actingAs($user)->postJson($this->link($schedule, '/execute'))->assertForbidden();
|
$this->actingAs($user)->postJson($this->link($schedule, '/execute'))->assertForbidden();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function permissionsDataProvider(): array
|
public static function permissionsDataProvider(): array
|
||||||
{
|
{
|
||||||
return [[[]], [[Permission::ACTION_SCHEDULE_UPDATE]]];
|
return [[[]], [[Permission::ACTION_SCHEDULE_UPDATE]]];
|
||||||
}
|
}
|
||||||
|
|
|
@ -89,7 +89,7 @@ class GetServerSchedulesTest extends ClientApiIntegrationTestCase
|
||||||
->assertForbidden();
|
->assertForbidden();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function permissionsDataProvider(): array
|
public static function permissionsDataProvider(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
[[], false],
|
[[], false],
|
||||||
|
|
|
@ -54,7 +54,7 @@ class ScheduleAuthorizationTest extends ClientApiIntegrationTestCase
|
||||||
$this->actingAs($user)->json($method, $this->link($server3, '/schedules/' . $schedule3->id . $endpoint))->assertNotFound();
|
$this->actingAs($user)->json($method, $this->link($server3, '/schedules/' . $schedule3->id . $endpoint))->assertNotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function methodDataProvider(): array
|
public static function methodDataProvider(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
['GET', ''],
|
['GET', ''],
|
||||||
|
|
|
@ -109,7 +109,7 @@ class UpdateServerScheduleTest extends ClientApiIntegrationTestCase
|
||||||
$this->assertFalse($schedule->is_processing);
|
$this->assertFalse($schedule->is_processing);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function permissionsDataProvider(): array
|
public static function permissionsDataProvider(): array
|
||||||
{
|
{
|
||||||
return [[[]], [[Permission::ACTION_SCHEDULE_UPDATE]]];
|
return [[[]], [[Permission::ACTION_SCHEDULE_UPDATE]]];
|
||||||
}
|
}
|
||||||
|
|
|
@ -170,7 +170,7 @@ class CreateServerScheduleTaskTest extends ClientApiIntegrationTestCase
|
||||||
->assertForbidden();
|
->assertForbidden();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function permissionsDataProvider(): array
|
public static function permissionsDataProvider(): array
|
||||||
{
|
{
|
||||||
return [[[]], [[Permission::ACTION_SCHEDULE_UPDATE]]];
|
return [[[]], [[Permission::ACTION_SCHEDULE_UPDATE]]];
|
||||||
}
|
}
|
||||||
|
|
|
@ -112,12 +112,12 @@ class SettingsControllerTest extends ClientApiIntegrationTestCase
|
||||||
$this->assertTrue($server->isInstalled());
|
$this->assertTrue($server->isInstalled());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function renamePermissionsDataProvider(): array
|
public static function renamePermissionsDataProvider(): array
|
||||||
{
|
{
|
||||||
return [[[]], [[Permission::ACTION_SETTINGS_RENAME]]];
|
return [[[]], [[Permission::ACTION_SETTINGS_RENAME]]];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function reinstallPermissionsDataProvider(): array
|
public static function reinstallPermissionsDataProvider(): array
|
||||||
{
|
{
|
||||||
return [[[]], [[Permission::ACTION_SETTINGS_REINSTALL]]];
|
return [[[]], [[Permission::ACTION_SETTINGS_REINSTALL]]];
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,7 +58,7 @@ class GetStartupAndVariablesTest extends ClientApiIntegrationTestCase
|
||||||
$this->actingAs($user2)->getJson($this->link($server) . '/startup')->assertNotFound();
|
$this->actingAs($user2)->getJson($this->link($server) . '/startup')->assertNotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function permissionsDataProvider(): array
|
public static function permissionsDataProvider(): array
|
||||||
{
|
{
|
||||||
return [[[]], [[Permission::ACTION_STARTUP_READ]]];
|
return [[[]], [[Permission::ACTION_STARTUP_READ]]];
|
||||||
}
|
}
|
||||||
|
|
|
@ -149,7 +149,7 @@ class UpdateStartupVariableTest extends ClientApiIntegrationTestCase
|
||||||
$this->actingAs($user2)->putJson($this->link($server) . '/startup/variable')->assertNotFound();
|
$this->actingAs($user2)->putJson($this->link($server) . '/startup/variable')->assertNotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function permissionsDataProvider(): array
|
public static function permissionsDataProvider(): array
|
||||||
{
|
{
|
||||||
return [[[]], [[Permission::ACTION_STARTUP_UPDATE]]];
|
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.');
|
$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]]];
|
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();
|
$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']];
|
return [['GET'], ['POST'], ['DELETE']];
|
||||||
}
|
}
|
||||||
|
|
|
@ -213,7 +213,7 @@ class SftpAuthenticationControllerTest extends IntegrationTestCase
|
||||||
$this->post('/api/remote/sftp/auth', $data)->assertForbidden();
|
$this->post('/api/remote/sftp/auth', $data)->assertForbidden();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function authorizationTypeDataProvider(): array
|
public static function authorizationTypeDataProvider(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'password auth' => ['password'],
|
'password auth' => ['password'],
|
||||||
|
@ -221,7 +221,7 @@ class SftpAuthenticationControllerTest extends IntegrationTestCase
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function serverStateDataProvider(): array
|
public static function serverStateDataProvider(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'installing' => [Server::STATUS_INSTALLING],
|
'installing' => [Server::STATUS_INSTALLING],
|
||||||
|
|
|
@ -37,7 +37,7 @@ class RunTaskJobTest extends IntegrationTestCase
|
||||||
|
|
||||||
$job = new RunTaskJob($task);
|
$job = new RunTaskJob($task);
|
||||||
|
|
||||||
Bus::dispatchNow($job);
|
Bus::dispatchSync($job);
|
||||||
|
|
||||||
$task->refresh();
|
$task->refresh();
|
||||||
$schedule->refresh();
|
$schedule->refresh();
|
||||||
|
@ -61,7 +61,7 @@ class RunTaskJobTest extends IntegrationTestCase
|
||||||
|
|
||||||
$this->expectException(\InvalidArgumentException::class);
|
$this->expectException(\InvalidArgumentException::class);
|
||||||
$this->expectExceptionMessage('Invalid task action provided: foobar');
|
$this->expectExceptionMessage('Invalid task action provided: foobar');
|
||||||
Bus::dispatchNow($job);
|
Bus::dispatchSync($job);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -95,7 +95,7 @@ class RunTaskJobTest extends IntegrationTestCase
|
||||||
}))->andReturnSelf();
|
}))->andReturnSelf();
|
||||||
$mock->expects('send')->with('start')->andReturn(new Response());
|
$mock->expects('send')->with('start')->andReturn(new Response());
|
||||||
|
|
||||||
Bus::dispatchNow(new RunTaskJob($task, $isManualRun));
|
Bus::dispatchSync(new RunTaskJob($task, $isManualRun));
|
||||||
|
|
||||||
$task->refresh();
|
$task->refresh();
|
||||||
$schedule->refresh();
|
$schedule->refresh();
|
||||||
|
@ -133,7 +133,7 @@ class RunTaskJobTest extends IntegrationTestCase
|
||||||
$this->expectException(DaemonConnectionException::class);
|
$this->expectException(DaemonConnectionException::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
Bus::dispatchNow(new RunTaskJob($task));
|
Bus::dispatchSync(new RunTaskJob($task));
|
||||||
|
|
||||||
if ($continueOnFailure) {
|
if ($continueOnFailure) {
|
||||||
$task->refresh();
|
$task->refresh();
|
||||||
|
@ -165,7 +165,7 @@ class RunTaskJobTest extends IntegrationTestCase
|
||||||
'payload' => 'start',
|
'payload' => 'start',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
Bus::dispatchNow(new RunTaskJob($task));
|
Bus::dispatchSync(new RunTaskJob($task));
|
||||||
|
|
||||||
$task->refresh();
|
$task->refresh();
|
||||||
$schedule->refresh();
|
$schedule->refresh();
|
||||||
|
@ -175,7 +175,7 @@ class RunTaskJobTest extends IntegrationTestCase
|
||||||
$this->assertTrue(Carbon::now()->isSameAs(\DateTimeInterface::ATOM, $schedule->last_run_at));
|
$this->assertTrue(Carbon::now()->isSameAs(\DateTimeInterface::ATOM, $schedule->last_run_at));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function isManualRunDataProvider(): array
|
public static function isManualRunDataProvider(): array
|
||||||
{
|
{
|
||||||
return [[true], [false]];
|
return [[true], [false]];
|
||||||
}
|
}
|
||||||
|
|
|
@ -194,7 +194,7 @@ class DatabaseManagementServiceTest extends IntegrationTestCase
|
||||||
$this->assertDatabaseMissing('databases', ['server_id' => $server->id]);
|
$this->assertDatabaseMissing('databases', ['server_id' => $server->id]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function invalidDataDataProvider(): array
|
public static function invalidDataDataProvider(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
[[]],
|
[[]],
|
||||||
|
|
|
@ -141,7 +141,7 @@ class DeployServerDatabaseServiceTest extends IntegrationTestCase
|
||||||
$this->assertInstanceOf(Database::class, $response);
|
$this->assertInstanceOf(Database::class, $response);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function invalidDataProvider(): array
|
public static function invalidDataProvider(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
[['remote' => '%']],
|
[['remote' => '%']],
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue