mirror of
https://github.com/pelican-dev/panel.git
synced 2026-07-16 04:03:50 +03:00
Compare commits
197 Commits
v1.0.0-bet
...
boy132/fil
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e0f38a8c99 | ||
|
|
1f2ff09a8b | ||
|
|
f44ad34dc3 | ||
|
|
4cd8b5767a | ||
|
|
4ff5adc2a6 | ||
|
|
b8bbf29a7f | ||
|
|
c5181c9c84 | ||
|
|
54e583e0e4 | ||
|
|
614153a1ed | ||
|
|
7f14a72002 | ||
|
|
6614ae5ea1 | ||
|
|
9e0f7cee35 | ||
|
|
f9793c36fd | ||
|
|
d4e974b806 | ||
|
|
00a56398fb | ||
|
|
ab9535eb37 | ||
|
|
a9cb16e042 | ||
|
|
483145aac8 | ||
|
|
5953a268c4 | ||
|
|
2486446e23 | ||
|
|
553c87597f | ||
|
|
0f1aa46aeb | ||
|
|
2771ccc421 | ||
|
|
fab5da496f | ||
|
|
ed7d4f5a6b | ||
|
|
d5df688638 | ||
|
|
0e75027c12 | ||
|
|
475a454806 | ||
|
|
3a015e8081 | ||
|
|
29d774eae4 | ||
|
|
2e7e4bb6d4 | ||
|
|
420ae022e9 | ||
|
|
666a409b66 | ||
|
|
2b9c08dc5d | ||
|
|
21ed58839c | ||
|
|
69e88a2ea7 | ||
|
|
cc2aa38ad4 | ||
|
|
1bd4492cdc | ||
|
|
90fbc87a8a | ||
|
|
ac3db5af71 | ||
|
|
a46443e5e4 | ||
|
|
f791a3a686 | ||
|
|
b3fe96f262 | ||
|
|
4d114571e0 | ||
|
|
136224224c | ||
|
|
28452e2820 | ||
|
|
be6abcefe7 | ||
|
|
2a328a24ac | ||
|
|
02e4527b84 | ||
|
|
f676b6c794 | ||
|
|
cb21f13059 | ||
|
|
13bb47e6eb | ||
|
|
447a8c1ff7 | ||
|
|
a99f18b189 | ||
|
|
8f01095f9d | ||
|
|
110fdd2bdb | ||
|
|
81bb09f4cf | ||
|
|
dcba4a91d0 | ||
|
|
8f7dbe5e16 | ||
|
|
92bfccb2a4 | ||
|
|
11a1581a1d | ||
|
|
38620a95f4 | ||
|
|
d520224bc4 | ||
|
|
4464ccb469 | ||
|
|
4ad305c709 | ||
|
|
4b22010576 | ||
|
|
eb4e947ddb | ||
|
|
2bbfb0eef9 | ||
|
|
2a64ea8536 | ||
|
|
4fdbbff74b | ||
|
|
2ed891633c | ||
|
|
58dcbeac0b | ||
|
|
91c5ddb2bd | ||
|
|
562be98b20 | ||
|
|
fd3b8a7ab3 | ||
|
|
1817383bf5 | ||
|
|
d39a0c4464 | ||
|
|
2e48095379 | ||
|
|
06c662988a | ||
|
|
98d7158dfc | ||
|
|
e01d9f2cf3 | ||
|
|
b693d0e728 | ||
|
|
612041e1f8 | ||
|
|
64bcdb514b | ||
|
|
c1105db702 | ||
|
|
761492d09f | ||
|
|
e35ce1e79d | ||
|
|
42c127c004 | ||
|
|
593f209142 | ||
|
|
9bf5b2cf0a | ||
|
|
f76e864a30 | ||
|
|
01cfa31ee1 | ||
|
|
dead664e4d | ||
|
|
1bbbcd0e25 | ||
|
|
677d2f742c | ||
|
|
650fb16d2d | ||
|
|
58814ea782 | ||
|
|
0918ed308b | ||
|
|
85d5f2ec3f | ||
|
|
810f237547 | ||
|
|
8f191890a1 | ||
|
|
160e0e54f5 | ||
|
|
fdcfbb00ca | ||
|
|
44f6cf8928 | ||
|
|
cf2a26bbf0 | ||
|
|
adb6678eee | ||
|
|
9539e21b39 | ||
|
|
33660f635f | ||
|
|
8c475ed95f | ||
|
|
d43cb1d180 | ||
|
|
fe55dbd200 | ||
|
|
0bb4503c2b | ||
|
|
3c1168beb5 | ||
|
|
1a092aedc8 | ||
|
|
8c99a8030f | ||
|
|
6e53b1cd7d | ||
|
|
4042e0416b | ||
|
|
cc8973cf00 | ||
|
|
8ebe75b947 | ||
|
|
f8144407d1 | ||
|
|
e431ccb66a | ||
|
|
9291bb4477 | ||
|
|
e8c80ae420 | ||
|
|
f1be003276 | ||
|
|
e532a9a180 | ||
|
|
41fdd7bc8e | ||
|
|
9b01203b7c | ||
|
|
ab4eadec32 | ||
|
|
789c4c7284 | ||
|
|
b1a39f1724 | ||
|
|
6a548c09a0 | ||
|
|
55bda569cc | ||
|
|
adf1249086 | ||
|
|
dbf77bf146 | ||
|
|
a34bf9fd49 | ||
|
|
7a9deba0e1 | ||
|
|
159bfe2210 | ||
|
|
a821db8aae | ||
|
|
1556f8efb8 | ||
|
|
57c2aa6f21 | ||
|
|
36de4c3786 | ||
|
|
26312e3897 | ||
|
|
a477c89025 | ||
|
|
93e81c26a9 | ||
|
|
23e91e8df3 | ||
|
|
833294bfaf | ||
|
|
abaeeff86d | ||
|
|
dd77555c42 | ||
|
|
297ecb544d | ||
|
|
e14bb7d030 | ||
|
|
c770937880 | ||
|
|
426643eaa6 | ||
|
|
3ca0f64e6e | ||
|
|
8e8ce3b50f | ||
|
|
b1e9cadc10 | ||
|
|
7bf1f18c2d | ||
|
|
6fe7d29960 | ||
|
|
15172b1d86 | ||
|
|
9f744d39a2 | ||
|
|
b79511568e | ||
|
|
adeb1b4217 | ||
|
|
d064bf9734 | ||
|
|
107286d618 | ||
|
|
a3203f7dda | ||
|
|
e9abd56f7a | ||
|
|
675ab057b0 | ||
|
|
943d9d3ef5 | ||
|
|
c06a525be2 | ||
|
|
2ff5fdf831 | ||
|
|
0e810f3110 | ||
|
|
eadbe6e8fd | ||
|
|
53aa49b11a | ||
|
|
6ae4f007c8 | ||
|
|
6b9d683f06 | ||
|
|
3b24e22316 | ||
|
|
bd012f52a9 | ||
|
|
af202d9827 | ||
|
|
6ebeb40ba0 | ||
|
|
333eeda065 | ||
|
|
fcfafadec7 | ||
|
|
76b6118fd1 | ||
|
|
3141fe61b4 | ||
|
|
bed9dbeb2b | ||
|
|
976cb00c0d | ||
|
|
e3534bbb29 | ||
|
|
5740c93032 | ||
|
|
d72e075977 | ||
|
|
9af608f808 | ||
|
|
ac36e7a4b5 | ||
|
|
b1c64e2ef1 | ||
|
|
da2e930d4d | ||
|
|
460a5dfaf8 | ||
|
|
576f04be58 | ||
|
|
43fb030133 | ||
|
|
ae054f6e9b | ||
|
|
fef91791c3 | ||
|
|
1d5ace3a6d |
8
.github/workflows/build.yaml
vendored
8
.github/workflows/build.yaml
vendored
@@ -13,15 +13,15 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
node-version: [20, 22]
|
||||
node-version: [22, 24]
|
||||
steps:
|
||||
- name: Code Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php }}
|
||||
php-version: "8.5"
|
||||
extensions: bcmath, curl, gd, mbstring, mysql, openssl, pdo, tokenizer, xml, zip
|
||||
tools: composer:v2
|
||||
coverage: none
|
||||
@@ -30,7 +30,7 @@ jobs:
|
||||
run: composer install --no-interaction --no-suggest --no-progress --no-autoloader --no-scripts --no-dev
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: "yarn"
|
||||
|
||||
83
.github/workflows/ci.yaml
vendored
83
.github/workflows/ci.yaml
vendored
@@ -26,13 +26,13 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
php: [8.2, 8.3, 8.4]
|
||||
php: [8.3, 8.4, 8.5]
|
||||
env:
|
||||
DB_CONNECTION: sqlite
|
||||
DB_DATABASE: testing.sqlite
|
||||
steps:
|
||||
- name: Code Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Get cache directory
|
||||
id: composer-cache
|
||||
@@ -40,7 +40,7 @@ jobs:
|
||||
echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Cache
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: ${{ steps.composer-cache.outputs.dir }}
|
||||
key: ${{ runner.os }}-composer-${{ matrix.php }}-${{ hashFiles('**/composer.lock') }}
|
||||
@@ -61,14 +61,17 @@ jobs:
|
||||
- name: Create SQLite file
|
||||
run: touch database/testing.sqlite
|
||||
|
||||
- name: Run Migrations
|
||||
run: php artisan migrate --force --seed
|
||||
|
||||
- name: Unit tests
|
||||
run: vendor/bin/pest tests/Unit
|
||||
run: vendor/bin/pest tests/Unit --parallel --do-not-fail-on-phpunit-warning
|
||||
env:
|
||||
DB_HOST: UNIT_NO_DB
|
||||
SKIP_MIGRATIONS: true
|
||||
|
||||
- name: Integration tests
|
||||
run: vendor/bin/pest tests/Integration
|
||||
run: vendor/bin/pest tests/Integration --parallel --do-not-fail-on-phpunit-warning
|
||||
|
||||
mysql:
|
||||
name: MySQL
|
||||
@@ -76,8 +79,8 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
php: [8.2, 8.3, 8.4]
|
||||
database: ["mysql:8"]
|
||||
php: [8.5]
|
||||
database: ["mysql:8.4", "mysql:9.6"]
|
||||
services:
|
||||
database:
|
||||
image: ${{ matrix.database }}
|
||||
@@ -86,7 +89,7 @@ jobs:
|
||||
MYSQL_DATABASE: testing
|
||||
ports:
|
||||
- 3306
|
||||
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
|
||||
options: --tmpfs /var/lib/mysql:rw --health-cmd="mysqladmin ping" --health-interval=2s --health-timeout=5s --health-retries=30
|
||||
env:
|
||||
DB_CONNECTION: mysql
|
||||
DB_HOST: 127.0.0.1
|
||||
@@ -94,7 +97,7 @@ jobs:
|
||||
DB_USERNAME: root
|
||||
steps:
|
||||
- name: Code Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Get cache directory
|
||||
id: composer-cache
|
||||
@@ -102,7 +105,7 @@ jobs:
|
||||
echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Cache
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: ${{ steps.composer-cache.outputs.dir }}
|
||||
key: ${{ runner.os }}-composer-${{ matrix.php }}-${{ hashFiles('**/composer.lock') }}
|
||||
@@ -120,14 +123,14 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: composer install --no-interaction --no-suggest --no-progress --no-scripts
|
||||
|
||||
- name: Unit tests
|
||||
run: vendor/bin/pest tests/Unit
|
||||
- name: Run Migrations
|
||||
run: php artisan migrate --force --seed
|
||||
env:
|
||||
DB_HOST: UNIT_NO_DB
|
||||
SKIP_MIGRATIONS: true
|
||||
DB_PORT: ${{ job.services.database.ports[3306] }}
|
||||
DB_USERNAME: root
|
||||
|
||||
- name: Integration tests
|
||||
run: vendor/bin/pest tests/Integration
|
||||
run: vendor/bin/pest tests/Integration --parallel --do-not-fail-on-phpunit-warning
|
||||
env:
|
||||
DB_PORT: ${{ job.services.database.ports[3306] }}
|
||||
DB_USERNAME: root
|
||||
@@ -138,8 +141,8 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
php: [8.2, 8.3, 8.4]
|
||||
database: ["mariadb:10.6", "mariadb:10.11", "mariadb:11.4"]
|
||||
php: [8.5]
|
||||
database: ["mariadb:10.11", "mariadb:11.4"]
|
||||
services:
|
||||
database:
|
||||
image: ${{ matrix.database }}
|
||||
@@ -148,7 +151,7 @@ jobs:
|
||||
MYSQL_DATABASE: testing
|
||||
ports:
|
||||
- 3306
|
||||
options: --health-cmd="mariadb-admin ping || mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
|
||||
options: --tmpfs /var/lib/mysql:rw --health-cmd="mariadb-admin ping || mysqladmin ping" --health-interval=2s --health-timeout=5s --health-retries=30
|
||||
env:
|
||||
DB_CONNECTION: mariadb
|
||||
DB_HOST: 127.0.0.1
|
||||
@@ -156,7 +159,7 @@ jobs:
|
||||
DB_USERNAME: root
|
||||
steps:
|
||||
- name: Code Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Get cache directory
|
||||
id: composer-cache
|
||||
@@ -164,7 +167,7 @@ jobs:
|
||||
echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Cache
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: ${{ steps.composer-cache.outputs.dir }}
|
||||
key: ${{ runner.os }}-composer-${{ matrix.php }}-${{ hashFiles('**/composer.lock') }}
|
||||
@@ -182,14 +185,14 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: composer install --no-interaction --no-suggest --no-progress --no-scripts
|
||||
|
||||
- name: Unit tests
|
||||
run: vendor/bin/pest tests/Unit
|
||||
- name: Run Migrations
|
||||
run: php artisan migrate --force --seed
|
||||
env:
|
||||
DB_HOST: UNIT_NO_DB
|
||||
SKIP_MIGRATIONS: true
|
||||
DB_PORT: ${{ job.services.database.ports[3306] }}
|
||||
DB_USERNAME: root
|
||||
|
||||
- name: Integration tests
|
||||
run: vendor/bin/pest tests/Integration
|
||||
run: vendor/bin/pest tests/Integration --parallel --do-not-fail-on-phpunit-warning
|
||||
env:
|
||||
DB_PORT: ${{ job.services.database.ports[3306] }}
|
||||
DB_USERNAME: root
|
||||
@@ -200,8 +203,15 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
php: [8.2, 8.3, 8.4]
|
||||
database: ["postgres:14"]
|
||||
php: [8.5]
|
||||
database: ["postgres:17", "postgres:18"]
|
||||
include:
|
||||
# postgres:17 stores data in /var/lib/postgresql/data, 18+ requires a
|
||||
# single mount at /var/lib/postgresql and errors on a data submount
|
||||
- database: "postgres:17"
|
||||
tmpfs: /var/lib/postgresql/data
|
||||
- database: "postgres:18"
|
||||
tmpfs: /var/lib/postgresql
|
||||
services:
|
||||
database:
|
||||
image: ${{ matrix.database }}
|
||||
@@ -213,10 +223,11 @@ jobs:
|
||||
ports:
|
||||
- 5432:5432
|
||||
options: >-
|
||||
--tmpfs ${{ matrix.tmpfs }}:rw
|
||||
--health-cmd pg_isready
|
||||
--health-interval 10s
|
||||
--health-interval 2s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
--health-retries 30
|
||||
env:
|
||||
DB_CONNECTION: pgsql
|
||||
DB_HOST: 127.0.0.1
|
||||
@@ -225,7 +236,7 @@ jobs:
|
||||
DB_PASSWORD: postgres
|
||||
steps:
|
||||
- name: Code Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Get cache directory
|
||||
id: composer-cache
|
||||
@@ -233,11 +244,12 @@ jobs:
|
||||
echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Cache
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: ${{ steps.composer-cache.outputs.dir }}
|
||||
key: ${{ runner.os }}-composer-${{ matrix.php }}-${{ hashFiles('**/composer.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-composer-${{ matrix.php }}-
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
@@ -250,11 +262,8 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: composer install --no-interaction --no-suggest --no-progress --no-scripts
|
||||
|
||||
- name: Unit tests
|
||||
run: vendor/bin/pest tests/Unit
|
||||
env:
|
||||
DB_HOST: UNIT_NO_DB
|
||||
SKIP_MIGRATIONS: true
|
||||
- name: Run Migrations
|
||||
run: php artisan migrate --force --seed
|
||||
|
||||
- name: Integration tests
|
||||
run: vendor/bin/pest tests/Integration
|
||||
run: vendor/bin/pest tests/Integration --parallel --do-not-fail-on-phpunit-warning
|
||||
|
||||
67
.github/workflows/docker-publish.yml
vendored
67
.github/workflows/docker-publish.yml
vendored
@@ -7,6 +7,8 @@ on:
|
||||
release:
|
||||
types:
|
||||
- published
|
||||
schedule:
|
||||
- cron: '0 0 * * 1'
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
@@ -31,13 +33,13 @@ jobs:
|
||||
platform: linux/arm64
|
||||
steps:
|
||||
- name: Code checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Docker buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
uses: docker/setup-buildx-action@v4
|
||||
|
||||
- name: Build the base PHP image
|
||||
uses: docker/build-push-action@v6
|
||||
uses: docker/build-push-action@v7
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile.base
|
||||
@@ -52,7 +54,7 @@ jobs:
|
||||
run: docker save -o base-php-${{ matrix.arch }}.tar base-php:${{ matrix.arch }}
|
||||
|
||||
- name: Push the docker build to the artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: base-php-${{ matrix.arch }}.tar
|
||||
path: base-php-${{ matrix.arch }}.tar
|
||||
@@ -75,32 +77,54 @@ jobs:
|
||||
# Always run against a tag, even if the commit into the tag has [docker skip] within the commit message.
|
||||
if: "!contains(github.ref, 'main') || (!contains(github.event.head_commit.message, 'skip docker') && !contains(github.event.head_commit.message, 'docker skip'))"
|
||||
steps:
|
||||
# Fetch the latest release tag from GitHub API before checkout and metadata so we can check out the correct ref and tag the image
|
||||
- name: Get latest release tag (scheduled)
|
||||
id: release_info
|
||||
if: github.event_name == 'schedule'
|
||||
run: |
|
||||
LATEST_TAG=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" -w "\n%{http_code}" https://api.github.com/repos/${{ github.repository }}/releases/latest | tail -1 | head -1)
|
||||
HTTP_CODE=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" -w "%{http_code}" -o /dev/null https://api.github.com/repos/${{ github.repository }}/releases/latest)
|
||||
if [ "$HTTP_CODE" != "200" ]; then
|
||||
echo "Failed to fetch latest release (HTTP $HTTP_CODE)"
|
||||
exit 1
|
||||
fi
|
||||
LATEST_TAG=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" https://api.github.com/repos/${{ github.repository }}/releases/latest | jq -r '.tag_name')
|
||||
if [ "$LATEST_TAG" = "null" ] || [ -z "$LATEST_TAG" ]; then
|
||||
echo "No release found"
|
||||
exit 1
|
||||
fi
|
||||
echo "version_tag=${LATEST_TAG#v}" >> $GITHUB_OUTPUT
|
||||
echo "schedule_tag=$LATEST_TAG" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Code checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ steps.release_info.outputs.schedule_tag || github.ref }}
|
||||
|
||||
- name: Docker metadata
|
||||
id: docker_meta
|
||||
uses: docker/metadata-action@v5
|
||||
uses: docker/metadata-action@v6
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
flavor: |
|
||||
latest=false
|
||||
tags: |
|
||||
type=raw,value=latest,enable=${{ github.event_name == 'release' && github.event.action == 'published' && github.event.release.prerelease == false }}
|
||||
type=raw,value=latest,enable=${{ (github.event_name == 'release' && github.event.action == 'published' && github.event.release.prerelease == false) || github.event_name == 'schedule' }}
|
||||
type=raw,value=${{ steps.release_info.outputs.version_tag }},enable=${{ github.event_name == 'schedule' }}
|
||||
type=ref,event=tag
|
||||
type=ref,event=branch
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
uses: docker/setup-qemu-action@v4
|
||||
|
||||
# We Need to start it in host mode else it can't acces the local registry on port 5000
|
||||
- name: Setup Docker buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
uses: docker/setup-buildx-action@v4
|
||||
with:
|
||||
driver-opts: network=host
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
@@ -109,16 +133,16 @@ jobs:
|
||||
- name: Get Build Information
|
||||
id: build_info
|
||||
run: |
|
||||
echo "version_tag=${GITHUB_REF/refs\/tags\/v/}" >> $GITHUB_OUTPUT
|
||||
echo "version_tag=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT
|
||||
echo "short_sha=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
|
||||
|
||||
# Download the base PHP image AMD64
|
||||
- uses: actions/download-artifact@v4
|
||||
- uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: base-php-amd64.tar
|
||||
|
||||
# Download the base PHP image ARM64
|
||||
- uses: actions/download-artifact@v4
|
||||
- uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: base-php-arm64.tar
|
||||
|
||||
@@ -133,27 +157,32 @@ jobs:
|
||||
rm base-php-arm64.tar base-php-amd64.tar
|
||||
|
||||
- name: Update version in config/app.php (tag)
|
||||
if: "github.event_name == 'release' && github.event.action == 'published'"
|
||||
if: "(github.event_name == 'release' && github.event.action == 'published') || github.event_name == 'schedule'"
|
||||
run: |
|
||||
sed -i "s/'version' => 'canary',/'version' => '${{ steps.build_info.outputs.version_tag }}',/" config/app.php
|
||||
VERSION="${{ github.event_name == 'schedule' && steps.release_info.outputs.version_tag || steps.build_info.outputs.version_tag }}"
|
||||
if [ -z "$VERSION" ] || [ "$VERSION" = "refs/heads/main" ]; then
|
||||
echo "Invalid version tag, refusing to update config"
|
||||
exit 1
|
||||
fi
|
||||
sed -i "s|'version' => 'canary',|'version' => '$VERSION',|" config/app.php
|
||||
|
||||
- name: Build and Push (tag)
|
||||
uses: docker/build-push-action@v6
|
||||
if: "github.event_name == 'release' && github.event.action == 'published'"
|
||||
uses: docker/build-push-action@v7
|
||||
if: "(github.event_name == 'release' && github.event.action == 'published') || github.event_name == 'schedule'"
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
push: true
|
||||
platforms: 'linux/amd64,linux/arm64'
|
||||
build-args: |
|
||||
VERSION=${{ steps.build_info.outputs.version_tag }}
|
||||
VERSION=${{ github.event_name == 'schedule' && steps.release_info.outputs.version_tag || steps.build_info.outputs.version_tag }}
|
||||
labels: ${{ steps.docker_meta.outputs.labels }}
|
||||
tags: ${{ steps.docker_meta.outputs.tags }}
|
||||
cache-from: type=gha,scope=tagged${{ matrix.os }}
|
||||
cache-to: type=gha,scope=tagged${{ matrix.os }},mode=max
|
||||
|
||||
- name: Build and Push (main)
|
||||
uses: docker/build-push-action@v6
|
||||
uses: docker/build-push-action@v7
|
||||
if: "github.event_name == 'push' && contains(github.ref, 'main')"
|
||||
with:
|
||||
context: .
|
||||
|
||||
54
.github/workflows/download-translations.yml
vendored
Normal file
54
.github/workflows/download-translations.yml
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
name: Download Crowdin Translations
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 0 * * 0' # Every Sunday at midnight
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
download-translations:
|
||||
name: Download Translations from Crowdin
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Download Translations from Crowdin
|
||||
uses: crowdin/github-action@v2
|
||||
with:
|
||||
upload_sources: false
|
||||
upload_translations: false
|
||||
download_translations: true
|
||||
export_only_approved: true
|
||||
push_translations: false
|
||||
create_pull_request: false
|
||||
crowdin_branch_name: main
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
|
||||
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
|
||||
|
||||
- name: Run Laravel Pint
|
||||
uses: aglipanci/laravel-pint-action@8eedec46a1856977c41667f9c66d5562fa3f9c08 # 2.4
|
||||
|
||||
# The Crowdin and Pint steps run in Docker as root, so new translation
|
||||
# files land owned by root. Reclaim them so the host-side git operations
|
||||
# in create-pull-request can manage the working tree.
|
||||
- name: Fix file ownership
|
||||
run: sudo chown -R "$(id -u):$(id -g)" "${{ github.workspace }}"
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
commit-message: 'chore: update translations and format with pint'
|
||||
title: 'New Crowdin Translations'
|
||||
body: 'New Crowdin translations by [Crowdin GH Action](https://github.com/crowdin/github-action), formatted by Laravel Pint.'
|
||||
branch: 'i18n_crowdin_translations'
|
||||
base: 'main'
|
||||
12
.github/workflows/lint.yaml
vendored
12
.github/workflows/lint.yaml
vendored
@@ -3,7 +3,7 @@ name: Lint
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- '**'
|
||||
- "**"
|
||||
|
||||
jobs:
|
||||
pint:
|
||||
@@ -11,12 +11,12 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Code Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: "8.3"
|
||||
php-version: "8.5"
|
||||
extensions: bcmath, curl, gd, mbstring, mysql, openssl, pdo, tokenizer, xml, zip
|
||||
tools: composer:v2
|
||||
coverage: none
|
||||
@@ -35,10 +35,10 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
php: [ 8.2, 8.3, 8.4 ]
|
||||
php: [8.3, 8.4, 8.5]
|
||||
steps:
|
||||
- name: Code Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Get cache directory
|
||||
id: composer-cache
|
||||
@@ -46,7 +46,7 @@ jobs:
|
||||
echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Cache
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: ${{ steps.composer-cache.outputs.dir }}
|
||||
key: ${{ runner.os }}-composer-${{ matrix.php }}-${{ hashFiles('**/composer.lock') }}
|
||||
|
||||
8
.github/workflows/release.yaml
vendored
8
.github/workflows/release.yaml
vendored
@@ -14,12 +14,12 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Code checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php }}
|
||||
php-version: "8.5"
|
||||
extensions: bcmath, curl, gd, mbstring, mysql, openssl, pdo, tokenizer, xml, zip
|
||||
tools: composer:v2
|
||||
coverage: none
|
||||
@@ -28,9 +28,9 @@ jobs:
|
||||
run: composer install --no-interaction --no-suggest --no-progress --no-autoloader --no-scripts --no-dev
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 20
|
||||
node-version: 22
|
||||
cache: "yarn"
|
||||
|
||||
- name: Install JS dependencies
|
||||
|
||||
32
.github/workflows/upload-sources.yml
vendored
Normal file
32
.github/workflows/upload-sources.yml
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
name: Upload Crowdin Sources
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
paths:
|
||||
- 'lang/en/**'
|
||||
- 'crowdin.yml'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
upload-sources:
|
||||
name: Upload Sources to Crowdin
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Upload Sources to Crowdin
|
||||
uses: crowdin/github-action@v2
|
||||
with:
|
||||
upload_sources: true
|
||||
upload_translations: false
|
||||
download_translations: false
|
||||
crowdin_branch_name: main
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
|
||||
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
|
||||
51
Dockerfile
51
Dockerfile
@@ -22,7 +22,7 @@ RUN composer install --no-dev --no-interaction --no-autoloader --no-scripts
|
||||
# ================================
|
||||
# Stage 1-2: Yarn Install
|
||||
# ================================
|
||||
FROM --platform=$TARGETOS/$TARGETARCH node:20-alpine AS yarn
|
||||
FROM --platform=$TARGETOS/$TARGETARCH node:22-alpine AS yarn
|
||||
|
||||
WORKDIR /build
|
||||
|
||||
@@ -38,7 +38,7 @@ RUN yarn config set network-timeout 300000 \
|
||||
FROM --platform=$TARGETOS/$TARGETARCH composer AS composerbuild
|
||||
|
||||
# Copy full code to optimize autoload
|
||||
COPY --exclude=Caddyfile --exclude=docker/ . ./
|
||||
COPY --exclude=docker/ . ./
|
||||
|
||||
RUN composer dump-autoload --optimize
|
||||
|
||||
@@ -50,7 +50,7 @@ FROM --platform=$TARGETOS/$TARGETARCH yarn AS yarnbuild
|
||||
WORKDIR /build
|
||||
|
||||
# Copy full code
|
||||
COPY --exclude=Caddyfile --exclude=docker/ . ./
|
||||
COPY --exclude=docker/ . ./
|
||||
COPY --from=composer /build .
|
||||
|
||||
RUN yarn run build
|
||||
@@ -62,38 +62,33 @@ FROM --platform=$TARGETOS/$TARGETARCH localhost:5000/base-php:$TARGETARCH AS fin
|
||||
|
||||
WORKDIR /var/www/html
|
||||
|
||||
# Install additional required libraries
|
||||
RUN apk add --no-cache \
|
||||
caddy ca-certificates supervisor supercronic fcgi
|
||||
# packages for running the panel
|
||||
caddy ca-certificates supervisor supercronic fcgi \
|
||||
# required for installing plugins. Pulled from https://github.com/pelican-dev/panel/pull/2034
|
||||
zip unzip 7zip bzip2-dev yarn git
|
||||
|
||||
COPY --chown=root:www-data --chmod=640 --from=composerbuild /build .
|
||||
COPY --chown=root:www-data --chmod=640 --from=yarnbuild /build/public ./public
|
||||
|
||||
# Set permissions
|
||||
# First ensure all files are owned by root and restrict www-data to read access
|
||||
RUN chown root:www-data ./ \
|
||||
&& chmod 750 ./ \
|
||||
# Files should not have execute set, but directories need it
|
||||
&& find ./ -type d -exec chmod 750 {} \; \
|
||||
# Create necessary directories
|
||||
&& mkdir -p /pelican-data/storage /pelican-data/plugins /var/www/html/storage/app/public /var/run/supervisord /etc/supercronic \
|
||||
# Symlinks for env, database, storage, and plugins
|
||||
&& ln -s /pelican-data/.env ./.env \
|
||||
&& ln -s /pelican-data/database/database.sqlite ./database/database.sqlite \
|
||||
&& ln -sf /var/www/html/storage/app/public /var/www/html/public/storage \
|
||||
&& ln -s /pelican-data/storage/avatars /var/www/html/storage/app/public/avatars \
|
||||
&& ln -s /pelican-data/storage/fonts /var/www/html/storage/app/public/fonts \
|
||||
&& ln -s /pelican-data/plugins /var/www/html/plugins \
|
||||
# Allow www-data write permissions where necessary
|
||||
&& chown -R www-data:www-data /pelican-data ./storage ./bootstrap/cache /var/run/supervisord /var/www/html/public/storage \
|
||||
&& chmod -R u+rwX,g+rwX,o-rwx /pelican-data ./storage ./bootstrap/cache /var/run/supervisord \
|
||||
&& chown -R www-data: /usr/local/etc/php/
|
||||
# Copy composer binary for runtime plugin dependency management
|
||||
COPY --from=composer /usr/local/bin/composer /usr/local/bin/composer
|
||||
COPY --chown=root:www-data --chmod=770 --from=composerbuild /build .
|
||||
COPY --chown=root:www-data --chmod=770 --from=yarnbuild /build/public ./public
|
||||
|
||||
# Create and remove directories
|
||||
RUN mkdir -p /pelican-data/storage /var/run/supervisord \
|
||||
# Symlinks for env, database, storage
|
||||
&& ln -s /pelican-data/.env /var/www/html/.env \
|
||||
&& ln -s /pelican-data/database/database.sqlite ./database/database.sqlite \
|
||||
&& ln -s /pelican-data/storage /var/www/html/public/storage \
|
||||
&& ln -s /pelican-data/storage /var/www/html/storage/app/public \
|
||||
# Allow www-data write permissions where necessary
|
||||
&& chown -R www-data: /pelican-data .env ./storage ./bootstrap/cache /var/run/supervisord /var/www/html/public/storage \
|
||||
&& chmod -R 770 /pelican-data ./storage ./bootstrap/cache /var/run/supervisord \
|
||||
&& chown -R www-data: /usr/local/etc/php/ /usr/local/etc/php-fpm.d/ /var/www/html/composer.json /var/www/html/composer.lock
|
||||
# Configure Supervisor
|
||||
COPY docker/supervisord.conf /etc/supervisord.conf
|
||||
COPY docker/Caddyfile /etc/caddy/Caddyfile
|
||||
# Add Laravel scheduler to crontab
|
||||
COPY docker/crontab /etc/supercronic/crontab
|
||||
COPY docker/crontab /etc/crontabs/crontab
|
||||
|
||||
COPY docker/entrypoint.sh /entrypoint.sh
|
||||
COPY docker/healthcheck.sh /healthcheck.sh
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
# ================================
|
||||
# Stage 0: Build PHP Base Image
|
||||
# ================================
|
||||
FROM --platform=$TARGETOS/$TARGETARCH php:8.4-fpm-alpine
|
||||
FROM --platform=$TARGETOS/$TARGETARCH php:8.5-fpm-alpine
|
||||
|
||||
ADD --chmod=0755 https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions /usr/local/bin/
|
||||
|
||||
RUN install-php-extensions bcmath gd intl zip opcache pcntl posix pdo_mysql pdo_pgsql
|
||||
RUN install-php-extensions bcmath gd intl zip pcntl pdo_mysql pdo_pgsql bz2
|
||||
|
||||
RUN rm /usr/local/bin/install-php-extensions
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
# syntax=docker.io/docker/dockerfile:1.13-labs
|
||||
# Pelican Development Dockerfile
|
||||
|
||||
FROM --platform=$TARGETOS/$TARGETARCH php:8.4-fpm-alpine AS base
|
||||
FROM --platform=$TARGETOS/$TARGETARCH php:8.5-fpm-alpine AS base
|
||||
|
||||
ADD --chmod=0755 https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions /usr/local/bin/
|
||||
|
||||
RUN install-php-extensions bcmath gd intl zip opcache pcntl posix pdo_mysql pdo_pgsql
|
||||
RUN install-php-extensions bcmath gd intl zip pcntl pdo_mysql pdo_pgsql bz2
|
||||
|
||||
RUN rm /usr/local/bin/install-php-extensions
|
||||
|
||||
@@ -26,7 +26,7 @@ RUN composer install --no-dev --no-interaction --no-autoloader --no-scripts
|
||||
# ================================
|
||||
# Stage 1-2: Yarn Install
|
||||
# ================================
|
||||
FROM --platform=$TARGETOS/$TARGETARCH node:20-alpine AS yarn
|
||||
FROM --platform=$TARGETOS/$TARGETARCH node:22-alpine AS yarn
|
||||
|
||||
WORKDIR /build
|
||||
|
||||
@@ -42,7 +42,7 @@ RUN yarn config set network-timeout 300000 \
|
||||
FROM --platform=$TARGETOS/$TARGETARCH composer AS composerbuild
|
||||
|
||||
# Copy full code to optimize autoload
|
||||
COPY --exclude=Caddyfile --exclude=docker/ . ./
|
||||
COPY --exclude=docker/ . ./
|
||||
|
||||
RUN composer dump-autoload --optimize
|
||||
|
||||
@@ -54,7 +54,7 @@ FROM --platform=$TARGETOS/$TARGETARCH yarn AS yarnbuild
|
||||
WORKDIR /build
|
||||
|
||||
# Copy full code
|
||||
COPY --exclude=Caddyfile --exclude=docker/ . ./
|
||||
COPY --exclude=docker/ . ./
|
||||
COPY --from=composer /build .
|
||||
|
||||
RUN yarn run build
|
||||
@@ -68,36 +68,33 @@ WORKDIR /var/www/html
|
||||
|
||||
# Install additional required libraries
|
||||
RUN apk add --no-cache \
|
||||
caddy ca-certificates supervisor supercronic fcgi coreutils
|
||||
# packages for running the panel
|
||||
caddy ca-certificates supervisor supercronic fcgi coreutils \
|
||||
# required for installing plugins. Pulled from https://github.com/pelican-dev/panel/pull/2034
|
||||
zip unzip 7zip bzip2-dev yarn git
|
||||
|
||||
COPY --chown=root:www-data --chmod=640 --from=composerbuild /build .
|
||||
COPY --chown=root:www-data --chmod=640 --from=yarnbuild /build/public ./public
|
||||
# Copy composer binary for runtime plugin dependency management
|
||||
COPY --from=composer /usr/local/bin/composer /usr/local/bin/composer
|
||||
COPY --chown=root:www-data --chmod=770 --from=composerbuild /build .
|
||||
COPY --chown=root:www-data --chmod=770 --from=yarnbuild /build/public ./public
|
||||
|
||||
# Set permissions
|
||||
# First ensure all files are owned by root and restrict www-data to read access
|
||||
RUN chown root:www-data ./ \
|
||||
&& chmod 750 ./ \
|
||||
# Files should not have execute set, but directories need it
|
||||
&& find ./ -type d -exec chmod 750 {} \; \
|
||||
# Create necessary directories
|
||||
&& mkdir -p /pelican-data/storage /pelican-data/plugins /var/www/html/storage/app/public /var/run/supervisord /etc/supercronic \
|
||||
# Symlinks for env, database, storage, and plugins
|
||||
&& ln -s /pelican-data/.env ./.env \
|
||||
&& ln -s /pelican-data/database/database.sqlite ./database/database.sqlite \
|
||||
&& ln -sf /var/www/html/storage/app/public /var/www/html/public/storage \
|
||||
&& ln -s /pelican-data/storage/avatars /var/www/html/storage/app/public/avatars \
|
||||
&& ln -s /pelican-data/storage/fonts /var/www/html/storage/app/public/fonts \
|
||||
&& ln -s /pelican-data/plugins /var/www/html/plugins \
|
||||
# Allow www-data write permissions where necessary
|
||||
&& chown -R www-data:www-data /pelican-data ./storage ./bootstrap/cache /var/run/supervisord /var/www/html/public/storage \
|
||||
&& chmod -R u+rwX,g+rwX,o-rwx /pelican-data ./storage ./bootstrap/cache /var/run/supervisord \
|
||||
&& chown -R www-data: /usr/local/etc/php/
|
||||
# Create and remove directories
|
||||
RUN mkdir -p /pelican-data/storage /var/run/supervisord \
|
||||
# Symlinks for env, database, storage
|
||||
&& ln -s /pelican-data/.env /var/www/html/.env \
|
||||
&& ln -s /pelican-data/database/database.sqlite ./database/database.sqlite \
|
||||
&& ln -s /pelican-data/storage /var/www/html/public/storage \
|
||||
&& ln -s /pelican-data/storage /var/www/html/storage/app/public \
|
||||
# Allow www-data write permissions where necessary
|
||||
&& chown -R www-data: /pelican-data .env ./storage ./bootstrap/cache /var/run/supervisord /var/www/html/public/storage \
|
||||
&& chmod -R 770 /pelican-data ./storage ./bootstrap/cache /var/run/supervisord \
|
||||
&& chown -R www-data: /usr/local/etc/php/ /usr/local/etc/php-fpm.d/ /var/www/html/composer.json /var/www/html/composer.lock
|
||||
|
||||
# Configure Supervisor
|
||||
COPY docker/supervisord.conf /etc/supervisord.conf
|
||||
COPY docker/Caddyfile /etc/caddy/Caddyfile
|
||||
# Add Laravel scheduler to crontab
|
||||
COPY docker/crontab /etc/supercronic/crontab
|
||||
COPY docker/crontab /etc/crontabs/crontab
|
||||
|
||||
COPY docker/entrypoint.sh /entrypoint.sh
|
||||
COPY docker/healthcheck.sh /healthcheck.sh
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
namespace App\Checks;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Composer\InstalledVersions;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Spatie\Health\Checks\Checks\ScheduleCheck as BaseCheck;
|
||||
use Spatie\Health\Checks\Result;
|
||||
|
||||
|
||||
48
app/Console/Commands/Dev/GenerateTablerIconsEnum.php
Normal file
48
app/Console/Commands/Dev/GenerateTablerIconsEnum.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands\Dev;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\File;
|
||||
|
||||
class GenerateTablerIconsEnum extends Command
|
||||
{
|
||||
protected $signature = 'dev:generate-tabler-icons-enum';
|
||||
|
||||
protected $description = 'Generate an enum for tabler icons based on the secondnetwork/blade-tabler-icons svgs';
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
$files = File::files(base_path('vendor/secondnetwork/blade-tabler-icons/resources/svg'));
|
||||
$files = array_filter($files, fn ($file) => $file->getExtension() === 'svg');
|
||||
|
||||
$enumContent = "<?php\n\n";
|
||||
$enumContent .= "namespace App\\Enums;\n\n";
|
||||
$enumContent .= "enum TablerIcon: string\n{\n";
|
||||
|
||||
foreach ($files as $file) {
|
||||
$filename = pathinfo($file->getFilename(), PATHINFO_FILENAME);
|
||||
|
||||
// Letter V is duplicate, as "letter-v" and "letter-letter-v"
|
||||
if (str($filename)->contains('letter-letter')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Filled icons exist with "-f" and "-filled", we only want the later
|
||||
if (str($filename)->endsWith('-f') && file_exists(base_path("vendor/secondnetwork/blade-tabler-icons/resources/svg/{$filename}illed.svg"))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$caseName = str($filename)->title()->replace('-', '');
|
||||
$value = str($filename)->slug()->prepend('tabler-');
|
||||
|
||||
$enumContent .= " case $caseName = '$value';\n";
|
||||
}
|
||||
|
||||
$enumContent .= "}\n";
|
||||
|
||||
File::put(base_path('app/Enums/TablerIcon.php'), $enumContent);
|
||||
|
||||
$this->info('Enum generated');
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,6 @@ use App\Services\Eggs\Sharing\EggExporterService;
|
||||
use Exception;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use JsonException;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
class CheckEggUpdatesCommand extends Command
|
||||
@@ -22,14 +21,12 @@ class CheckEggUpdatesCommand extends Command
|
||||
try {
|
||||
$this->check($egg, $exporterService);
|
||||
} catch (Exception $exception) {
|
||||
$this->error("{$egg->name}: Error ({$exception->getMessage()})");
|
||||
$this->error("$egg->name: Error ({$exception->getMessage()})");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws JsonException
|
||||
*/
|
||||
/** @throws Exception */
|
||||
private function check(Egg $egg, EggExporterService $exporterService): void
|
||||
{
|
||||
if (is_null($egg->update_url)) {
|
||||
@@ -38,14 +35,21 @@ class CheckEggUpdatesCommand extends Command
|
||||
return;
|
||||
}
|
||||
|
||||
$ext = strtolower(pathinfo(parse_url($egg->update_url, PHP_URL_PATH), PATHINFO_EXTENSION));
|
||||
$isYaml = in_array($ext, ['yaml', 'yml']);
|
||||
$extension = strtolower(pathinfo(parse_url($egg->update_url, PHP_URL_PATH), PATHINFO_EXTENSION));
|
||||
|
||||
throw_if(empty($extension), new Exception('Unsupported file format.'));
|
||||
|
||||
$isYaml = in_array($extension, ['yaml', 'yml']);
|
||||
|
||||
$local = $isYaml
|
||||
? Yaml::parse($exporterService->handle($egg->id, EggFormat::YAML))
|
||||
: json_decode($exporterService->handle($egg->id, EggFormat::JSON), true);
|
||||
|
||||
$remote = Http::timeout(5)->connectTimeout(1)->get($egg->update_url)->throw()->body();
|
||||
$remote = Http::timeout(5)->connectTimeout(1)->get($egg->update_url);
|
||||
|
||||
throw_if($remote->failed(), new Exception("HTTP request returned status code {$remote->status()}"));
|
||||
|
||||
$remote = $remote->body();
|
||||
$remote = $isYaml ? Yaml::parse($remote) : json_decode($remote, true);
|
||||
|
||||
unset($local['exported_at'], $remote['exported_at']);
|
||||
|
||||
152
app/Console/Commands/Egg/NormalizeEggCommand.php
Normal file
152
app/Console/Commands/Egg/NormalizeEggCommand.php
Normal file
@@ -0,0 +1,152 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands\Egg;
|
||||
|
||||
use App\Enums\EggFormat;
|
||||
use App\Exceptions\Service\InvalidFileUploadException;
|
||||
use App\Models\Egg;
|
||||
use App\Services\Eggs\Sharing\EggExporterService;
|
||||
use App\Services\Eggs\Sharing\EggImporterService;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
class NormalizeEggCommand extends Command
|
||||
{
|
||||
protected $description = 'Upgrades egg json/yaml to latest format as yaml';
|
||||
|
||||
protected $signature = 'p:egg:normalize {file} {--delete-original}';
|
||||
|
||||
public function __construct(
|
||||
private EggExporterService $exporter,
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function handle(EggImporterService $importer): int
|
||||
{
|
||||
$inputFile = $this->argument('file');
|
||||
$unparsed = file_get_contents($inputFile);
|
||||
if ($unparsed === false) {
|
||||
$this->error("Failed to read file: {$inputFile}");
|
||||
|
||||
return Command::FAILURE;
|
||||
}
|
||||
$extension = strtolower(pathinfo($inputFile, PATHINFO_EXTENSION));
|
||||
|
||||
$this->info("Importing {$inputFile}");
|
||||
|
||||
$format = match ($extension) {
|
||||
'yaml', 'yml' => EggFormat::YAML,
|
||||
'json' => EggFormat::JSON,
|
||||
default => null,
|
||||
};
|
||||
if (is_null($format)) {
|
||||
$this->error(" -> unsupported extension {$extension} for {$inputFile}");
|
||||
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
try {
|
||||
$eggArray = $importer->parse($unparsed, $format);
|
||||
} catch (InvalidFileUploadException) {
|
||||
$this->error(" -> unsupported file version, is it actually an egg? ({$inputFile})");
|
||||
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
if (
|
||||
!array_key_exists('meta', $eggArray) ||
|
||||
!is_array($eggArray['meta'])
|
||||
) {
|
||||
$this->error(" -> does not contain existing meta or meta is not array, is it actually an egg? ({$inputFile})");
|
||||
|
||||
return Command::FAILURE;
|
||||
}
|
||||
if (!array_key_exists('exported_at', $eggArray)) {
|
||||
$this->error(" -> does not contain existing exported_at, is it actually an egg? ({$inputFile})");
|
||||
|
||||
return Command::FAILURE;
|
||||
}
|
||||
if (
|
||||
!array_key_exists('update_url', $eggArray['meta']) ||
|
||||
!is_string($eggArray['meta']['update_url'])
|
||||
) {
|
||||
$this->error(" -> does not contain existing meta.update_url or is not a string, is it actually an egg? ({$inputFile})");
|
||||
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
// We upgraded our in-memory array when we imported, so set to latest version before export
|
||||
$eggArray['meta']['version'] = Egg::EXPORT_VERSION;
|
||||
$eggArray['meta']['update_url'] = self::replaceExtension($eggArray['meta']['update_url']);
|
||||
self::fixVariableRules($eggArray);
|
||||
|
||||
$outputFile = self::replaceExtension($inputFile);
|
||||
|
||||
if ($outputFile === $inputFile && !$this->hasFileChanged($unparsed, $eggArray)) {
|
||||
$this->info(' -> no changes required');
|
||||
|
||||
return Command::SUCCESS;
|
||||
} else {
|
||||
$eggArray['exported_at'] = Carbon::now()->toAtomString();
|
||||
}
|
||||
|
||||
$this->info(" -> exporting to {$outputFile}");
|
||||
$yaml = $this->eggToYaml($eggArray);
|
||||
if (file_put_contents($outputFile, $yaml) === false) {
|
||||
$this->error(" -> failed to write output file: {$outputFile}");
|
||||
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
if ($this->option('delete-original') && $outputFile !== $inputFile) {
|
||||
$this->info(' -> deleting input file as requested');
|
||||
if (!unlink($inputFile)) {
|
||||
$this->warn(" -> failed to delete original file: {$inputFile}");
|
||||
}
|
||||
}
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
private static function replaceExtension(string $path): string
|
||||
{
|
||||
return preg_replace('/^(.*\.)(?:yml|json|yaml)$/', '$1yaml', $path) ?? $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $eggArray
|
||||
*/
|
||||
private static function fixVariableRules(array &$eggArray): void
|
||||
{
|
||||
if (!array_key_exists('variables', $eggArray)) {
|
||||
return;
|
||||
}
|
||||
foreach ($eggArray['variables'] as &$var) {
|
||||
unset($var['field_type']);
|
||||
if (!array_key_exists('rules', $var)) {
|
||||
continue;
|
||||
}
|
||||
$var['rules'] = is_array($var['rules']) ? $var['rules'] : explode('|', $var['rules']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $eggArray
|
||||
*/
|
||||
private function hasFileChanged(string $unparsed, array $eggArray): bool
|
||||
{
|
||||
$yaml = $this->eggToYaml($eggArray);
|
||||
|
||||
return $unparsed !== $yaml;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $eggArray
|
||||
*/
|
||||
private function eggToYaml(array $eggArray): string
|
||||
{
|
||||
return Yaml::dump($this->exporter->yamlExport($eggArray), 10, 2, Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK | Yaml::DUMP_OBJECT_AS_MAP);
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ class UpdateEggIndexCommand extends Command
|
||||
public function handle(): int
|
||||
{
|
||||
try {
|
||||
$data = Http::timeout(5)->connectTimeout(1)->get('https://raw.githubusercontent.com/pelican-eggs/pelican-eggs.github.io/refs/heads/main/content/pelican.json')->throw()->json();
|
||||
$data = Http::timeout(5)->connectTimeout(1)->get(config('panel.cdn.egg_index_url'))->throw()->json();
|
||||
} catch (Exception $exception) {
|
||||
$this->error($exception->getMessage());
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ class DatabaseSettingsCommand extends Command
|
||||
'sqlite' => 'SQLite (recommended)',
|
||||
'mariadb' => 'MariaDB',
|
||||
'mysql' => 'MySQL',
|
||||
'pgsql' => 'PostgreSQL',
|
||||
];
|
||||
|
||||
protected $description = 'Configure database settings for the Panel.';
|
||||
@@ -23,10 +24,10 @@ class DatabaseSettingsCommand extends Command
|
||||
protected $signature = 'p:environment:database
|
||||
{--driver= : The database driver backend to use.}
|
||||
{--database= : The database to use.}
|
||||
{--host= : The connection address for the MySQL/ MariaDB server.}
|
||||
{--port= : The connection port for the MySQL/ MariaDB server.}
|
||||
{--username= : Username to use when connecting to the MySQL/ MariaDB server.}
|
||||
{--password= : Password to use for the MySQL/ MariaDB database.}';
|
||||
{--host= : The connection address for the MySQL/ MariaDB/ PostgreSQL server.}
|
||||
{--port= : The connection port for the MySQL/ MariaDB/ PostgreSQL server.}
|
||||
{--username= : Username to use when connecting to the MySQL/ MariaDB/ PostgreSQL server.}
|
||||
{--password= : Password to use for the MySQL/ MariaDB/ PostgreSQL database.}';
|
||||
|
||||
/** @var array<array-key, mixed> */
|
||||
protected array $variables = [];
|
||||
@@ -176,6 +177,68 @@ class DatabaseSettingsCommand extends Command
|
||||
return $this->handle();
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
} elseif ($this->variables['DB_CONNECTION'] === 'pgsql') {
|
||||
$this->output->note(trans('commands.database_settings.DB_HOST_note'));
|
||||
$this->variables['DB_HOST'] = $this->option('host') ?? $this->ask(
|
||||
'Database Host',
|
||||
config('database.connections.pgsql.host', '127.0.0.1')
|
||||
);
|
||||
|
||||
$this->variables['DB_PORT'] = $this->option('port') ?? $this->ask(
|
||||
'Database Port',
|
||||
config('database.connections.pgsql.port', 5432)
|
||||
);
|
||||
|
||||
$this->variables['DB_DATABASE'] = $this->option('database') ?? $this->ask(
|
||||
'Database Name',
|
||||
config('database.connections.pgsql.database', 'panel')
|
||||
);
|
||||
|
||||
$this->output->note(trans('commands.database_settings.DB_USERNAME_note'));
|
||||
$this->variables['DB_USERNAME'] = $this->option('username') ?? $this->ask(
|
||||
'Database Username',
|
||||
config('database.connections.pgsql.username', 'pelican')
|
||||
);
|
||||
|
||||
$askForPgSQLPassword = true;
|
||||
if (!empty(config('database.connections.pgsql.password')) && $this->input->isInteractive()) {
|
||||
$this->variables['DB_PASSWORD'] = config('database.connections.pgsql.password');
|
||||
$askForPgSQLPassword = $this->confirm(trans('commands.database_settings.DB_PASSWORD_note'));
|
||||
}
|
||||
|
||||
if ($askForPgSQLPassword) {
|
||||
$this->variables['DB_PASSWORD'] = $this->option('password') ?? $this->secret('Database Password');
|
||||
}
|
||||
|
||||
try {
|
||||
// Test connection
|
||||
config()->set('database.connections._panel_command_test', [
|
||||
'driver' => 'pgsql',
|
||||
'host' => $this->variables['DB_HOST'],
|
||||
'port' => $this->variables['DB_PORT'],
|
||||
'database' => $this->variables['DB_DATABASE'],
|
||||
'username' => $this->variables['DB_USERNAME'],
|
||||
'password' => $this->variables['DB_PASSWORD'],
|
||||
'charset' => 'utf8',
|
||||
'prefix' => '',
|
||||
'prefix_indexes' => true,
|
||||
'search_path' => 'public',
|
||||
'sslmode' => 'prefer',
|
||||
]);
|
||||
|
||||
$this->database->connection('_panel_command_test')->getPdo();
|
||||
} catch (PDOException $exception) {
|
||||
$this->output->error(sprintf('Unable to connect to the PostgreSQL server using the provided credentials. The error returned was "%s".', $exception->getMessage()));
|
||||
$this->output->error(trans('commands.database_settings.DB_error_2'));
|
||||
|
||||
if ($this->confirm(trans('commands.database_settings.go_back'))) {
|
||||
$this->database->disconnect('_panel_command_test');
|
||||
|
||||
return $this->handle();
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
} elseif ($this->variables['DB_CONNECTION'] === 'sqlite') {
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
|
||||
namespace App\Console\Commands\Maintenance;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Contracts\Filesystem\Factory as FilesystemFactory;
|
||||
use Illuminate\Contracts\Filesystem\Filesystem;
|
||||
use Illuminate\Support\Carbon;
|
||||
use SplFileInfo;
|
||||
|
||||
class CleanServiceBackupFilesCommand extends Command
|
||||
|
||||
@@ -16,9 +16,7 @@ class PruneOrphanedBackupsCommand extends Command
|
||||
public function handle(): void
|
||||
{
|
||||
$since = $this->option('prune-age') ?? config('backups.prune_age', 360);
|
||||
if (!$since || !is_digit($since)) {
|
||||
throw new InvalidArgumentException('The "--prune-age" argument must be a value greater than 0.');
|
||||
}
|
||||
throw_if(!$since || !is_digit($since), new InvalidArgumentException('The "--prune-age" argument must be a value greater than 0.'));
|
||||
|
||||
$query = Backup::query()
|
||||
->whereNull('completed_at')
|
||||
|
||||
19
app/Console/Commands/Overrides/ConfigCacheCommand.php
Normal file
19
app/Console/Commands/Overrides/ConfigCacheCommand.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands\Overrides;
|
||||
|
||||
use Illuminate\Foundation\Console\ConfigCacheCommand as BaseConfigCacheCommand;
|
||||
|
||||
class ConfigCacheCommand extends BaseConfigCacheCommand
|
||||
{
|
||||
/**
|
||||
* Prevent config from being cached
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$this->components->warn('Configuration caching has been disabled.');
|
||||
|
||||
$this->line(' Reason: This application uses dynamic plugins. Caching config');
|
||||
$this->line(' prevents /plugins/config/*.php files from being loaded correctly.');
|
||||
}
|
||||
}
|
||||
18
app/Console/Commands/Overrides/OptimizeCommand.php
Normal file
18
app/Console/Commands/Overrides/OptimizeCommand.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands\Overrides;
|
||||
|
||||
use Illuminate\Foundation\Console\OptimizeCommand as BaseOptimizeCommand;
|
||||
|
||||
class OptimizeCommand extends BaseOptimizeCommand
|
||||
{
|
||||
/**
|
||||
* Prevent config from being cached
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
protected function getOptimizeTasks()
|
||||
{
|
||||
return array_except(parent::getOptimizeTasks(), 'config');
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@ class DisablePluginCommand extends Command
|
||||
{
|
||||
$id = $this->argument('id') ?? $this->choice('Plugin', Plugin::pluck('name', 'id')->toArray());
|
||||
|
||||
$plugin = Plugin::find($id);
|
||||
$plugin = Plugin::find(str($id)->lower()->toString());
|
||||
|
||||
if (!$plugin) {
|
||||
$this->error('Plugin does not exist!');
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace App\Console\Commands\Plugin;
|
||||
use App\Enums\PluginStatus;
|
||||
use App\Models\Plugin;
|
||||
use App\Services\Helpers\PluginService;
|
||||
use Exception;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class InstallPluginCommand extends Command
|
||||
@@ -17,7 +18,7 @@ class InstallPluginCommand extends Command
|
||||
{
|
||||
$id = $this->argument('id') ?? $this->choice('Plugin', Plugin::pluck('name', 'id')->toArray());
|
||||
|
||||
$plugin = Plugin::find($id);
|
||||
$plugin = Plugin::find(str($id)->lower()->toString());
|
||||
|
||||
if (!$plugin) {
|
||||
$this->error('Plugin does not exist!');
|
||||
@@ -31,8 +32,12 @@ class InstallPluginCommand extends Command
|
||||
return;
|
||||
}
|
||||
|
||||
$pluginService->installPlugin($plugin);
|
||||
try {
|
||||
$pluginService->installPlugin($plugin);
|
||||
|
||||
$this->info('Plugin installed and enabled.');
|
||||
$this->info('Plugin installed and enabled.');
|
||||
} catch (Exception $exception) {
|
||||
$this->error('Could not install plugin: ' . $exception->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace App\Console\Commands\Plugin;
|
||||
use App\Enums\PluginStatus;
|
||||
use App\Models\Plugin;
|
||||
use App\Services\Helpers\PluginService;
|
||||
use Exception;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class UninstallPluginCommand extends Command
|
||||
@@ -17,7 +18,7 @@ class UninstallPluginCommand extends Command
|
||||
{
|
||||
$id = $this->argument('id') ?? $this->choice('Plugin', Plugin::pluck('name', 'id')->toArray());
|
||||
|
||||
$plugin = Plugin::find($id);
|
||||
$plugin = Plugin::find(str($id)->lower()->toString());
|
||||
|
||||
if (!$plugin) {
|
||||
$this->error('Plugin does not exist!');
|
||||
@@ -36,8 +37,12 @@ class UninstallPluginCommand extends Command
|
||||
$deleteFiles = $this->confirm('Do you also want to delete the plugin files?');
|
||||
}
|
||||
|
||||
$pluginService->uninstallPlugin($plugin, $deleteFiles);
|
||||
try {
|
||||
$pluginService->uninstallPlugin($plugin, $deleteFiles);
|
||||
|
||||
$this->info('Plugin uninstalled' . ($deleteFiles ? ' and files deleted' : '') . '.');
|
||||
$this->info('Plugin uninstalled' . ($deleteFiles ? ' and files deleted' : '') . '.');
|
||||
} catch (Exception $exception) {
|
||||
$this->error('Could not uninstall plugin: ' . $exception->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace App\Console\Commands\Plugin;
|
||||
|
||||
use App\Models\Plugin;
|
||||
use App\Services\Helpers\PluginService;
|
||||
use Exception;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class UpdatePluginCommand extends Command
|
||||
@@ -16,7 +17,7 @@ class UpdatePluginCommand extends Command
|
||||
{
|
||||
$id = $this->argument('id') ?? $this->choice('Plugin', Plugin::pluck('name', 'id')->toArray());
|
||||
|
||||
$plugin = Plugin::find($id);
|
||||
$plugin = Plugin::find(str($id)->lower()->toString());
|
||||
|
||||
if (!$plugin) {
|
||||
$this->error('Plugin does not exist!');
|
||||
@@ -30,8 +31,12 @@ class UpdatePluginCommand extends Command
|
||||
return;
|
||||
}
|
||||
|
||||
$pluginService->updatePlugin($plugin);
|
||||
try {
|
||||
$pluginService->updatePlugin($plugin);
|
||||
|
||||
$this->info('Plugin updated.');
|
||||
$this->info('Plugin updated.');
|
||||
} catch (Exception $exception) {
|
||||
$this->error('Could not update plugin: ' . $exception->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,11 @@ use Filament\Schemas\Components\Component;
|
||||
|
||||
interface HasPluginSettings
|
||||
{
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function getSettingsFormData(): array;
|
||||
|
||||
/**
|
||||
* @return Component[]
|
||||
*/
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
use BackedEnum;
|
||||
use Filament\Support\Contracts\HasColor;
|
||||
use Filament\Support\Contracts\HasIcon;
|
||||
use Filament\Support\Contracts\HasLabel;
|
||||
@@ -12,12 +13,12 @@ enum BackupStatus: string implements HasColor, HasIcon, HasLabel
|
||||
case Successful = 'successful';
|
||||
case Failed = 'failed';
|
||||
|
||||
public function getIcon(): string
|
||||
public function getIcon(): BackedEnum
|
||||
{
|
||||
return match ($this) {
|
||||
self::InProgress => 'tabler-circle-dashed',
|
||||
self::Successful => 'tabler-circle-check',
|
||||
self::Failed => 'tabler-circle-x',
|
||||
self::InProgress => TablerIcon::CircleDashed,
|
||||
self::Successful => TablerIcon::CircleCheck,
|
||||
self::Failed => TablerIcon::CircleX,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
use BackedEnum;
|
||||
use Filament\Support\Contracts\HasColor;
|
||||
use Filament\Support\Contracts\HasIcon;
|
||||
use Filament\Support\Contracts\HasLabel;
|
||||
@@ -23,34 +24,25 @@ enum ContainerStatus: string implements HasColor, HasIcon, HasLabel
|
||||
// HTTP Based
|
||||
case Missing = 'missing';
|
||||
|
||||
public function getIcon(): string
|
||||
public function getIcon(): BackedEnum
|
||||
{
|
||||
return match ($this) {
|
||||
|
||||
self::Created => 'tabler-heart-plus',
|
||||
self::Starting => 'tabler-heart-up',
|
||||
self::Running => 'tabler-heartbeat',
|
||||
self::Restarting => 'tabler-heart-bolt',
|
||||
self::Exited => 'tabler-heart-exclamation',
|
||||
self::Paused => 'tabler-heart-pause',
|
||||
self::Dead, self::Offline => 'tabler-heart-x',
|
||||
self::Removing => 'tabler-heart-down',
|
||||
self::Missing => 'tabler-heart-search',
|
||||
self::Stopping => 'tabler-heart-minus',
|
||||
self::Created => TablerIcon::HeartPlus,
|
||||
self::Starting => TablerIcon::HeartUp,
|
||||
self::Running => TablerIcon::Heartbeat,
|
||||
self::Restarting => TablerIcon::HeartBolt,
|
||||
self::Exited => TablerIcon::HeartExclamation,
|
||||
self::Paused => TablerIcon::HeartPause,
|
||||
self::Dead, self::Offline => TablerIcon::HeartX,
|
||||
self::Removing => TablerIcon::HeartDown,
|
||||
self::Missing => TablerIcon::HeartSearch,
|
||||
self::Stopping => TablerIcon::HeartMinus,
|
||||
};
|
||||
}
|
||||
|
||||
public function getColor(bool $hex = false): string
|
||||
public function getColor(): string
|
||||
{
|
||||
if ($hex) {
|
||||
return match ($this) {
|
||||
self::Created, self::Restarting => '#2563EB',
|
||||
self::Starting, self::Paused, self::Removing, self::Stopping => '#D97706',
|
||||
self::Running => '#22C55E',
|
||||
self::Exited, self::Missing, self::Dead, self::Offline => '#EF4444',
|
||||
};
|
||||
}
|
||||
|
||||
return match ($this) {
|
||||
self::Created => 'primary',
|
||||
self::Starting => 'warning',
|
||||
|
||||
@@ -11,6 +11,9 @@ enum CustomizationKey: string
|
||||
case TopNavigation = 'top_navigation';
|
||||
case DashboardLayout = 'dashboard_layout';
|
||||
|
||||
case ButtonStyle = 'button_style';
|
||||
case RedirectToAdmin = 'redirect_to_admin';
|
||||
|
||||
public function getDefaultValue(): string|int|bool
|
||||
{
|
||||
return match ($this) {
|
||||
@@ -20,6 +23,8 @@ enum CustomizationKey: string
|
||||
self::ConsoleGraphPeriod => 30,
|
||||
self::TopNavigation => config('panel.filament.default-navigation', 'sidebar'),
|
||||
self::DashboardLayout => 'grid',
|
||||
self::ButtonStyle => true,
|
||||
self::RedirectToAdmin => false,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
12
app/Enums/NodeJwtScope.php
Normal file
12
app/Enums/NodeJwtScope.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
enum NodeJwtScope: string
|
||||
{
|
||||
case BackupDownload = 'backup-download';
|
||||
case FileDownload = 'file-download';
|
||||
case FileUpload = 'file-upload';
|
||||
case ServerTransfer = 'transfer';
|
||||
case Websocket = 'websocket';
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
use BackedEnum;
|
||||
use Filament\Support\Contracts\HasIcon;
|
||||
use Filament\Support\Contracts\HasLabel;
|
||||
|
||||
@@ -11,12 +12,12 @@ enum PluginCategory: string implements HasIcon, HasLabel
|
||||
case Theme = 'theme';
|
||||
case Language = 'language';
|
||||
|
||||
public function getIcon(): string
|
||||
public function getIcon(): BackedEnum
|
||||
{
|
||||
return match ($this) {
|
||||
self::Plugin => 'tabler-package',
|
||||
self::Theme => 'tabler-palette',
|
||||
self::Language => 'tabler-language',
|
||||
self::Plugin => TablerIcon::Package,
|
||||
self::Theme => TablerIcon::Palette,
|
||||
self::Language => TablerIcon::Language,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
use BackedEnum;
|
||||
use Filament\Support\Contracts\HasColor;
|
||||
use Filament\Support\Contracts\HasIcon;
|
||||
use Filament\Support\Contracts\HasLabel;
|
||||
@@ -14,14 +15,14 @@ enum PluginStatus: string implements HasColor, HasIcon, HasLabel
|
||||
case Errored = 'errored';
|
||||
case Incompatible = 'incompatible';
|
||||
|
||||
public function getIcon(): string
|
||||
public function getIcon(): BackedEnum
|
||||
{
|
||||
return match ($this) {
|
||||
self::NotInstalled => 'tabler-heart-off',
|
||||
self::Disabled => 'tabler-heart-x',
|
||||
self::Enabled => 'tabler-heart-check',
|
||||
self::Errored => 'tabler-heart-broken',
|
||||
self::Incompatible => 'tabler-heart-cancel',
|
||||
self::NotInstalled => TablerIcon::HeartOff,
|
||||
self::Disabled => TablerIcon::HeartX,
|
||||
self::Enabled => TablerIcon::HeartCheck,
|
||||
self::Errored => TablerIcon::HeartBroken,
|
||||
self::Incompatible => TablerIcon::HeartCancel,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
65
app/Enums/ResourceLimit.php
Normal file
65
app/Enums/ResourceLimit.php
Normal file
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
use App\Models\Server;
|
||||
use Illuminate\Cache\RateLimiting\Limit;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Routing\Middleware\ThrottleRequests;
|
||||
use Illuminate\Support\Facades\RateLimiter;
|
||||
use Webmozart\Assert\Assert;
|
||||
|
||||
/**
|
||||
* A basic resource throttler for individual servers. This is applied in addition
|
||||
* to existing rate limits and allows the code to slow down speedy users that might
|
||||
* be creating resources a little too quickly for comfort. This throttle generally
|
||||
* only applies to creation flows, and not general view/edit/delete flows.
|
||||
*/
|
||||
enum ResourceLimit: string
|
||||
{
|
||||
case Websocket = 'websocket';
|
||||
case AllocationCreate = 'allocation-create';
|
||||
case BackupRestore = 'backup-restore';
|
||||
case DatabaseCreate = 'database-create';
|
||||
case ScheduleCreate = 'schedule-create';
|
||||
case SubuserCreate = 'subuser-create';
|
||||
case FilePull = 'file-pull';
|
||||
|
||||
public function throttleKey(): string
|
||||
{
|
||||
return "api.client:server-resource:{$this->name}";
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a middleware that will throttle the specific resource by server. This
|
||||
* throttle applies to any user making changes to that resource on the specific
|
||||
* server, it is NOT per-user.
|
||||
*/
|
||||
public function middleware(): string
|
||||
{
|
||||
return ThrottleRequests::using($this->throttleKey());
|
||||
}
|
||||
|
||||
public function limit(): Limit
|
||||
{
|
||||
return match ($this) {
|
||||
self::Websocket => Limit::perMinutes(config('http.rate_limit.websocket_period'), config('http.rate_limit.websocket')),
|
||||
self::BackupRestore => Limit::perMinutes(config('http.rate_limit.backup_restore_period'), config('http.rate_limit.backup_restore')),
|
||||
self::DatabaseCreate => Limit::perMinutes(config('http.rate_limit.database_create_period'), config('http.rate_limit.database_create')),
|
||||
self::SubuserCreate => Limit::perMinutes(config('http.rate_limit.subuser_create_period'), config('http.rate_limit.subuser_create')),
|
||||
self::FilePull => Limit::perMinutes(config('http.rate_limit.file_pull_period'), config('http.rate_limit.file_pull')),
|
||||
default => Limit::perMinutes(config('http.rate_limit.default_period'), config('http.rate_limit.default')),
|
||||
};
|
||||
}
|
||||
|
||||
public static function boot(): void
|
||||
{
|
||||
foreach (self::cases() as $case) {
|
||||
RateLimiter::for($case->throttleKey(), function (Request $request) use ($case) {
|
||||
Assert::isInstanceOf($server = $request->route()->parameter('server'), Server::class);
|
||||
|
||||
return $case->limit()->by($server->uuid);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,8 @@ namespace App\Enums;
|
||||
enum RolePermissionModels: string
|
||||
{
|
||||
case ApiKey = 'apiKey';
|
||||
case Allocation = 'allocation';
|
||||
case BackupHost = 'backupHost';
|
||||
case DatabaseHost = 'databaseHost';
|
||||
case Database = 'database';
|
||||
case Egg = 'egg';
|
||||
@@ -34,4 +36,9 @@ enum RolePermissionModels: string
|
||||
{
|
||||
return RolePermissionPrefixes::Update->value . ' ' . $this->value;
|
||||
}
|
||||
|
||||
public function delete(): string
|
||||
{
|
||||
return RolePermissionPrefixes::Delete->value . ' ' . $this->value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
use BackedEnum;
|
||||
use Filament\Support\Contracts\HasColor;
|
||||
use Filament\Support\Contracts\HasIcon;
|
||||
use Filament\Support\Contracts\HasLabel;
|
||||
@@ -14,27 +15,18 @@ enum ServerState: string implements HasColor, HasIcon, HasLabel
|
||||
case Suspended = 'suspended';
|
||||
case RestoringBackup = 'restoring_backup';
|
||||
|
||||
public function getIcon(): string
|
||||
public function getIcon(): BackedEnum
|
||||
{
|
||||
return match ($this) {
|
||||
self::Installing => 'tabler-heart-bolt',
|
||||
self::InstallFailed => 'tabler-heart-x',
|
||||
self::ReinstallFailed => 'tabler-heart-x',
|
||||
self::Suspended => 'tabler-heart-cancel',
|
||||
self::RestoringBackup => 'tabler-heart-up',
|
||||
self::Installing => TablerIcon::HeartBolt,
|
||||
self::InstallFailed, self::ReinstallFailed => TablerIcon::HeartX,
|
||||
self::Suspended => TablerIcon::HeartCancel,
|
||||
self::RestoringBackup => TablerIcon::HeartUp,
|
||||
};
|
||||
}
|
||||
|
||||
public function getColor(bool $hex = false): string
|
||||
public function getColor(): string
|
||||
{
|
||||
if ($hex) {
|
||||
return match ($this) {
|
||||
self::Installing, self::RestoringBackup => '#2563EB',
|
||||
self::Suspended => '#D97706',
|
||||
self::InstallFailed, self::ReinstallFailed => '#EF4444',
|
||||
};
|
||||
}
|
||||
|
||||
return match ($this) {
|
||||
self::Installing => 'primary',
|
||||
self::InstallFailed => 'danger',
|
||||
|
||||
9
app/Enums/StepPosition.php
Normal file
9
app/Enums/StepPosition.php
Normal file
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
enum StepPosition: string
|
||||
{
|
||||
case Before = 'before';
|
||||
case After = 'after';
|
||||
}
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
use BackedEnum;
|
||||
|
||||
enum SubuserPermission: string
|
||||
{
|
||||
case WebsocketConnect = 'websocket.connect';
|
||||
@@ -48,6 +50,9 @@ enum SubuserPermission: string
|
||||
|
||||
case ActivityRead = 'activity.read';
|
||||
|
||||
case MountRead = 'mount.read';
|
||||
case MountUpdate = 'mount.update';
|
||||
|
||||
case StartupRead = 'startup.read';
|
||||
case StartupUpdate = 'startup.update';
|
||||
case StartupDockerImage = 'startup.docker-image';
|
||||
@@ -55,6 +60,7 @@ enum SubuserPermission: string
|
||||
case SettingsRename = 'settings.rename';
|
||||
case SettingsDescription = 'settings.description';
|
||||
case SettingsReinstall = 'settings.reinstall';
|
||||
case SettingsChangeIcon = 'settings.change-icon';
|
||||
|
||||
/** @return string[] */
|
||||
public function split(): array
|
||||
@@ -67,21 +73,22 @@ enum SubuserPermission: string
|
||||
return $this === self::WebsocketConnect;
|
||||
}
|
||||
|
||||
public function getIcon(): ?string
|
||||
public function getIcon(): ?BackedEnum
|
||||
{
|
||||
[$group, $permission] = $this->split();
|
||||
|
||||
return match ($group) {
|
||||
'control' => 'tabler-terminal-2',
|
||||
'user' => 'tabler-users',
|
||||
'file' => 'tabler-files',
|
||||
'backup' => 'tabler-file-zip',
|
||||
'allocation' => 'tabler-network',
|
||||
'startup' => 'tabler-player-play',
|
||||
'database' => 'tabler-database',
|
||||
'schedule' => 'tabler-clock',
|
||||
'settings' => 'tabler-settings',
|
||||
'activity' => 'tabler-stack',
|
||||
'control' => TablerIcon::Terminal2,
|
||||
'user' => TablerIcon::Users,
|
||||
'file' => TablerIcon::Files,
|
||||
'backup' => TablerIcon::FileZip,
|
||||
'allocation' => TablerIcon::Network,
|
||||
'startup' => TablerIcon::PlayerPlay,
|
||||
'database' => TablerIcon::Database,
|
||||
'schedule' => TablerIcon::Clock,
|
||||
'settings' => TablerIcon::Settings,
|
||||
'activity' => TablerIcon::Stack,
|
||||
'mount' => TablerIcon::LayersLinked,
|
||||
default => null,
|
||||
};
|
||||
}
|
||||
|
||||
9
app/Enums/TabPosition.php
Normal file
9
app/Enums/TabPosition.php
Normal file
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
enum TabPosition: string
|
||||
{
|
||||
case Before = 'before';
|
||||
case After = 'after';
|
||||
}
|
||||
5997
app/Enums/TablerIcon.php
Normal file
5997
app/Enums/TablerIcon.php
Normal file
File diff suppressed because it is too large
Load Diff
17
app/Enums/WebhookScope.php
Normal file
17
app/Enums/WebhookScope.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
enum WebhookScope: string
|
||||
{
|
||||
case Global = 'global';
|
||||
case Server = 'server';
|
||||
|
||||
public function getLabel(): string
|
||||
{
|
||||
return match ($this) {
|
||||
self::Global => 'Global',
|
||||
self::Server => 'Server',
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
use BackedEnum;
|
||||
use Filament\Support\Contracts\HasColor;
|
||||
use Filament\Support\Contracts\HasIcon;
|
||||
use Filament\Support\Contracts\HasLabel;
|
||||
@@ -24,11 +25,11 @@ enum WebhookType: string implements HasColor, HasIcon, HasLabel
|
||||
};
|
||||
}
|
||||
|
||||
public function getIcon(): string
|
||||
public function getIcon(): BackedEnum
|
||||
{
|
||||
return match ($this) {
|
||||
self::Regular => 'tabler-world-www',
|
||||
self::Discord => 'tabler-brand-discord',
|
||||
self::Regular => TablerIcon::WorldWww,
|
||||
self::Discord => TablerIcon::BrandDiscord,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
17
app/Events/Server/BackupCompleted.php
Normal file
17
app/Events/Server/BackupCompleted.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace App\Events\Server;
|
||||
|
||||
use App\Events\Event;
|
||||
use App\Models\Backup;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class BackupCompleted extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*/
|
||||
public function __construct(public Backup $backup) {}
|
||||
}
|
||||
17
app/Events/User/Deleting.php
Normal file
17
app/Events/User/Deleting.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace App\Events\User;
|
||||
|
||||
use App\Events\Event;
|
||||
use App\Models\User;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class Deleting extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*/
|
||||
public function __construct(public User $user) {}
|
||||
}
|
||||
13
app/Events/User/PasswordChanged.php
Normal file
13
app/Events/User/PasswordChanged.php
Normal file
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Events\User;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
|
||||
final class PasswordChanged
|
||||
{
|
||||
use Dispatchable;
|
||||
|
||||
public function __construct(public readonly User $user) {}
|
||||
}
|
||||
@@ -11,6 +11,6 @@ class FileSizeTooLargeException extends DisplayException
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct('The file you are attempting to open is too large to view in the file editor.');
|
||||
parent::__construct(trans('exceptions.server.file_too_large'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,17 +15,17 @@ class ServerStateConflictException extends ConflictHttpException
|
||||
*/
|
||||
public function __construct(Server $server, ?Throwable $previous = null)
|
||||
{
|
||||
$message = 'This server is currently in an unsupported state, please try again later.';
|
||||
$message = trans('exceptions.server.state_conflict');
|
||||
if ($server->isSuspended()) {
|
||||
$message = 'This server is currently suspended and the functionality requested is unavailable.';
|
||||
$message = trans('exceptions.server.suspended');
|
||||
} elseif ($server->node->isUnderMaintenance()) {
|
||||
$message = 'The node of this server is currently under maintenance and the functionality requested is unavailable.';
|
||||
$message = trans('exceptions.server.maintenance');
|
||||
} elseif (!$server->isInstalled()) {
|
||||
$message = 'This server has not yet completed its installation process, please try again later.';
|
||||
$message = trans('exceptions.server.marked_as_failed');
|
||||
} elseif ($server->status === ServerState::RestoringBackup) {
|
||||
$message = 'This server is currently restoring from a backup, please try again later.';
|
||||
$message = trans('exceptions.server.restoring_backup');
|
||||
} elseif (!is_null($server->transfer)) {
|
||||
$message = 'This server is currently being transferred to a new machine, please try again later.';
|
||||
$message = trans('exceptions.server.transferring');
|
||||
}
|
||||
|
||||
parent::__construct($message, $previous);
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
use App\Exceptions\Solutions\ManifestDoesNotExistSolution;
|
||||
use Exception;
|
||||
use Spatie\Ignition\Contracts\ProvidesSolution;
|
||||
use Spatie\Ignition\Contracts\Solution;
|
||||
|
||||
class ManifestDoesNotExistException extends Exception implements ProvidesSolution
|
||||
{
|
||||
public function getSolution(): Solution
|
||||
{
|
||||
return new ManifestDoesNotExistSolution();
|
||||
}
|
||||
}
|
||||
7
app/Exceptions/PluginIdMismatchException.php
Normal file
7
app/Exceptions/PluginIdMismatchException.php
Normal file
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
use Exception;
|
||||
|
||||
class PluginIdMismatchException extends Exception {}
|
||||
@@ -1,25 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions\Solutions;
|
||||
|
||||
use Spatie\Ignition\Contracts\Solution;
|
||||
|
||||
class ManifestDoesNotExistSolution implements Solution
|
||||
{
|
||||
public function getSolutionTitle(): string
|
||||
{
|
||||
return "The manifest.json file hasn't been generated yet";
|
||||
}
|
||||
|
||||
public function getSolutionDescription(): string
|
||||
{
|
||||
return 'Run yarn run build:production to build the frontend first.';
|
||||
}
|
||||
|
||||
public function getDocumentationLinks(): array
|
||||
{
|
||||
return [
|
||||
'Docs' => 'https://github.com/pelican/panel/blob/master/package.json',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace App\Extensions\BackupAdapter;
|
||||
|
||||
use App\Models\Backup;
|
||||
use App\Models\User;
|
||||
use Filament\Schemas\Components\Component;
|
||||
|
||||
interface BackupAdapterSchemaInterface
|
||||
{
|
||||
public function getId(): string;
|
||||
|
||||
public function getName(): string;
|
||||
|
||||
public function createBackup(Backup $backup): void;
|
||||
|
||||
public function deleteBackup(Backup $backup): void;
|
||||
|
||||
public function getDownloadLink(Backup $backup, User $user): string;
|
||||
|
||||
/** @return Component[] */
|
||||
public function getConfigurationForm(): array;
|
||||
}
|
||||
35
app/Extensions/BackupAdapter/BackupAdapterService.php
Normal file
35
app/Extensions/BackupAdapter/BackupAdapterService.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace App\Extensions\BackupAdapter;
|
||||
|
||||
class BackupAdapterService
|
||||
{
|
||||
/** @var array<string, BackupAdapterSchemaInterface> */
|
||||
private array $schemas = [];
|
||||
|
||||
/** @return BackupAdapterSchemaInterface[] */
|
||||
public function getAll(): array
|
||||
{
|
||||
return $this->schemas;
|
||||
}
|
||||
|
||||
public function get(string $id): ?BackupAdapterSchemaInterface
|
||||
{
|
||||
return array_get($this->schemas, $id);
|
||||
}
|
||||
|
||||
public function register(BackupAdapterSchemaInterface $schema): void
|
||||
{
|
||||
if (array_key_exists($schema->getId(), $this->schemas)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->schemas[$schema->getId()] = $schema;
|
||||
}
|
||||
|
||||
/** @return array<string, string> */
|
||||
public function getMappings(): array
|
||||
{
|
||||
return collect($this->schemas)->mapWithKeys(fn ($schema) => [$schema->getId() => $schema->getName()])->all();
|
||||
}
|
||||
}
|
||||
14
app/Extensions/BackupAdapter/Schemas/BackupAdapterSchema.php
Normal file
14
app/Extensions/BackupAdapter/Schemas/BackupAdapterSchema.php
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace App\Extensions\BackupAdapter\Schemas;
|
||||
|
||||
use App\Extensions\BackupAdapter\BackupAdapterSchemaInterface;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
abstract class BackupAdapterSchema implements BackupAdapterSchemaInterface
|
||||
{
|
||||
public function getName(): string
|
||||
{
|
||||
return Str::title($this->getId());
|
||||
}
|
||||
}
|
||||
207
app/Extensions/BackupAdapter/Schemas/S3BackupSchema.php
Normal file
207
app/Extensions/BackupAdapter/Schemas/S3BackupSchema.php
Normal file
@@ -0,0 +1,207 @@
|
||||
<?php
|
||||
|
||||
namespace App\Extensions\BackupAdapter\Schemas;
|
||||
|
||||
use App\Enums\TablerIcon;
|
||||
use App\Http\Controllers\Api\Remote\Backups\BackupRemoteUploadController;
|
||||
use App\Models\Backup;
|
||||
use App\Models\BackupHost;
|
||||
use App\Models\User;
|
||||
use App\Repositories\Daemon\DaemonBackupRepository;
|
||||
use Aws\S3\S3Client;
|
||||
use Carbon\CarbonImmutable;
|
||||
use Exception;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Toggle;
|
||||
use Filament\Schemas\Components\Component;
|
||||
use Filament\Schemas\Components\StateCasts\BooleanStateCast;
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
final class S3BackupSchema extends BackupAdapterSchema
|
||||
{
|
||||
public function __construct(private readonly DaemonBackupRepository $repository) {}
|
||||
|
||||
private function createClient(BackupHost $backupHost): S3Client
|
||||
{
|
||||
$config = $backupHost->configuration;
|
||||
$config['version'] = 'latest';
|
||||
|
||||
if (!empty($config['key']) && !empty($config['secret'])) {
|
||||
$config['credentials'] = Arr::only($config, ['key', 'secret', 'token']);
|
||||
}
|
||||
|
||||
return new S3Client($config);
|
||||
}
|
||||
|
||||
public function getId(): string
|
||||
{
|
||||
return 's3';
|
||||
}
|
||||
|
||||
public function createBackup(Backup $backup): void
|
||||
{
|
||||
$this->repository->setServer($backup->server)->create($backup);
|
||||
}
|
||||
|
||||
public function deleteBackup(Backup $backup): void
|
||||
{
|
||||
$client = $this->createClient($backup->backupHost);
|
||||
|
||||
$client->deleteObject([
|
||||
'Bucket' => $backup->backupHost->configuration['bucket'],
|
||||
'Key' => "{$backup->server->uuid}/$backup->uuid.tar.gz",
|
||||
]);
|
||||
}
|
||||
|
||||
public function getDownloadLink(Backup $backup, User $user): string
|
||||
{
|
||||
$client = $this->createClient($backup->backupHost);
|
||||
|
||||
$request = $client->createPresignedRequest(
|
||||
$client->getCommand('GetObject', [
|
||||
'Bucket' => $backup->backupHost->configuration['bucket'],
|
||||
'Key' => "{$backup->server->uuid}/$backup->uuid.tar.gz",
|
||||
'ContentType' => 'application/x-gzip',
|
||||
]),
|
||||
CarbonImmutable::now()->addMinutes(5)
|
||||
);
|
||||
|
||||
return $request->getUri()->__toString();
|
||||
}
|
||||
|
||||
/** @return Component[] */
|
||||
public function getConfigurationForm(): array
|
||||
{
|
||||
return [
|
||||
TextInput::make('configuration.region')
|
||||
->label(trans('admin/setting.backup.s3.default_region'))
|
||||
->required(),
|
||||
TextInput::make('configuration.key')
|
||||
->label(trans('admin/setting.backup.s3.access_key'))
|
||||
->required(),
|
||||
TextInput::make('configuration.secret')
|
||||
->label(trans('admin/setting.backup.s3.secret_key'))
|
||||
->required(),
|
||||
TextInput::make('configuration.bucket')
|
||||
->label(trans('admin/setting.backup.s3.bucket'))
|
||||
->required(),
|
||||
TextInput::make('configuration.endpoint')
|
||||
->label(trans('admin/setting.backup.s3.endpoint'))
|
||||
->required(),
|
||||
Toggle::make('configuration.use_path_style_endpoint')
|
||||
->label(trans('admin/setting.backup.s3.use_path_style_endpoint'))
|
||||
->inline(false)
|
||||
->onIcon(TablerIcon::Check)
|
||||
->offIcon(TablerIcon::X)
|
||||
->onColor('success')
|
||||
->offColor('danger')
|
||||
->live()
|
||||
->stateCast(new BooleanStateCast(false)),
|
||||
];
|
||||
}
|
||||
|
||||
/** @return array{parts: string[], part_size: int} */
|
||||
public function getUploadParts(Backup $backup, int $size): array
|
||||
{
|
||||
$expires = CarbonImmutable::now()->addMinutes(config('backups.presigned_url_lifespan', 60));
|
||||
|
||||
// Params for generating the presigned urls
|
||||
$params = [
|
||||
'Bucket' => $backup->backupHost->configuration['bucket'],
|
||||
'Key' => "{$backup->server->uuid}/$backup->uuid.tar.gz",
|
||||
'ContentType' => 'application/x-gzip',
|
||||
];
|
||||
|
||||
$storageClass = $backup->backupHost->configuration['storage_class'];
|
||||
if (!is_null($storageClass)) {
|
||||
$params['StorageClass'] = $storageClass;
|
||||
}
|
||||
|
||||
$client = $this->createClient($backup->backupHost);
|
||||
|
||||
// Execute the CreateMultipartUpload request
|
||||
$result = $client->execute($client->getCommand('CreateMultipartUpload', $params));
|
||||
|
||||
// Get the UploadId from the CreateMultipartUpload request, this is needed to create
|
||||
// the other presigned urls.
|
||||
$params['UploadId'] = $result->get('UploadId');
|
||||
|
||||
// Retrieve configured part size
|
||||
$maxPartSize = config('backups.max_part_size', BackupRemoteUploadController::DEFAULT_MAX_PART_SIZE);
|
||||
if ($maxPartSize <= 0) {
|
||||
$maxPartSize = BackupRemoteUploadController::DEFAULT_MAX_PART_SIZE;
|
||||
}
|
||||
|
||||
// Create as many UploadPart presigned urls as needed
|
||||
$parts = [];
|
||||
for ($i = 0; $i < ($size / $maxPartSize); $i++) {
|
||||
$parts[] = $client->createPresignedRequest(
|
||||
$client->getCommand('UploadPart', array_merge($params, ['PartNumber' => $i + 1])),
|
||||
$expires
|
||||
)->getUri()->__toString();
|
||||
}
|
||||
|
||||
// Set the upload_id on the backup in the database.
|
||||
$backup->update(['upload_id' => $params['UploadId']]);
|
||||
|
||||
return [
|
||||
'parts' => $parts,
|
||||
'part_size' => $maxPartSize,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks a multipart upload in a given S3-compatible instance as failed or successful for the given backup.
|
||||
*
|
||||
* @param ?array<array{int, etag: string, part_number: string}> $parts
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function completeMultipartUpload(Backup $backup, bool $successful, ?array $parts): void
|
||||
{
|
||||
// This should never really happen, but if it does don't let us fall victim to Amazon's
|
||||
// wildly fun error messaging. Just stop the process right here.
|
||||
if (empty($backup->upload_id)) {
|
||||
// A failed backup doesn't need to error here, this can happen if the backup encounters
|
||||
// an error before we even start the upload. AWS gives you tooling to clear these failed
|
||||
// multipart uploads as needed too.
|
||||
if (!$successful) {
|
||||
return;
|
||||
}
|
||||
|
||||
throw new Exception('Cannot complete backup request: no upload_id present on model.');
|
||||
}
|
||||
|
||||
$params = [
|
||||
'Bucket' => $backup->backupHost->configuration['bucket'],
|
||||
'Key' => "{$backup->server->uuid}/$backup->uuid.tar.gz",
|
||||
'UploadId' => $backup->upload_id,
|
||||
];
|
||||
|
||||
$client = $this->createClient($backup->backupHost);
|
||||
|
||||
if (!$successful) {
|
||||
$client->execute($client->getCommand('AbortMultipartUpload', $params));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise send a CompleteMultipartUpload request.
|
||||
$params['MultipartUpload'] = [
|
||||
'Parts' => [],
|
||||
];
|
||||
|
||||
if (is_null($parts)) {
|
||||
$params['MultipartUpload']['Parts'] = $client->execute($client->getCommand('ListParts', $params))['Parts'];
|
||||
} else {
|
||||
foreach ($parts as $part) {
|
||||
$params['MultipartUpload']['Parts'][] = [
|
||||
'ETag' => $part['etag'],
|
||||
'PartNumber' => $part['part_number'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$client->execute($client->getCommand('CompleteMultipartUpload', $params));
|
||||
}
|
||||
}
|
||||
66
app/Extensions/BackupAdapter/Schemas/WingsBackupSchema.php
Normal file
66
app/Extensions/BackupAdapter/Schemas/WingsBackupSchema.php
Normal file
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
namespace App\Extensions\BackupAdapter\Schemas;
|
||||
|
||||
use App\Enums\NodeJwtScope;
|
||||
use App\Models\Backup;
|
||||
use App\Models\User;
|
||||
use App\Repositories\Daemon\DaemonBackupRepository;
|
||||
use App\Services\Nodes\NodeJWTService;
|
||||
use Carbon\CarbonImmutable;
|
||||
use Exception;
|
||||
use Filament\Infolists\Components\TextEntry;
|
||||
use Filament\Schemas\Components\Component;
|
||||
use Illuminate\Http\Response;
|
||||
|
||||
final class WingsBackupSchema extends BackupAdapterSchema
|
||||
{
|
||||
public function __construct(private readonly DaemonBackupRepository $repository, private readonly NodeJWTService $jwtService) {}
|
||||
|
||||
public function getId(): string
|
||||
{
|
||||
return 'wings';
|
||||
}
|
||||
|
||||
public function createBackup(Backup $backup): void
|
||||
{
|
||||
$this->repository->setServer($backup->server)->create($backup);
|
||||
}
|
||||
|
||||
/** @throws Exception */
|
||||
public function deleteBackup(Backup $backup): void
|
||||
{
|
||||
try {
|
||||
$this->repository->setServer($backup->server)->delete($backup);
|
||||
} catch (Exception $exception) {
|
||||
// Don't fail the request if the Daemon responds with a 404, just assume the backup
|
||||
// doesn't actually exist and remove its reference from the Panel as well.
|
||||
if ($exception->getCode() !== Response::HTTP_NOT_FOUND) {
|
||||
throw $exception;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getDownloadLink(Backup $backup, User $user): string
|
||||
{
|
||||
$token = $this->jwtService
|
||||
->setExpiresAt(CarbonImmutable::now()->addMinutes(15))
|
||||
->setScopes(NodeJwtScope::BackupDownload)
|
||||
->setUser($user)
|
||||
->setClaims([
|
||||
'backup_uuid' => $backup->uuid,
|
||||
'server_uuid' => $backup->server->uuid,
|
||||
])
|
||||
->handle($backup->server->node, $user->id . $backup->server->uuid);
|
||||
|
||||
return $backup->server->node->getConnectionAddress() . '/download/backup?token=' . $token->toString();
|
||||
}
|
||||
|
||||
/** @return Component[] */
|
||||
public function getConfigurationForm(): array
|
||||
{
|
||||
return [
|
||||
TextEntry::make(trans('admin/backuphost.no_configuration')),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,177 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Extensions\Backups;
|
||||
|
||||
use App\Extensions\Filesystem\S3Filesystem;
|
||||
use Aws\S3\S3Client;
|
||||
use Closure;
|
||||
use Illuminate\Foundation\Application;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Str;
|
||||
use InvalidArgumentException;
|
||||
use League\Flysystem\FilesystemAdapter;
|
||||
use League\Flysystem\InMemory\InMemoryFilesystemAdapter;
|
||||
use Webmozart\Assert\Assert;
|
||||
|
||||
class BackupManager
|
||||
{
|
||||
/**
|
||||
* The array of resolved backup drivers.
|
||||
*
|
||||
* @var array<string, FilesystemAdapter>
|
||||
*/
|
||||
protected array $adapters = [];
|
||||
|
||||
/**
|
||||
* The registered custom driver creators.
|
||||
*
|
||||
* @var array<string, callable>
|
||||
*/
|
||||
protected array $customCreators;
|
||||
|
||||
public function __construct(protected Application $app) {}
|
||||
|
||||
/**
|
||||
* Returns a backup adapter instance.
|
||||
*/
|
||||
public function adapter(?string $name = null): FilesystemAdapter
|
||||
{
|
||||
return $this->get($name ?: $this->getDefaultAdapter());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the given backup adapter instance.
|
||||
*/
|
||||
public function set(string $name, FilesystemAdapter $disk): self
|
||||
{
|
||||
$this->adapters[$name] = $disk;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a backup adapter.
|
||||
*/
|
||||
protected function get(string $name): FilesystemAdapter
|
||||
{
|
||||
return $this->adapters[$name] = $this->resolve($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the given backup disk.
|
||||
*/
|
||||
protected function resolve(string $name): FilesystemAdapter
|
||||
{
|
||||
$config = $this->getConfig($name);
|
||||
|
||||
if (empty($config['adapter'])) {
|
||||
throw new InvalidArgumentException("Backup disk [$name] does not have a configured adapter.");
|
||||
}
|
||||
|
||||
$adapter = $config['adapter'];
|
||||
|
||||
if (isset($this->customCreators[$name])) {
|
||||
return $this->callCustomCreator($config);
|
||||
}
|
||||
|
||||
$adapterMethod = 'create' . Str::studly($adapter) . 'Adapter';
|
||||
if (method_exists($this, $adapterMethod)) {
|
||||
$instance = $this->{$adapterMethod}($config);
|
||||
|
||||
Assert::isInstanceOf($instance, FilesystemAdapter::class);
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException("Adapter [$adapter] is not supported.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls a custom creator for a given adapter type.
|
||||
*
|
||||
* @param array{adapter: string} $config
|
||||
*/
|
||||
protected function callCustomCreator(array $config): mixed
|
||||
{
|
||||
return $this->customCreators[$config['adapter']]($this->app, $config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new daemon adapter.
|
||||
*
|
||||
* @param array<string, string> $config
|
||||
*/
|
||||
public function createWingsAdapter(array $config): FilesystemAdapter
|
||||
{
|
||||
return new InMemoryFilesystemAdapter();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new S3 adapter.
|
||||
*
|
||||
* @param array<string, string> $config
|
||||
*/
|
||||
public function createS3Adapter(array $config): FilesystemAdapter
|
||||
{
|
||||
$config['version'] = 'latest';
|
||||
|
||||
if (!empty($config['key']) && !empty($config['secret'])) {
|
||||
$config['credentials'] = Arr::only($config, ['key', 'secret', 'token']);
|
||||
}
|
||||
|
||||
$client = new S3Client($config);
|
||||
|
||||
return new S3Filesystem($client, $config['bucket'], $config['prefix'] ?? '', $config['options'] ?? []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the configuration associated with a given backup type.
|
||||
*
|
||||
* @return array<mixed>
|
||||
*/
|
||||
protected function getConfig(string $name): array
|
||||
{
|
||||
return config("backups.disks.$name") ?: [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default backup driver name.
|
||||
*/
|
||||
public function getDefaultAdapter(): string
|
||||
{
|
||||
return config('backups.default');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the default session driver name.
|
||||
*/
|
||||
public function setDefaultAdapter(string $name): void
|
||||
{
|
||||
config()->set('backups.default', $name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unset the given adapter instances.
|
||||
*
|
||||
* @param string|string[] $adapter
|
||||
*/
|
||||
public function forget(array|string $adapter): self
|
||||
{
|
||||
$adapters = &$this->adapters;
|
||||
foreach ((array) $adapter as $adapterName) {
|
||||
unset($adapters[$adapterName]);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a custom adapter creator closure.
|
||||
*/
|
||||
public function extend(string $adapter, Closure $callback): self
|
||||
{
|
||||
$this->customCreators[$adapter] = $callback;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Extensions\Captcha\Schemas;
|
||||
|
||||
use BackedEnum;
|
||||
use Filament\Schemas\Components\Component;
|
||||
|
||||
interface CaptchaSchemaInterface
|
||||
@@ -24,7 +25,7 @@ interface CaptchaSchemaInterface
|
||||
*/
|
||||
public function getSettingsForm(): array;
|
||||
|
||||
public function getIcon(): ?string;
|
||||
public function getIcon(): null|string|BackedEnum;
|
||||
|
||||
public function validateResponse(?string $captchaResponse = null): void;
|
||||
}
|
||||
|
||||
@@ -2,8 +2,10 @@
|
||||
|
||||
namespace App\Extensions\Captcha\Schemas\Turnstile;
|
||||
|
||||
use App\Enums\TablerIcon;
|
||||
use App\Extensions\Captcha\Schemas\BaseSchema;
|
||||
use App\Extensions\Captcha\Schemas\CaptchaSchemaInterface;
|
||||
use BackedEnum;
|
||||
use Exception;
|
||||
use Filament\Forms\Components\Toggle;
|
||||
use Filament\Infolists\Components\TextEntry;
|
||||
@@ -19,7 +21,7 @@ class TurnstileSchema extends BaseSchema implements CaptchaSchemaInterface
|
||||
|
||||
public function isEnabled(): bool
|
||||
{
|
||||
return env('CAPTCHA_TURNSTILE_ENABLED', false);
|
||||
return (bool) env('CAPTCHA_TURNSTILE_ENABLED', false);
|
||||
}
|
||||
|
||||
public function getFormComponent(): Component
|
||||
@@ -49,8 +51,8 @@ class TurnstileSchema extends BaseSchema implements CaptchaSchemaInterface
|
||||
->label(trans('admin/setting.captcha.verify'))
|
||||
->columnSpan(2)
|
||||
->inline(false)
|
||||
->onIcon('tabler-check')
|
||||
->offIcon('tabler-x')
|
||||
->onIcon(TablerIcon::Check)
|
||||
->offIcon(TablerIcon::X)
|
||||
->onColor('success')
|
||||
->offColor('danger')
|
||||
->default(env('CAPTCHA_TURNSTILE_VERIFY_DOMAIN', true)),
|
||||
@@ -61,9 +63,9 @@ class TurnstileSchema extends BaseSchema implements CaptchaSchemaInterface
|
||||
]);
|
||||
}
|
||||
|
||||
public function getIcon(): ?string
|
||||
public function getIcon(): BackedEnum
|
||||
{
|
||||
return 'tabler-brand-cloudflare';
|
||||
return TablerIcon::BrandCloudflare;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -73,9 +75,7 @@ class TurnstileSchema extends BaseSchema implements CaptchaSchemaInterface
|
||||
{
|
||||
$captchaResponse ??= request()->get('cf-turnstile-response');
|
||||
|
||||
if (!$secret = env('CAPTCHA_TURNSTILE_SECRET_KEY')) {
|
||||
throw new Exception('Turnstile secret key is not defined.');
|
||||
}
|
||||
throw_unless($secret = env('CAPTCHA_TURNSTILE_SECRET_KEY'), new Exception('Turnstile secret key is not defined.'));
|
||||
|
||||
$response = Http::asJson()
|
||||
->timeout(15)
|
||||
@@ -99,9 +99,7 @@ class TurnstileSchema extends BaseSchema implements CaptchaSchemaInterface
|
||||
};
|
||||
}
|
||||
|
||||
if (!$this->verifyDomain($response['hostname'] ?? '')) {
|
||||
throw new Exception('Domain verification failed.');
|
||||
}
|
||||
throw_unless($this->verifyDomain($response['hostname'] ?? ''), new Exception('Domain verification failed.'));
|
||||
}
|
||||
|
||||
private function verifyDomain(string $hostname): bool
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Extensions\Features\Schemas;
|
||||
|
||||
use App\Enums\SubuserPermission;
|
||||
use App\Enums\TablerIcon;
|
||||
use App\Extensions\Features\FeatureSchemaInterface;
|
||||
use App\Facades\Activity;
|
||||
use App\Models\Server;
|
||||
@@ -51,13 +52,13 @@ class GSLTokenSchema implements FeatureSchemaInterface
|
||||
|
||||
return Action::make($this->getId())
|
||||
->requiresConfirmation()
|
||||
->modalHeading('Invalid GSL token')
|
||||
->modalDescription('It seems like your Gameserver Login Token (GSL token) is invalid or has expired.')
|
||||
->modalSubmitActionLabel('Update GSL Token')
|
||||
->modalHeading(trans('server/feature.gsl_token.heading'))
|
||||
->modalDescription(trans('server/feature.gsl_token.description'))
|
||||
->modalSubmitActionLabel(trans('server/feature.gsl_token.submit'))
|
||||
->disabledSchema(fn () => !user()?->can(SubuserPermission::StartupUpdate, $server))
|
||||
->schema([
|
||||
TextEntry::make('info')
|
||||
->label(new HtmlString(Blade::render('You can either <x-filament::link href="https://steamcommunity.com/dev/managegameservers" target="_blank">generate a new one</x-filament::link> and enter it below or leave the field blank to remove it completely.'))),
|
||||
->label(new HtmlString(Blade::render(trans('server/feature.gsl_token.info')))),
|
||||
TextInput::make('gsltoken')
|
||||
->label('GSL Token')
|
||||
->rules([
|
||||
@@ -73,7 +74,7 @@ class GSLTokenSchema implements FeatureSchemaInterface
|
||||
}
|
||||
},
|
||||
])
|
||||
->hintIcon('tabler-code', fn () => implode('|', $serverVariable->variable->rules))
|
||||
->hintIcon(TablerIcon::Code, fn () => implode('|', $serverVariable->variable->rules))
|
||||
->label(fn () => $serverVariable->variable->name)
|
||||
->prefix(fn () => '{{' . $serverVariable->variable->env_variable . '}}')
|
||||
->helperText(fn () => empty($serverVariable->variable->description) ? '—' : $serverVariable->variable->description),
|
||||
@@ -103,13 +104,13 @@ class GSLTokenSchema implements FeatureSchemaInterface
|
||||
$serverRepository->setServer($server)->power('restart');
|
||||
|
||||
Notification::make()
|
||||
->title('GSL Token updated')
|
||||
->body('Server will restart now.')
|
||||
->title(trans('server/feature.gsl_token.updated'))
|
||||
->body(trans('server/feature.restart_now'))
|
||||
->success()
|
||||
->send();
|
||||
} catch (Exception $exception) {
|
||||
Notification::make()
|
||||
->title('Could not update GSL Token')
|
||||
->title(trans('server/feature.gsl_token.failed'))
|
||||
->body($exception->getMessage())
|
||||
->danger()
|
||||
->send();
|
||||
|
||||
@@ -41,15 +41,15 @@ class JavaVersionSchema implements FeatureSchemaInterface
|
||||
|
||||
return Action::make($this->getId())
|
||||
->requiresConfirmation()
|
||||
->modalHeading('Unsupported Java Version')
|
||||
->modalDescription('This server is currently running an unsupported version of Java and cannot be started.')
|
||||
->modalSubmitActionLabel('Update Docker Image')
|
||||
->modalHeading(trans('server/feature.java_version.heading'))
|
||||
->modalDescription(trans('server/feature.java_version.description'))
|
||||
->modalSubmitActionLabel(trans('server/feature.java_version.submit'))
|
||||
->disabledSchema(fn () => !user()?->can(SubuserPermission::StartupDockerImage, $server))
|
||||
->schema([
|
||||
TextEntry::make('java')
|
||||
->label('Please select a supported version from the list below to continue starting the server.'),
|
||||
->label(trans('server/feature.java_version.select_version')),
|
||||
Select::make('image')
|
||||
->label('Docker Image')
|
||||
->label(trans('server/feature.java_version.docker_image'))
|
||||
->disabled(fn () => !in_array($server->image, $server->egg->docker_images))
|
||||
->options(fn () => collect($server->egg->docker_images)->mapWithKeys(fn ($key, $value) => [$key => $value]))
|
||||
->selectablePlaceholder(false)
|
||||
@@ -73,13 +73,13 @@ class JavaVersionSchema implements FeatureSchemaInterface
|
||||
$serverRepository->setServer($server)->power('restart');
|
||||
|
||||
Notification::make()
|
||||
->title('Docker image updated')
|
||||
->body('Server will restart now.')
|
||||
->title(trans('server/feature.java_version.updated'))
|
||||
->body(trans('server/feature.restart_now'))
|
||||
->success()
|
||||
->send();
|
||||
} catch (Exception $exception) {
|
||||
Notification::make()
|
||||
->title('Could not update docker image')
|
||||
->title(trans('server/feature.java_version.failed'))
|
||||
->body($exception->getMessage())
|
||||
->danger()
|
||||
->send();
|
||||
|
||||
@@ -32,9 +32,9 @@ class MinecraftEulaSchema implements FeatureSchemaInterface
|
||||
{
|
||||
return Action::make($this->getId())
|
||||
->requiresConfirmation()
|
||||
->modalHeading('Minecraft EULA')
|
||||
->modalDescription(new HtmlString(Blade::render('By pressing "I Accept" below you are indicating your agreement to the <x-filament::link href="https://minecraft.net/eula" target="_blank">Minecraft EULA </x-filament::link>.')))
|
||||
->modalSubmitActionLabel('I Accept')
|
||||
->modalHeading(trans('server/feature.eula.heading'))
|
||||
->modalDescription(new HtmlString(Blade::render(trans('server/feature.eula.description'))))
|
||||
->modalSubmitActionLabel(trans('server/feature.eula.accept'))
|
||||
->action(function (DaemonFileRepository $fileRepository, DaemonServerRepository $serverRepository) {
|
||||
try {
|
||||
/** @var Server $server */
|
||||
@@ -45,13 +45,13 @@ class MinecraftEulaSchema implements FeatureSchemaInterface
|
||||
$serverRepository->setServer($server)->power('restart');
|
||||
|
||||
Notification::make()
|
||||
->title('Minecraft EULA accepted')
|
||||
->body('Server will restart now.')
|
||||
->title(trans('server/feature.eula.accepted'))
|
||||
->body(trans('server/feature.restart_now'))
|
||||
->success()
|
||||
->send();
|
||||
} catch (Exception $exception) {
|
||||
Notification::make()
|
||||
->title('Could not accept Minecraft EULA')
|
||||
->title(trans('server/feature.eula.failed'))
|
||||
->body($exception->getMessage())
|
||||
->danger()
|
||||
->send();
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
namespace App\Extensions\Features\Schemas;
|
||||
|
||||
use App\Enums\TablerIcon;
|
||||
use App\Extensions\Features\FeatureSchemaInterface;
|
||||
use Filament\Actions\Action;
|
||||
use Illuminate\Support\Facades\Blade;
|
||||
use Illuminate\Support\HtmlString;
|
||||
|
||||
class PIDLimitSchema implements FeatureSchemaInterface
|
||||
@@ -31,36 +31,10 @@ class PIDLimitSchema implements FeatureSchemaInterface
|
||||
{
|
||||
return Action::make($this->getId())
|
||||
->requiresConfirmation()
|
||||
->icon('tabler-alert-triangle')
|
||||
->modalHeading(fn () => user()?->isAdmin() ? 'Memory or process limit reached...' : 'Possible resource limit reached...')
|
||||
->modalDescription(new HtmlString(Blade::render(
|
||||
user()?->isAdmin() ? <<<'HTML'
|
||||
<p>
|
||||
This server has reached the maximum process or memory limit.
|
||||
</p>
|
||||
<p class="mt-4">
|
||||
Increasing <code>container_pid_limit</code> in the wings
|
||||
configuration, <code>config.yml</code>, might help resolve
|
||||
this issue.
|
||||
</p>
|
||||
<p class="mt-4">
|
||||
<b>Note: Wings must be restarted for the configuration file changes to take effect</b>
|
||||
</p>
|
||||
HTML
|
||||
:
|
||||
<<<'HTML'
|
||||
<p>
|
||||
This server is attempting to use more resources than allocated. Please contact the administrator
|
||||
and give them the error below.
|
||||
</p>
|
||||
<p class="mt-4">
|
||||
<code>
|
||||
pthread_create failed, Possibly out of memory or process/resource limits reached
|
||||
</code>
|
||||
</p>
|
||||
HTML
|
||||
)))
|
||||
->modalCancelActionLabel('Close')
|
||||
->icon(TablerIcon::AlertTriangle)
|
||||
->modalHeading(fn () => user()?->isAdmin() ? trans('server/feature.pid_limit.heading_admin') : trans('server/feature.pid_limit.heading_user'))
|
||||
->modalDescription(new HtmlString(user()?->isAdmin() ? trans('server/feature.pid_limit.description_admin') : trans('server/feature.pid_limit.description_user')))
|
||||
->modalCancelActionLabel(trans('server/feature.close'))
|
||||
->action(fn () => null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ namespace App\Extensions\Features\Schemas;
|
||||
|
||||
use App\Extensions\Features\FeatureSchemaInterface;
|
||||
use Filament\Actions\Action;
|
||||
use Illuminate\Support\Facades\Blade;
|
||||
use Illuminate\Support\HtmlString;
|
||||
|
||||
class SteamDiskSpaceSchema implements FeatureSchemaInterface
|
||||
@@ -27,28 +26,9 @@ class SteamDiskSpaceSchema implements FeatureSchemaInterface
|
||||
{
|
||||
return Action::make($this->getId())
|
||||
->requiresConfirmation()
|
||||
->modalHeading('Out of available disk space...')
|
||||
->modalDescription(new HtmlString(Blade::render(
|
||||
user()?->isAdmin() ? <<<'HTML'
|
||||
<p>
|
||||
This server has run out of available disk space and cannot complete the install or update
|
||||
process.
|
||||
</p>
|
||||
<p class="mt-4">
|
||||
Ensure the machine has enough disk space by typing{' '}
|
||||
<code class="rounded py-1 px-2">df -h</code> on the machine hosting
|
||||
this server. Delete files or increase the available disk space to resolve the issue.
|
||||
</p>
|
||||
HTML
|
||||
:
|
||||
<<<'HTML'
|
||||
<p>
|
||||
This server has run out of available disk space and cannot complete the install or update
|
||||
process. Please get in touch with the administrator(s) and inform them of disk space issues.
|
||||
</p>
|
||||
HTML
|
||||
)))
|
||||
->modalCancelActionLabel('Close')
|
||||
->modalHeading(trans('server/feature.steam_disk_space.heading'))
|
||||
->modalDescription(new HtmlString(user()?->isAdmin() ? trans('server/feature.steam_disk_space.description_admin') : trans('server/feature.steam_disk_space.description_user')))
|
||||
->modalCancelActionLabel(trans('server/feature.close'))
|
||||
->action(fn () => null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Extensions\Filesystem;
|
||||
|
||||
use Aws\S3\S3ClientInterface;
|
||||
use League\Flysystem\AwsS3V3\AwsS3V3Adapter;
|
||||
|
||||
class S3Filesystem extends AwsS3V3Adapter
|
||||
{
|
||||
/**
|
||||
* @param array<mixed> $options
|
||||
*/
|
||||
public function __construct(
|
||||
private S3ClientInterface $client,
|
||||
private string $bucket,
|
||||
string $prefix = '',
|
||||
array $options = [],
|
||||
) {
|
||||
parent::__construct(
|
||||
$client,
|
||||
$bucket,
|
||||
$prefix,
|
||||
null,
|
||||
null,
|
||||
$options,
|
||||
);
|
||||
}
|
||||
|
||||
public function getClient(): S3ClientInterface
|
||||
{
|
||||
return $this->client;
|
||||
}
|
||||
|
||||
public function getBucket(): string
|
||||
{
|
||||
return $this->bucket;
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,11 @@
|
||||
|
||||
namespace App\Extensions\OAuth;
|
||||
|
||||
use App\Models\User;
|
||||
use BackedEnum;
|
||||
use Filament\Schemas\Components\Component;
|
||||
use Filament\Schemas\Components\Wizard\Step;
|
||||
use Laravel\Socialite\Contracts\User as OAuthUser;
|
||||
|
||||
interface OAuthSchemaInterface
|
||||
{
|
||||
@@ -27,13 +30,13 @@ interface OAuthSchemaInterface
|
||||
/** @return Step[] */
|
||||
public function getSetupSteps(): array;
|
||||
|
||||
public function getIcon(): ?string;
|
||||
public function getIcon(): null|string|BackedEnum;
|
||||
|
||||
public function getHexColor(): ?string;
|
||||
|
||||
public function isEnabled(): bool;
|
||||
|
||||
public function shouldCreateMissingUsers(): bool;
|
||||
public function shouldCreateMissingUser(OAuthUser $user): bool;
|
||||
|
||||
public function shouldLinkMissingUsers(): bool;
|
||||
public function shouldLinkMissingUser(User $user, OAuthUser $oauthUser): bool;
|
||||
}
|
||||
|
||||
@@ -73,11 +73,11 @@ final class AuthentikSchema extends OAuthSchema
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return env('OAUTH_AUTHENTIK_DISPLAY_NAME', 'Authentik');
|
||||
return (string) env('OAUTH_AUTHENTIK_DISPLAY_NAME', 'Authentik');
|
||||
}
|
||||
|
||||
public function getHexColor(): string
|
||||
{
|
||||
return env('OAUTH_AUTHENTIK_DISPLAY_COLOR', '#fd4b2d');
|
||||
return (string) env('OAUTH_AUTHENTIK_DISPLAY_COLOR', '#fd4b2d');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
namespace App\Extensions\OAuth\Schemas;
|
||||
|
||||
use App\Enums\TablerIcon;
|
||||
use BackedEnum;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Infolists\Components\TextEntry;
|
||||
use Filament\Schemas\Components\Wizard\Step;
|
||||
@@ -33,9 +35,9 @@ final class BitbucketSchema extends OAuthSchema
|
||||
], parent::getSetupSteps());
|
||||
}
|
||||
|
||||
public function getIcon(): string
|
||||
public function getIcon(): BackedEnum
|
||||
{
|
||||
return 'tabler-brand-bitbucket-f';
|
||||
return TablerIcon::BrandBitbucketFilled;
|
||||
}
|
||||
|
||||
public function getHexColor(): string
|
||||
|
||||
@@ -2,13 +2,15 @@
|
||||
|
||||
namespace App\Extensions\OAuth\Schemas;
|
||||
|
||||
use BackedEnum;
|
||||
|
||||
final class CommonSchema extends OAuthSchema
|
||||
{
|
||||
public function __construct(
|
||||
private readonly string $id,
|
||||
private readonly ?string $name = null,
|
||||
private readonly ?string $configName = null,
|
||||
private readonly ?string $icon = null,
|
||||
private readonly null|string|BackedEnum $icon = null,
|
||||
private readonly ?string $hexColor = null,
|
||||
) {}
|
||||
|
||||
@@ -27,7 +29,7 @@ final class CommonSchema extends OAuthSchema
|
||||
return $this->configName ?? parent::getConfigKey();
|
||||
}
|
||||
|
||||
public function getIcon(): ?string
|
||||
public function getIcon(): null|string|BackedEnum
|
||||
{
|
||||
return $this->icon;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
namespace App\Extensions\OAuth\Schemas;
|
||||
|
||||
use App\Enums\TablerIcon;
|
||||
use BackedEnum;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Infolists\Components\TextEntry;
|
||||
use Filament\Schemas\Components\Wizard\Step;
|
||||
@@ -42,9 +44,9 @@ final class DiscordSchema extends OAuthSchema
|
||||
], parent::getSetupSteps());
|
||||
}
|
||||
|
||||
public function getIcon(): string
|
||||
public function getIcon(): BackedEnum
|
||||
{
|
||||
return 'tabler-brand-discord-f';
|
||||
return TablerIcon::BrandDiscordFilled;
|
||||
}
|
||||
|
||||
public function getHexColor(): string
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
namespace App\Extensions\OAuth\Schemas;
|
||||
|
||||
use App\Enums\TablerIcon;
|
||||
use BackedEnum;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Infolists\Components\TextEntry;
|
||||
use Filament\Schemas\Components\Wizard\Step;
|
||||
@@ -36,9 +38,9 @@ final class FacebookSchema extends OAuthSchema
|
||||
], parent::getSetupSteps());
|
||||
}
|
||||
|
||||
public function getIcon(): string
|
||||
public function getIcon(): BackedEnum
|
||||
{
|
||||
return 'tabler-brand-facebook-f';
|
||||
return TablerIcon::BrandFacebookFilled;
|
||||
}
|
||||
|
||||
public function getHexColor(): string
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
namespace App\Extensions\OAuth\Schemas;
|
||||
|
||||
use App\Enums\TablerIcon;
|
||||
use BackedEnum;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Infolists\Components\TextEntry;
|
||||
use Filament\Schemas\Components\Wizard\Step;
|
||||
@@ -18,11 +20,11 @@ final class GithubSchema extends OAuthSchema
|
||||
public function getSetupSteps(): array
|
||||
{
|
||||
return array_merge([
|
||||
Step::make('Register new Github OAuth App')
|
||||
Step::make('Register new GitHub OAuth App')
|
||||
->schema([
|
||||
TextEntry::make('create_application')
|
||||
->hiddenLabel()
|
||||
->state(new HtmlString(Blade::render('<p>Visit the <x-filament::link href="https://github.com/settings/developers" target="_blank">Github Developer Dashboard</x-filament::link>, go to <b>OAuth Apps</b> and click on <b>New OAuth App</b>.</p><p>Enter an <b>Application name</b> (e.g. your panel name), set <b>Homepage URL</b> to your panel url and enter the below url as <b>Authorization callback URL</b>.</p>'))),
|
||||
->state(new HtmlString(Blade::render('<p>Visit the <x-filament::link href="https://github.com/settings/developers" target="_blank">GitHub Developer Dashboard</x-filament::link>, go to <b>OAuth Apps</b> and click on <b>New OAuth App</b>.</p><p>Enter an <b>Application name</b> (e.g. your panel name), set <b>Homepage URL</b> to your panel url and enter the below url as <b>Authorization callback URL</b>.</p>'))),
|
||||
TextInput::make('_noenv_callback')
|
||||
->label('Authorization callback URL')
|
||||
->dehydrated()
|
||||
@@ -42,9 +44,9 @@ final class GithubSchema extends OAuthSchema
|
||||
], parent::getSetupSteps());
|
||||
}
|
||||
|
||||
public function getIcon(): string
|
||||
public function getIcon(): BackedEnum
|
||||
{
|
||||
return 'tabler-brand-github-f';
|
||||
return TablerIcon::BrandGithubFilled;
|
||||
}
|
||||
|
||||
public function getHexColor(): string
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
namespace App\Extensions\OAuth\Schemas;
|
||||
|
||||
use App\Enums\TablerIcon;
|
||||
use BackedEnum;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Infolists\Components\TextEntry;
|
||||
use Filament\Schemas\Components\Wizard\Step;
|
||||
@@ -53,9 +55,9 @@ final class GitlabSchema extends OAuthSchema
|
||||
], parent::getSetupSteps());
|
||||
}
|
||||
|
||||
public function getIcon(): string
|
||||
public function getIcon(): BackedEnum
|
||||
{
|
||||
return 'tabler-brand-gitlab';
|
||||
return TablerIcon::BrandGitlab;
|
||||
}
|
||||
|
||||
public function getHexColor(): string
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
namespace App\Extensions\OAuth\Schemas;
|
||||
|
||||
use App\Enums\TablerIcon;
|
||||
use BackedEnum;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Infolists\Components\TextEntry;
|
||||
use Filament\Schemas\Components\Wizard\Step;
|
||||
@@ -42,9 +44,9 @@ final class GoogleSchema extends OAuthSchema
|
||||
], parent::getSetupSteps());
|
||||
}
|
||||
|
||||
public function getIcon(): string
|
||||
public function getIcon(): BackedEnum
|
||||
{
|
||||
return 'tabler-brand-google-f';
|
||||
return TablerIcon::BrandGoogleFilled;
|
||||
}
|
||||
|
||||
public function getHexColor(): string
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
namespace App\Extensions\OAuth\Schemas;
|
||||
|
||||
use App\Enums\TablerIcon;
|
||||
use BackedEnum;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Infolists\Components\TextEntry;
|
||||
use Filament\Schemas\Components\Wizard\Step;
|
||||
@@ -33,9 +35,9 @@ final class LinkedinSchema extends OAuthSchema
|
||||
], parent::getSetupSteps());
|
||||
}
|
||||
|
||||
public function getIcon(): string
|
||||
public function getIcon(): BackedEnum
|
||||
{
|
||||
return 'tabler-brand-linkedin-f';
|
||||
return TablerIcon::BrandLinkedinFilled;
|
||||
}
|
||||
|
||||
public function getHexColor(): string
|
||||
|
||||
@@ -2,13 +2,17 @@
|
||||
|
||||
namespace App\Extensions\OAuth\Schemas;
|
||||
|
||||
use App\Enums\TablerIcon;
|
||||
use App\Extensions\OAuth\OAuthSchemaInterface;
|
||||
use App\Models\User;
|
||||
use BackedEnum;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Toggle;
|
||||
use Filament\Schemas\Components\Component;
|
||||
use Filament\Schemas\Components\Utilities\Set;
|
||||
use Filament\Schemas\Components\Wizard\Step;
|
||||
use Illuminate\Support\Str;
|
||||
use Laravel\Socialite\Contracts\User as OAuthUser;
|
||||
|
||||
abstract class OAuthSchema implements OAuthSchemaInterface
|
||||
{
|
||||
@@ -59,8 +63,8 @@ abstract class OAuthSchema implements OAuthSchemaInterface
|
||||
->label(trans('admin/setting.oauth.create_missing_users'))
|
||||
->columnSpan(2)
|
||||
->inline(false)
|
||||
->onIcon('tabler-check')
|
||||
->offIcon('tabler-x')
|
||||
->onIcon(TablerIcon::Check)
|
||||
->offIcon(TablerIcon::X)
|
||||
->onColor('success')
|
||||
->offColor('danger')
|
||||
->formatStateUsing(fn ($state) => (bool) $state)
|
||||
@@ -70,8 +74,8 @@ abstract class OAuthSchema implements OAuthSchemaInterface
|
||||
->label(trans('admin/setting.oauth.link_missing_users'))
|
||||
->columnSpan(2)
|
||||
->inline(false)
|
||||
->onIcon('tabler-check')
|
||||
->offIcon('tabler-x')
|
||||
->onIcon(TablerIcon::Check)
|
||||
->offIcon(TablerIcon::X)
|
||||
->onColor('success')
|
||||
->offColor('danger')
|
||||
->formatStateUsing(fn ($state) => (bool) $state)
|
||||
@@ -104,7 +108,7 @@ abstract class OAuthSchema implements OAuthSchemaInterface
|
||||
return "OAUTH_{$id}_ENABLED";
|
||||
}
|
||||
|
||||
public function getIcon(): ?string
|
||||
public function getIcon(): null|string|BackedEnum
|
||||
{
|
||||
return null;
|
||||
}
|
||||
@@ -116,22 +120,20 @@ abstract class OAuthSchema implements OAuthSchemaInterface
|
||||
|
||||
public function isEnabled(): bool
|
||||
{
|
||||
$id = Str::upper($this->getId());
|
||||
|
||||
return env("OAUTH_{$id}_ENABLED", false);
|
||||
return (bool) env($this->getConfigKey(), false);
|
||||
}
|
||||
|
||||
public function shouldCreateMissingUsers(): bool
|
||||
public function shouldCreateMissingUser(OAuthUser $user): bool
|
||||
{
|
||||
$id = Str::upper($this->getId());
|
||||
|
||||
return env("OAUTH_{$id}_SHOULD_CREATE_MISSING_USERS", false);
|
||||
return (bool) env("OAUTH_{$id}_SHOULD_CREATE_MISSING_USERS", false);
|
||||
}
|
||||
|
||||
public function shouldLinkMissingUsers(): bool
|
||||
public function shouldLinkMissingUser(User $user, OAuthUser $oauthUser): bool
|
||||
{
|
||||
$id = Str::upper($this->getId());
|
||||
|
||||
return env("OAUTH_{$id}_SHOULD_LINK_MISSING_USERS", false);
|
||||
return (bool) env("OAUTH_{$id}_SHOULD_LINK_MISSING_USERS", false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
namespace App\Extensions\OAuth\Schemas;
|
||||
|
||||
use App\Enums\TablerIcon;
|
||||
use BackedEnum;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Infolists\Components\TextEntry;
|
||||
use Filament\Schemas\Components\Wizard\Step;
|
||||
@@ -33,9 +35,9 @@ final class SlackSchema extends OAuthSchema
|
||||
], parent::getSetupSteps());
|
||||
}
|
||||
|
||||
public function getIcon(): string
|
||||
public function getIcon(): BackedEnum
|
||||
{
|
||||
return 'tabler-brand-slack';
|
||||
return TablerIcon::BrandSlack;
|
||||
}
|
||||
|
||||
public function getHexColor(): string
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
namespace App\Extensions\OAuth\Schemas;
|
||||
|
||||
use App\Enums\TablerIcon;
|
||||
use BackedEnum;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Infolists\Components\TextEntry;
|
||||
use Filament\Schemas\Components\Wizard\Step;
|
||||
@@ -59,9 +61,9 @@ final class SteamSchema extends OAuthSchema
|
||||
], parent::getSetupSteps());
|
||||
}
|
||||
|
||||
public function getIcon(): string
|
||||
public function getIcon(): BackedEnum
|
||||
{
|
||||
return 'tabler-brand-steam-f';
|
||||
return TablerIcon::BrandSteamFilled;
|
||||
}
|
||||
|
||||
public function getHexColor(): string
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
namespace App\Extensions\OAuth\Schemas;
|
||||
|
||||
use App\Enums\TablerIcon;
|
||||
use BackedEnum;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Infolists\Components\TextEntry;
|
||||
use Filament\Schemas\Components\Wizard\Step;
|
||||
@@ -42,9 +44,9 @@ final class XSchema extends OAuthSchema
|
||||
], parent::getSetupSteps());
|
||||
}
|
||||
|
||||
public function getIcon(): string
|
||||
public function getIcon(): BackedEnum
|
||||
{
|
||||
return 'tabler-brand-x';
|
||||
return TablerIcon::BrandX;
|
||||
}
|
||||
|
||||
public function getHexColor(): string
|
||||
|
||||
@@ -44,4 +44,17 @@ class Fractal extends SpatieFractal
|
||||
|
||||
return parent::createData();
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the transformation to array.
|
||||
*
|
||||
* @return array<mixed>
|
||||
*
|
||||
* @throws InvalidTransformation
|
||||
* @throws NoTransformerSpecified
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
return parent::toArray() ?? [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ use App\Services\Backups\InitiateBackupService;
|
||||
|
||||
final class CreateBackupSchema extends TaskSchema
|
||||
{
|
||||
public function __construct(private InitiateBackupService $backupService) {}
|
||||
public function __construct(private InitiateBackupService $initiateService) {}
|
||||
|
||||
public function getId(): string
|
||||
{
|
||||
@@ -17,7 +17,7 @@ final class CreateBackupSchema extends TaskSchema
|
||||
|
||||
public function runTask(Task $task): void
|
||||
{
|
||||
$this->backupService->setIgnoredFiles(explode(PHP_EOL, $task->payload))->handle($task->server, null, true);
|
||||
$this->initiateService->setIgnoredFiles(explode(PHP_EOL, $task->payload))->handle($task->server, null, true);
|
||||
}
|
||||
|
||||
public function canCreate(Schedule $schedule): bool
|
||||
|
||||
@@ -2,12 +2,14 @@
|
||||
|
||||
namespace App\Filament\Admin\Pages;
|
||||
|
||||
use App\Enums\TablerIcon;
|
||||
use App\Services\Helpers\SoftwareVersionService;
|
||||
use BackedEnum;
|
||||
use Filament\Pages\Dashboard as BaseDashboard;
|
||||
|
||||
class Dashboard extends BaseDashboard
|
||||
{
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'tabler-layout-dashboard';
|
||||
protected static string|BackedEnum|null $navigationIcon = TablerIcon::LayoutDashboard;
|
||||
|
||||
private SoftwareVersionService $softwareVersionService;
|
||||
|
||||
@@ -23,7 +25,7 @@ class Dashboard extends BaseDashboard
|
||||
|
||||
public function getHeading(): string
|
||||
{
|
||||
return trans('admin/dashboard.heading');
|
||||
return trans('admin/dashboard.heading', ['app' => config('app.name')]);
|
||||
}
|
||||
|
||||
public function getSubheading(): string
|
||||
|
||||
@@ -2,11 +2,12 @@
|
||||
|
||||
namespace App\Filament\Admin\Pages;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use App\Enums\TablerIcon;
|
||||
use BackedEnum;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Pages\Page;
|
||||
use Filament\Support\Enums\IconSize;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Spatie\Health\Commands\RunHealthChecksCommand;
|
||||
use Spatie\Health\Enums\Status;
|
||||
@@ -14,7 +15,7 @@ use Spatie\Health\ResultStores\ResultStore;
|
||||
|
||||
class Health extends Page
|
||||
{
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'tabler-heart';
|
||||
protected static string|BackedEnum|null $navigationIcon = TablerIcon::Heart;
|
||||
|
||||
protected string $view = 'filament.pages.health';
|
||||
|
||||
@@ -40,16 +41,16 @@ class Health extends Page
|
||||
|
||||
public static function canAccess(): bool
|
||||
{
|
||||
return user()?->can('view health');
|
||||
return user()?->can('view health') ?? false;
|
||||
}
|
||||
|
||||
protected function getActions(): array
|
||||
{
|
||||
return [
|
||||
Action::make('refresh')
|
||||
->label(trans('admin/health.refresh'))
|
||||
->iconButton()->iconSize(IconSize::ExtraLarge)
|
||||
->icon('tabler-refresh')
|
||||
->hiddenLabel()
|
||||
->tooltip(trans('admin/health.refresh'))
|
||||
->icon(TablerIcon::Refresh)
|
||||
->action('refresh'),
|
||||
];
|
||||
}
|
||||
@@ -128,16 +129,16 @@ class Health extends Page
|
||||
return trans('admin/health.checks.failed', ['checks' => implode(', ', $failedNames)]);
|
||||
}
|
||||
|
||||
public static function getNavigationIcon(): string
|
||||
public static function getNavigationIcon(): BackedEnum
|
||||
{
|
||||
// @phpstan-ignore myCustomRules.forbiddenGlobalFunctions
|
||||
$results = app(ResultStore::class)->latestResults();
|
||||
|
||||
if ($results === null) {
|
||||
return 'tabler-heart-question';
|
||||
return TablerIcon::HeartQuestion;
|
||||
}
|
||||
|
||||
return $results->containsFailingCheck() ? 'tabler-heart-exclamation' : 'tabler-heart-check';
|
||||
return $results->containsFailingCheck() ? TablerIcon::HeartExclamation : TablerIcon::HeartCheck;
|
||||
}
|
||||
|
||||
public function backgroundColor(string $str): string
|
||||
@@ -162,14 +163,14 @@ class Health extends Page
|
||||
};
|
||||
}
|
||||
|
||||
public function icon(string $str): string
|
||||
public function icon(string $str): BackedEnum
|
||||
{
|
||||
return match ($str) {
|
||||
Status::ok()->value => 'tabler-circle-check',
|
||||
Status::warning()->value => 'tabler-exclamation-circle',
|
||||
Status::skipped()->value => 'tabler-circle-chevron-right',
|
||||
Status::failed()->value, Status::crashed()->value => 'tabler-circle-x',
|
||||
default => 'tabler-help-circle'
|
||||
Status::ok()->value => TablerIcon::CircleCheck,
|
||||
Status::warning()->value => TablerIcon::ExclamationCircle,
|
||||
Status::skipped()->value => TablerIcon::CircleChevronRight,
|
||||
Status::failed()->value, Status::crashed()->value => TablerIcon::CircleX,
|
||||
default => TablerIcon::HelpCircle
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,24 +2,32 @@
|
||||
|
||||
namespace App\Filament\Admin\Pages;
|
||||
|
||||
use App\Enums\TablerIcon;
|
||||
use Boquizo\FilamentLogViewer\Actions\DeleteAction;
|
||||
use Boquizo\FilamentLogViewer\Actions\DownloadAction;
|
||||
use Boquizo\FilamentLogViewer\Actions\ViewLogAction;
|
||||
use Boquizo\FilamentLogViewer\FilamentLogViewerPlugin;
|
||||
use Boquizo\FilamentLogViewer\Pages\ListLogs as BaseListLogs;
|
||||
use Boquizo\FilamentLogViewer\Tables\Columns\LevelColumn;
|
||||
use Boquizo\FilamentLogViewer\Tables\Columns\NameColumn;
|
||||
use Boquizo\FilamentLogViewer\UseCases\ParseDateUseCase;
|
||||
use Boquizo\FilamentLogViewer\Utils\Level;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\BulkAction;
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Support\Enums\IconSize;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Contracts\Support\Htmlable;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
||||
|
||||
class ListLogs extends BaseListLogs
|
||||
{
|
||||
protected string $view = 'filament.components.list-logs';
|
||||
|
||||
public function getHeading(): string|null|\Illuminate\Contracts\Support\Htmlable
|
||||
public function getHeading(): string|null|Htmlable
|
||||
{
|
||||
return trans('admin/log.navigation.panel_logs');
|
||||
}
|
||||
@@ -28,7 +36,13 @@ class ListLogs extends BaseListLogs
|
||||
{
|
||||
return parent::table($table)
|
||||
->emptyStateHeading(trans('admin/log.empty_table'))
|
||||
->emptyStateIcon('tabler-check')
|
||||
->emptyStateIcon(TablerIcon::Check)
|
||||
->toolbarActions([
|
||||
BulkActionGroup::make([
|
||||
self::exclude_downloadBulkAction(),
|
||||
self::exclude_deleteBulkAction(),
|
||||
]),
|
||||
])
|
||||
->columns([
|
||||
NameColumn::make('date'),
|
||||
LevelColumn::make(Level::ALL)
|
||||
@@ -46,22 +60,28 @@ class ListLogs extends BaseListLogs
|
||||
])
|
||||
->recordActions([
|
||||
ViewLogAction::make()
|
||||
->icon('tabler-file-description')->iconSize(IconSize::Large)->iconButton(),
|
||||
->icon(TablerIcon::FileDescription)->iconButton(),
|
||||
DownloadAction::make()
|
||||
->icon('tabler-file-download')->iconSize(IconSize::Large)->iconButton(),
|
||||
->tooltip(function (array $record): string {
|
||||
return trans('filament-log-viewer::log.table.actions.download.label', ['log' => ParseDateUseCase::execute((string) ($record['date'] ?? ''))]);
|
||||
})
|
||||
->icon(TablerIcon::FileDownload)->iconButton(),
|
||||
Action::make('uploadLogs')
|
||||
->hiddenLabel()
|
||||
->icon('tabler-world-upload')->iconSize(IconSize::Large)->iconButton()
|
||||
->tooltip(trans('admin/log.actions.upload_tooltip', ['url' => 'logs.pelican.dev']))
|
||||
->icon(TablerIcon::WorldUpload)
|
||||
->requiresConfirmation()
|
||||
->modalHeading(trans('admin/log.actions.upload_logs'))
|
||||
->modalDescription(fn ($record) => trans('admin/log.actions.upload_logs_description', ['file' => $record['date'], 'url' => 'https://logs.pelican.dev']))
|
||||
->action(function ($record) {
|
||||
$logPath = storage_path('logs/' . $record['date']);
|
||||
->action(function (array $record): void {
|
||||
$prefix = config('filament-log-viewer.pattern.prefix', 'laravel-');
|
||||
$extension = config('filament-log-viewer.pattern.extension', '.log');
|
||||
$logPath = storage_path('logs/' . $prefix . (string) ($record['date'] ?? '') . $extension);
|
||||
|
||||
if (!file_exists($logPath)) {
|
||||
Notification::make()
|
||||
->title(trans('admin/log.actions.log_not_found'))
|
||||
->body(trans('admin/log.actions.log_not_found_description', ['filename' => $record['date']]))
|
||||
->body(trans('admin/log.actions.log_not_found_description', ['filename' => (string) ($record['date'] ?? '')]))
|
||||
->danger()
|
||||
->send();
|
||||
|
||||
@@ -69,22 +89,16 @@ class ListLogs extends BaseListLogs
|
||||
}
|
||||
|
||||
$lines = file($logPath, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
||||
$totalLines = count($lines);
|
||||
$uploadLines = $totalLines <= 1000 ? $lines : array_slice($lines, -1000);
|
||||
$content = implode("\n", $uploadLines);
|
||||
$totalLines = is_array($lines) ? count($lines) : 0;
|
||||
$uploadLines = $totalLines <= 1000 ? ($lines ?: []) : array_slice($lines ?: [], -1000);
|
||||
$content = implode("\n", (array) $uploadLines);
|
||||
|
||||
$logUrl = 'https://logs.pelican.dev';
|
||||
try {
|
||||
$response = Http::timeout(10)->asMultipart()->post($logUrl, [
|
||||
[
|
||||
'name' => 'c',
|
||||
'contents' => $content,
|
||||
],
|
||||
[
|
||||
'name' => 'e',
|
||||
'contents' => '14d',
|
||||
],
|
||||
]);
|
||||
$response = Http::timeout(10)
|
||||
->asMultipart()
|
||||
->attach('c', $content)
|
||||
->attach('e', '14d')
|
||||
->post('https://logs.pelican.dev');
|
||||
|
||||
if ($response->failed()) {
|
||||
Notification::make()
|
||||
@@ -97,14 +111,14 @@ class ListLogs extends BaseListLogs
|
||||
}
|
||||
|
||||
$data = $response->json();
|
||||
$url = $data['url'];
|
||||
$url = (string) ($data['url'] ?? '');
|
||||
|
||||
Notification::make()
|
||||
->title(trans('admin/log.actions.log_upload'))
|
||||
->body("{$url}")
|
||||
->success()
|
||||
->actions([
|
||||
Action::make('viewLogs')
|
||||
Action::make('exclude_viewLogs')
|
||||
->label(trans('admin/log.actions.view_logs'))
|
||||
->url($url)
|
||||
->openUrlInNewTab(true),
|
||||
@@ -123,7 +137,53 @@ class ListLogs extends BaseListLogs
|
||||
}
|
||||
}),
|
||||
DeleteAction::make()
|
||||
->iconSize(IconSize::Medium)->iconButton(),
|
||||
->icon(TablerIcon::Trash)->iconButton(),
|
||||
]);
|
||||
}
|
||||
|
||||
private static function exclude_downloadBulkAction(): BulkAction
|
||||
{
|
||||
return BulkAction::make('exclude_download')
|
||||
->label(trans('filament-log-viewer::log.table.actions.download.bulk.label'))
|
||||
->icon(TablerIcon::FileZip)
|
||||
->color('success')
|
||||
->requiresConfirmation()
|
||||
->modalHeading(trans('filament-log-viewer::log.table.actions.download.bulk.label'))
|
||||
->failureNotificationTitle(trans('filament-log-viewer::log.table.actions.download.bulk.error'))
|
||||
->action(self::exclude_downloadBulkAction_getAction(...));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Collection<array-key, array<string, mixed>> $records
|
||||
*/
|
||||
private static function exclude_downloadBulkAction_getAction(
|
||||
BulkAction $action,
|
||||
Collection $records,
|
||||
): ?BinaryFileResponse {
|
||||
try {
|
||||
$logs = $records->pluck('date')->all();
|
||||
|
||||
return FilamentLogViewerPlugin::make()->downloadLogs($logs);
|
||||
} catch (\Exception) {
|
||||
$action->failure();
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static function exclude_deleteBulkAction(): DeleteBulkAction
|
||||
{
|
||||
return DeleteBulkAction::make('exclude_delete')
|
||||
->modalHeading(trans('filament-log-viewer::log.table.actions.delete.bulk.label'))
|
||||
->action(function (DeleteBulkAction $action) {
|
||||
$action->process(function (Collection $records): void {
|
||||
/** @var array<string, mixed> $record */
|
||||
foreach ($records as $record) {
|
||||
FilamentLogViewerPlugin::make()->deleteLog((string) ($record['date'] ?? ''));
|
||||
}
|
||||
});
|
||||
|
||||
$action->success();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,15 +2,18 @@
|
||||
|
||||
namespace App\Filament\Admin\Pages;
|
||||
|
||||
use App\Enums\TablerIcon;
|
||||
use App\Extensions\Avatar\AvatarService;
|
||||
use App\Extensions\Captcha\CaptchaService;
|
||||
use App\Extensions\OAuth\OAuthService;
|
||||
use App\Models\Backup;
|
||||
use App\Notifications\MailTested;
|
||||
use App\Traits\EnvironmentWriterTrait;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use App\Traits\Filament\CanCustomizeTabs;
|
||||
use BackedEnum;
|
||||
use BladeUI\Icons\Exceptions\SvgNotFound;
|
||||
use BladeUI\Icons\Factory as IconFactory;
|
||||
use Exception;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\ActionGroup;
|
||||
@@ -27,16 +30,17 @@ use Filament\Pages\Concerns\InteractsWithHeaderActions;
|
||||
use Filament\Pages\Page;
|
||||
use Filament\Schemas\Components\Actions;
|
||||
use Filament\Schemas\Components\Component;
|
||||
use Filament\Schemas\Components\Fieldset;
|
||||
use Filament\Schemas\Components\Group;
|
||||
use Filament\Schemas\Components\Section;
|
||||
use Filament\Schemas\Components\StateCasts\BooleanStateCast;
|
||||
use Filament\Schemas\Components\Tabs;
|
||||
use Filament\Schemas\Components\Tabs\Tab;
|
||||
use Filament\Schemas\Components\Text;
|
||||
use Filament\Schemas\Components\Utilities\Get;
|
||||
use Filament\Schemas\Components\Utilities\Set;
|
||||
use Filament\Schemas\Contracts\HasSchemas;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Support\Enums\IconSize;
|
||||
use Filament\Support\Enums\Width;
|
||||
use Illuminate\Http\Client\Factory;
|
||||
use Illuminate\Support\Arr;
|
||||
@@ -53,10 +57,11 @@ class Settings extends Page implements HasSchemas
|
||||
CanCustomizeHeaderActions::getHeaderActions insteadof InteractsWithHeaderActions;
|
||||
}
|
||||
use CanCustomizeHeaderWidgets;
|
||||
use CanCustomizeTabs;
|
||||
use EnvironmentWriterTrait;
|
||||
use InteractsWithForms;
|
||||
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'tabler-settings';
|
||||
protected static string|BackedEnum|null $navigationIcon = TablerIcon::Settings;
|
||||
|
||||
protected string $view = 'filament.pages.settings';
|
||||
|
||||
@@ -66,6 +71,8 @@ class Settings extends Page implements HasSchemas
|
||||
|
||||
protected CaptchaService $captchaService;
|
||||
|
||||
protected IconFactory $iconFactory;
|
||||
|
||||
/** @var array<mixed>|null */
|
||||
public ?array $data = [];
|
||||
|
||||
@@ -74,16 +81,17 @@ class Settings extends Page implements HasSchemas
|
||||
$this->form->fill();
|
||||
}
|
||||
|
||||
public function boot(OAuthService $oauthService, AvatarService $avatarService, CaptchaService $captchaService): void
|
||||
public function boot(OAuthService $oauthService, AvatarService $avatarService, CaptchaService $captchaService, IconFactory $iconFactory): void
|
||||
{
|
||||
$this->oauthService = $oauthService;
|
||||
$this->avatarService = $avatarService;
|
||||
$this->captchaService = $captchaService;
|
||||
$this->iconFactory = $iconFactory;
|
||||
}
|
||||
|
||||
public static function canAccess(): bool
|
||||
{
|
||||
return user()?->can('view settings');
|
||||
return user()?->can('view settings') ?? false;
|
||||
}
|
||||
|
||||
public function getTitle(): string
|
||||
@@ -96,11 +104,7 @@ class Settings extends Page implements HasSchemas
|
||||
return trans('admin/setting.title');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<Component>
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
/** @return array<Component> */
|
||||
protected function getFormSchema(): array
|
||||
{
|
||||
return [
|
||||
@@ -108,34 +112,44 @@ class Settings extends Page implements HasSchemas
|
||||
->columns()
|
||||
->persistTabInQueryString()
|
||||
->disabled(fn () => !user()?->can('update settings'))
|
||||
->tabs([
|
||||
Tab::make('general')
|
||||
->label(trans('admin/setting.navigation.general'))
|
||||
->icon('tabler-home')
|
||||
->schema($this->generalSettings()),
|
||||
Tab::make('captcha')
|
||||
->label(trans('admin/setting.navigation.captcha'))
|
||||
->icon('tabler-shield')
|
||||
->schema($this->captchaSettings())
|
||||
->columns(1),
|
||||
Tab::make('mail')
|
||||
->label(trans('admin/setting.navigation.mail'))
|
||||
->icon('tabler-mail')
|
||||
->schema($this->mailSettings()),
|
||||
Tab::make('backup')
|
||||
->label(trans('admin/setting.navigation.backup'))
|
||||
->icon('tabler-box')
|
||||
->schema($this->backupSettings()),
|
||||
Tab::make('oauth')
|
||||
->label(trans('admin/setting.navigation.oauth'))
|
||||
->icon('tabler-brand-oauth')
|
||||
->schema($this->oauthSettings())
|
||||
->columns(1),
|
||||
Tab::make('misc')
|
||||
->label(trans('admin/setting.navigation.misc'))
|
||||
->icon('tabler-tool')
|
||||
->schema($this->miscSettings()),
|
||||
]),
|
||||
->tabs($this->getTabs()),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Tab[]
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function getDefaultTabs(): array
|
||||
{
|
||||
return [
|
||||
Tab::make('general')
|
||||
->label(trans('admin/setting.navigation.general'))
|
||||
->icon(TablerIcon::Home)
|
||||
->schema($this->generalSettings()),
|
||||
Tab::make('captcha')
|
||||
->label(trans('admin/setting.navigation.captcha'))
|
||||
->icon(TablerIcon::Shield)
|
||||
->schema($this->captchaSettings())
|
||||
->columns(1),
|
||||
Tab::make('mail')
|
||||
->label(trans('admin/setting.navigation.mail'))
|
||||
->icon(TablerIcon::Mail)
|
||||
->schema($this->mailSettings()),
|
||||
Tab::make('backup')
|
||||
->label(trans('admin/setting.navigation.backup'))
|
||||
->icon(TablerIcon::Box)
|
||||
->schema($this->backupSettings()),
|
||||
Tab::make('oauth')
|
||||
->label(trans('admin/setting.navigation.oauth'))
|
||||
->icon(TablerIcon::BrandOauth)
|
||||
->schema($this->oauthSettings())
|
||||
->columns(1),
|
||||
Tab::make('misc')
|
||||
->label(trans('admin/setting.navigation.misc'))
|
||||
->icon(TablerIcon::Tool)
|
||||
->schema($this->miscSettings()),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -154,12 +168,12 @@ class Settings extends Page implements HasSchemas
|
||||
->schema([
|
||||
TextInput::make('APP_LOGO')
|
||||
->label(trans('admin/setting.general.app_logo'))
|
||||
->hintIcon('tabler-question-mark', trans('admin/setting.general.app_logo_help'))
|
||||
->hintIcon(TablerIcon::QuestionMark, trans('admin/setting.general.app_logo_help'))
|
||||
->default(env('APP_LOGO'))
|
||||
->placeholder('/pelican.svg'),
|
||||
TextInput::make('APP_FAVICON')
|
||||
->label(trans('admin/setting.general.app_favicon'))
|
||||
->hintIcon('tabler-question-mark', trans('admin/setting.general.app_favicon_help'))
|
||||
->hintIcon(TablerIcon::QuestionMark, trans('admin/setting.general.app_favicon_help'))
|
||||
->required()
|
||||
->default(env('APP_FAVICON', '/pelican.ico'))
|
||||
->placeholder('/pelican.ico'),
|
||||
@@ -170,8 +184,8 @@ class Settings extends Page implements HasSchemas
|
||||
Toggle::make('APP_DEBUG')
|
||||
->label(trans('admin/setting.general.debug_mode'))
|
||||
->inline(false)
|
||||
->onIcon('tabler-check')
|
||||
->offIcon('tabler-x')
|
||||
->onIcon(TablerIcon::Check)
|
||||
->offIcon(TablerIcon::X)
|
||||
->onColor('success')
|
||||
->offColor('danger')
|
||||
->stateCast(new BooleanStateCast(false))
|
||||
@@ -188,8 +202,8 @@ class Settings extends Page implements HasSchemas
|
||||
Toggle::make('FILAMENT_UPLOADABLE_AVATARS')
|
||||
->label(trans('admin/setting.general.uploadable_avatars'))
|
||||
->inline(false)
|
||||
->onIcon('tabler-check')
|
||||
->offIcon('tabler-x')
|
||||
->onIcon(TablerIcon::Check)
|
||||
->offIcon(TablerIcon::X)
|
||||
->onColor('success')
|
||||
->offColor('danger')
|
||||
->stateCast(new BooleanStateCast(false))
|
||||
@@ -236,16 +250,16 @@ class Settings extends Page implements HasSchemas
|
||||
->placeholder(trans('admin/setting.general.trusted_proxies_help'))
|
||||
->default(env('TRUSTED_PROXIES', implode(',', Arr::wrap(config('trustedproxy.proxies')))))
|
||||
->hintActions([
|
||||
Action::make('clear')
|
||||
Action::make('hint_clear')
|
||||
->label(trans('admin/setting.general.clear'))
|
||||
->color('danger')
|
||||
->icon('tabler-trash')
|
||||
->icon(TablerIcon::Trash)
|
||||
->requiresConfirmation()
|
||||
->authorize(fn () => user()?->can('update settings'))
|
||||
->action(fn (Set $set) => $set('TRUSTED_PROXIES', [])),
|
||||
Action::make('cloudflare')
|
||||
Action::make('hint_cloudflare')
|
||||
->label(trans('admin/setting.general.set_to_cf'))
|
||||
->icon('tabler-brand-cloudflare')
|
||||
->icon(TablerIcon::BrandCloudflare)
|
||||
->authorize(fn () => user()?->can('update settings'))
|
||||
->action(function (Factory $client, Set $set) {
|
||||
$ips = collect();
|
||||
@@ -256,7 +270,7 @@ class Settings extends Page implements HasSchemas
|
||||
->connectTimeout(3)
|
||||
->get('https://api.cloudflare.com/client/v4/ips');
|
||||
|
||||
if ($response->getStatusCode() === 200) {
|
||||
if ($response->status() === 200) {
|
||||
$result = $response->json('result');
|
||||
foreach (['ipv4_cidrs', 'ipv6_cidrs'] as $value) {
|
||||
$ips->push(...data_get($result, $value));
|
||||
@@ -287,7 +301,7 @@ class Settings extends Page implements HasSchemas
|
||||
|
||||
$formFields[] = Section::make($schema->getName())
|
||||
->columns(5)
|
||||
->icon($schema->getIcon() ?? 'tabler-shield')
|
||||
->icon($schema->getIcon() ?? TablerIcon::Shield)
|
||||
->collapsed(fn () => !$schema->isEnabled())
|
||||
->collapsible()
|
||||
->schema([
|
||||
@@ -341,9 +355,9 @@ class Settings extends Page implements HasSchemas
|
||||
->live()
|
||||
->default(env('MAIL_MAILER', config('mail.default')))
|
||||
->hintAction(
|
||||
Action::make('test')
|
||||
Action::make('hint_test')
|
||||
->label(trans('admin/setting.mail.test_mail'))
|
||||
->icon('tabler-send')
|
||||
->icon(TablerIcon::Send)
|
||||
->hidden(fn (Get $get) => $get('MAIL_MAILER') === 'log')
|
||||
->authorize(fn () => user()?->can('update settings'))
|
||||
->action(function (Get $get) {
|
||||
@@ -477,16 +491,6 @@ class Settings extends Page implements HasSchemas
|
||||
private function backupSettings(): array
|
||||
{
|
||||
return [
|
||||
ToggleButtons::make('APP_BACKUP_DRIVER')
|
||||
->label(trans('admin/setting.backup.backup_driver'))
|
||||
->columnSpanFull()
|
||||
->inline()
|
||||
->options([
|
||||
Backup::ADAPTER_DAEMON => 'Wings',
|
||||
Backup::ADAPTER_AWS_S3 => 'S3',
|
||||
])
|
||||
->live()
|
||||
->default(env('APP_BACKUP_DRIVER', config('backups.default'))),
|
||||
Section::make(trans('admin/setting.backup.throttle'))
|
||||
->description(trans('admin/setting.backup.throttle_help'))
|
||||
->columns()
|
||||
@@ -506,41 +510,6 @@ class Settings extends Page implements HasSchemas
|
||||
->suffix('Seconds')
|
||||
->default(config('backups.throttles.period')),
|
||||
]),
|
||||
Section::make(trans('admin/setting.backup.s3.s3_title'))
|
||||
->columns()
|
||||
->visible(fn (Get $get) => $get('APP_BACKUP_DRIVER') === Backup::ADAPTER_AWS_S3)
|
||||
->schema([
|
||||
TextInput::make('AWS_DEFAULT_REGION')
|
||||
->label(trans('admin/setting.backup.s3.default_region'))
|
||||
->required()
|
||||
->default(config('backups.disks.s3.region')),
|
||||
TextInput::make('AWS_ACCESS_KEY_ID')
|
||||
->label(trans('admin/setting.backup.s3.access_key'))
|
||||
->required()
|
||||
->default(config('backups.disks.s3.key')),
|
||||
TextInput::make('AWS_SECRET_ACCESS_KEY')
|
||||
->label(trans('admin/setting.backup.s3.secret_key'))
|
||||
->required()
|
||||
->default(config('backups.disks.s3.secret')),
|
||||
TextInput::make('AWS_BACKUPS_BUCKET')
|
||||
->label(trans('admin/setting.backup.s3.bucket'))
|
||||
->required()
|
||||
->default(config('backups.disks.s3.bucket')),
|
||||
TextInput::make('AWS_ENDPOINT')
|
||||
->label(trans('admin/setting.backup.s3.endpoint'))
|
||||
->required()
|
||||
->default(config('backups.disks.s3.endpoint')),
|
||||
Toggle::make('AWS_USE_PATH_STYLE_ENDPOINT')
|
||||
->label(trans('admin/setting.backup.s3.use_path_style_endpoint'))
|
||||
->inline(false)
|
||||
->onIcon('tabler-check')
|
||||
->offIcon('tabler-x')
|
||||
->onColor('success')
|
||||
->offColor('danger')
|
||||
->live()
|
||||
->stateCast(new BooleanStateCast(false))
|
||||
->default(env('AWS_USE_PATH_STYLE_ENDPOINT', config('backups.disks.s3.use_path_style_endpoint'))),
|
||||
]),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -555,26 +524,34 @@ class Settings extends Page implements HasSchemas
|
||||
|
||||
$oauthSchemas = $this->oauthService->getAll();
|
||||
foreach ($oauthSchemas as $schema) {
|
||||
$id = Str::upper($schema->getId());
|
||||
$key = $schema->getConfigKey();
|
||||
|
||||
$icon = $schema->getIcon();
|
||||
if (is_string($icon)) {
|
||||
try {
|
||||
$this->iconFactory->svg($icon);
|
||||
} catch (SvgNotFound) {
|
||||
$icon = null;
|
||||
}
|
||||
}
|
||||
|
||||
$formFields[] = Section::make($schema->getName())
|
||||
->columns(5)
|
||||
->icon($schema->getIcon() ?? 'tabler-brand-oauth')
|
||||
->collapsed(fn () => !env($key, false))
|
||||
->icon($icon ?? TablerIcon::BrandOauth)
|
||||
->collapsed(fn () => !$schema->isEnabled())
|
||||
->collapsible()
|
||||
->schema([
|
||||
Hidden::make($key)
|
||||
->live()
|
||||
->default(env($key)),
|
||||
->default($schema->isEnabled()),
|
||||
Actions::make([
|
||||
Action::make("disable_oauth_$id")
|
||||
Action::make('disable_oauth_' . $schema->getId())
|
||||
->visible(fn (Get $get) => $get($key))
|
||||
->disabled(fn () => !user()?->can('update settings'))
|
||||
->label(trans('admin/setting.oauth.disable'))
|
||||
->color('danger')
|
||||
->action(fn (Set $set) => $set($key, false)),
|
||||
Action::make("enable_oauth_$id")
|
||||
Action::make('enable_oauth_' . $schema->getId())
|
||||
->visible(fn (Get $get) => !$get($key))
|
||||
->disabled(fn () => !user()?->can('update settings'))
|
||||
->label(trans('admin/setting.oauth.enable'))
|
||||
@@ -619,8 +596,8 @@ class Settings extends Page implements HasSchemas
|
||||
->schema([
|
||||
Toggle::make('PANEL_CLIENT_ALLOCATIONS_ENABLED')
|
||||
->label(trans('admin/setting.misc.auto_allocation.question'))
|
||||
->onIcon('tabler-check')
|
||||
->offIcon('tabler-x')
|
||||
->onIcon(TablerIcon::Check)
|
||||
->offIcon(TablerIcon::X)
|
||||
->onColor('success')
|
||||
->offColor('danger')
|
||||
->live()
|
||||
@@ -630,8 +607,8 @@ class Settings extends Page implements HasSchemas
|
||||
Toggle::make('PANEL_CLIENT_ALLOCATIONS_CREATE_NEW')
|
||||
->label(trans('admin/setting.misc.auto_allocation.create_new'))
|
||||
->helperText(trans('admin/setting.misc.auto_allocation.create_new_help'))
|
||||
->onIcon('tabler-check')
|
||||
->offIcon('tabler-x')
|
||||
->onIcon(TablerIcon::Check)
|
||||
->offIcon(TablerIcon::X)
|
||||
->onColor('success')
|
||||
->offColor('danger')
|
||||
->live()
|
||||
@@ -662,10 +639,40 @@ class Settings extends Page implements HasSchemas
|
||||
->collapsible()
|
||||
->collapsed()
|
||||
->schema([
|
||||
Toggle::make('PANEL_SEND_ACCOUNT_CREATED_NOTIFICATION')
|
||||
->label(trans('admin/setting.misc.mail_notifications.account_created'))
|
||||
->onIcon(TablerIcon::Check)
|
||||
->offIcon(TablerIcon::X)
|
||||
->onColor('success')
|
||||
->offColor('danger')
|
||||
->live()
|
||||
->formatStateUsing(fn ($state): bool => (bool) $state)
|
||||
->afterStateUpdated(fn ($state, Set $set) => $set('PANEL_SEND_ACCOUNT_CREATED_NOTIFICATION', (bool) $state))
|
||||
->default(env('PANEL_SEND_ACCOUNT_CREATED_NOTIFICATION', config('panel.email.send_account_created_notification'))),
|
||||
Toggle::make('PANEL_SEND_ADDED_TO_SERVER_NOTIFICATION')
|
||||
->label(trans('admin/setting.misc.mail_notifications.added_to_server'))
|
||||
->onIcon(TablerIcon::Check)
|
||||
->offIcon(TablerIcon::X)
|
||||
->onColor('success')
|
||||
->offColor('danger')
|
||||
->live()
|
||||
->formatStateUsing(fn ($state): bool => (bool) $state)
|
||||
->afterStateUpdated(fn ($state, Set $set) => $set('PANEL_SEND_ADDED_TO_SERVER_NOTIFICATION', (bool) $state))
|
||||
->default(env('PANEL_SEND_ADDED_TO_SERVER_NOTIFICATION', config('panel.email.send_added_to_server_notification'))),
|
||||
Toggle::make('PANEL_SEND_REMOVED_FROM_SERVER_NOTIFICATION')
|
||||
->label(trans('admin/setting.misc.mail_notifications.removed_from_server'))
|
||||
->onIcon(TablerIcon::Check)
|
||||
->offIcon(TablerIcon::X)
|
||||
->onColor('success')
|
||||
->offColor('danger')
|
||||
->live()
|
||||
->formatStateUsing(fn ($state): bool => (bool) $state)
|
||||
->afterStateUpdated(fn ($state, Set $set) => $set('PANEL_SEND_REMOVED_FROM_SERVER_NOTIFICATION', (bool) $state))
|
||||
->default(env('PANEL_SEND_REMOVED_FROM_SERVER_NOTIFICATION', config('panel.email.send_removed_from_server_notification'))),
|
||||
Toggle::make('PANEL_SEND_INSTALL_NOTIFICATION')
|
||||
->label(trans('admin/setting.misc.mail_notifications.server_installed'))
|
||||
->onIcon('tabler-check')
|
||||
->offIcon('tabler-x')
|
||||
->onIcon(TablerIcon::Check)
|
||||
->offIcon(TablerIcon::X)
|
||||
->onColor('success')
|
||||
->offColor('danger')
|
||||
->live()
|
||||
@@ -674,14 +681,24 @@ class Settings extends Page implements HasSchemas
|
||||
->default(env('PANEL_SEND_INSTALL_NOTIFICATION', config('panel.email.send_install_notification'))),
|
||||
Toggle::make('PANEL_SEND_REINSTALL_NOTIFICATION')
|
||||
->label(trans('admin/setting.misc.mail_notifications.server_reinstalled'))
|
||||
->onIcon('tabler-check')
|
||||
->offIcon('tabler-x')
|
||||
->onIcon(TablerIcon::Check)
|
||||
->offIcon(TablerIcon::X)
|
||||
->onColor('success')
|
||||
->offColor('danger')
|
||||
->live()
|
||||
->formatStateUsing(fn ($state): bool => (bool) $state)
|
||||
->afterStateUpdated(fn ($state, Set $set) => $set('PANEL_SEND_REINSTALL_NOTIFICATION', (bool) $state))
|
||||
->default(env('PANEL_SEND_REINSTALL_NOTIFICATION', config('panel.email.send_reinstall_notification'))),
|
||||
Toggle::make('PANEL_SEND_BACKUP_COMPLETED_NOTIFICATION')
|
||||
->label(trans('admin/setting.misc.mail_notifications.backup_completed'))
|
||||
->onIcon(TablerIcon::Check)
|
||||
->offIcon(TablerIcon::X)
|
||||
->onColor('success')
|
||||
->offColor('danger')
|
||||
->live()
|
||||
->formatStateUsing(fn ($state): bool => (bool) $state)
|
||||
->afterStateUpdated(fn ($state, Set $set) => $set('PANEL_SEND_BACKUP_COMPLETED_NOTIFICATION', (bool) $state))
|
||||
->default(env('PANEL_SEND_BACKUP_COMPLETED_NOTIFICATION', config('panel.email.send_backup_completed_notification'))),
|
||||
]),
|
||||
Section::make(trans('admin/setting.misc.connections.title'))
|
||||
->description(trans('admin/setting.misc.connections.helper'))
|
||||
@@ -723,8 +740,8 @@ class Settings extends Page implements HasSchemas
|
||||
Toggle::make('APP_ACTIVITY_HIDE_ADMIN')
|
||||
->label(trans('admin/setting.misc.activity_log.log_admin'))
|
||||
->inline(false)
|
||||
->onIcon('tabler-check')
|
||||
->offIcon('tabler-x')
|
||||
->onIcon(TablerIcon::Check)
|
||||
->offIcon(TablerIcon::X)
|
||||
->onColor('success')
|
||||
->offColor('danger')
|
||||
->live()
|
||||
@@ -733,24 +750,172 @@ class Settings extends Page implements HasSchemas
|
||||
]),
|
||||
Section::make(trans('admin/setting.misc.api.title'))
|
||||
->description(trans('admin/setting.misc.api.helper'))
|
||||
->columns()
|
||||
->columns(1)
|
||||
->collapsible()
|
||||
->collapsed()
|
||||
->schema([
|
||||
TextInput::make('APP_API_CLIENT_RATELIMIT')
|
||||
->label(trans('admin/setting.misc.api.client_rate'))
|
||||
->required()
|
||||
->numeric()
|
||||
->minValue(1)
|
||||
->suffix(trans('admin/setting.misc.api.rpm'))
|
||||
->default(env('APP_API_CLIENT_RATELIMIT', config('http.rate_limit.client'))),
|
||||
TextInput::make('APP_API_APPLICATION_RATELIMIT')
|
||||
->label(trans('admin/setting.misc.api.app_rate'))
|
||||
->required()
|
||||
->numeric()
|
||||
->minValue(1)
|
||||
->suffix(trans('admin/setting.misc.api.rpm'))
|
||||
->default(env('APP_API_APPLICATION_RATELIMIT', config('http.rate_limit.application'))),
|
||||
Fieldset::make()
|
||||
->label(trans('admin/setting.misc.api.client_title'))
|
||||
->schema([
|
||||
TextInput::make('APP_API_CLIENT_RATELIMIT')
|
||||
->label(trans('admin/setting.misc.api.requests_per_period'))
|
||||
->required()
|
||||
->numeric()
|
||||
->minValue(1)
|
||||
->suffix(trans('admin/setting.misc.api.requests'))->default(env('APP_API_CLIENT_RATELIMIT', config('http.rate_limit.client'))),
|
||||
|
||||
TextInput::make('APP_API_CLIENT_RATELIMIT_PERIOD')
|
||||
->label(trans('admin/setting.misc.api.period_length'))
|
||||
->required()
|
||||
->numeric()
|
||||
->minValue(1)
|
||||
->suffix(trans('admin/setting.misc.api.minutes'))->default(env('APP_API_CLIENT_RATELIMIT_PERIOD', config('http.rate_limit.client_period'))),
|
||||
Text::make(trans('admin/setting.misc.api.client_helper')),
|
||||
]),
|
||||
Fieldset::make()
|
||||
->label(trans('admin/setting.misc.api.application_title'))
|
||||
->schema([
|
||||
TextInput::make('APP_API_APPLICATION_RATELIMIT')
|
||||
->label(trans('admin/setting.misc.api.requests_per_period'))
|
||||
->required()
|
||||
->numeric()
|
||||
->minValue(1)
|
||||
->suffix(trans('admin/setting.misc.api.requests'))->default(env('APP_API_APPLICATION_RATELIMIT', config('http.rate_limit.application'))),
|
||||
|
||||
TextInput::make('APP_API_APPLICATION_RATELIMIT_PERIOD')
|
||||
->label(trans('admin/setting.misc.api.period_length'))
|
||||
->required()
|
||||
->numeric()
|
||||
->minValue(1)
|
||||
->suffix(trans('admin/setting.misc.api.minutes'))->default(env('APP_API_APPLICATION_RATELIMIT_PERIOD', config('http.rate_limit.application_period'))),
|
||||
Text::make(trans('admin/setting.misc.api.application_helper')),
|
||||
]),
|
||||
Fieldset::make()
|
||||
->label(trans('admin/setting.misc.api.password_reset_title'))
|
||||
->schema([
|
||||
TextInput::make('APP_API_PASSWORD_RESET_RATELIMIT')
|
||||
->label(trans('admin/setting.misc.api.requests_per_period'))
|
||||
->required()
|
||||
->numeric()
|
||||
->minValue(1)
|
||||
->suffix(trans('admin/setting.misc.api.requests'))->default(env('APP_API_PASSWORD_RESET_RATELIMIT', config('http.rate_limit.password_reset'))),
|
||||
|
||||
TextInput::make('APP_API_PASSWORD_RESET_RATELIMIT_PERIOD')
|
||||
->label(trans('admin/setting.misc.api.period_length'))
|
||||
->required()
|
||||
->numeric()
|
||||
->minValue(1)
|
||||
->suffix(trans('admin/setting.misc.api.minutes'))->default(env('APP_API_PASSWORD_RESET_RATELIMIT_PERIOD', config('http.rate_limit.password_reset_period'))),
|
||||
Text::make(trans('admin/setting.misc.api.password_reset_helper')),
|
||||
]),
|
||||
Fieldset::make()
|
||||
->label(trans('admin/setting.misc.api.websocket_title'))
|
||||
->schema([
|
||||
TextInput::make('APP_API_WEBSOCKET_RATELIMIT')
|
||||
->label(trans('admin/setting.misc.api.requests_per_period'))
|
||||
->required()
|
||||
->numeric()
|
||||
->minValue(1)
|
||||
->suffix(trans('admin/setting.misc.api.requests'))->default(env('APP_API_WEBSOCKET_RATELIMIT', config('http.rate_limit.websocket'))),
|
||||
|
||||
TextInput::make('APP_API_WEBSOCKET_RATELIMIT_PERIOD')
|
||||
->label(trans('admin/setting.misc.api.period_length'))
|
||||
->required()
|
||||
->numeric()
|
||||
->minValue(1)
|
||||
->suffix(trans('admin/setting.misc.api.minutes'))->default(env('APP_API_WEBSOCKET_RATELIMIT_PERIOD', config('http.rate_limit.websocket_period'))),
|
||||
Text::make(trans('admin/setting.misc.api.websocket_helper')),
|
||||
]),
|
||||
Fieldset::make()
|
||||
->label(trans('admin/setting.misc.api.backup_restore_title'))
|
||||
->schema([
|
||||
TextInput::make('APP_API_BACKUP_RESTORE_RATELIMIT')
|
||||
->label(trans('admin/setting.misc.api.requests_per_period'))
|
||||
->required()
|
||||
->numeric()
|
||||
->minValue(1)
|
||||
->suffix(trans('admin/setting.misc.api.requests'))->default(env('APP_API_BACKUP_RESTORE_RATELIMIT', config('http.rate_limit.backup_restore'))),
|
||||
|
||||
TextInput::make('APP_API_BACKUP_RESTORE_RATELIMIT_PERIOD')
|
||||
->label(trans('admin/setting.misc.api.period_length'))
|
||||
->required()
|
||||
->numeric()
|
||||
->minValue(1)
|
||||
->suffix(trans('admin/setting.misc.api.minutes'))->default(env('APP_API_BACKUP_RESTORE_RATELIMIT_PERIOD', config('http.rate_limit.backup_restore_period'))),
|
||||
Text::make(trans('admin/setting.misc.api.backup_restore_helper')),
|
||||
]),
|
||||
Fieldset::make()
|
||||
->label(trans('admin/setting.misc.api.database_create_title'))
|
||||
->schema([
|
||||
TextInput::make('APP_API_DATABASE_CREATE_RATELIMIT')
|
||||
->label(trans('admin/setting.misc.api.requests_per_period'))
|
||||
->required()
|
||||
->numeric()
|
||||
->minValue(1)
|
||||
->suffix(trans('admin/setting.misc.api.requests'))->default(env('APP_API_DATABASE_CREATE_RATELIMIT', config('http.rate_limit.database_create'))),
|
||||
|
||||
TextInput::make('APP_API_DATABASE_CREATE_RATELIMIT_PERIOD')
|
||||
->label(trans('admin/setting.misc.api.period_length'))
|
||||
->required()
|
||||
->numeric()
|
||||
->minValue(1)
|
||||
->suffix(trans('admin/setting.misc.api.minutes'))->default(env('APP_API_DATABASE_CREATE_RATELIMIT_PERIOD', config('http.rate_limit.database_create_period'))),
|
||||
Text::make(trans('admin/setting.misc.api.database_create_helper')),
|
||||
]),
|
||||
Fieldset::make()
|
||||
->label(trans('admin/setting.misc.api.subuser_create_title'))
|
||||
->schema([
|
||||
TextInput::make('APP_API_SUBUSER_CREATE_RATELIMIT')
|
||||
->label(trans('admin/setting.misc.api.requests_per_period'))
|
||||
->required()
|
||||
->numeric()
|
||||
->minValue(1)
|
||||
->suffix(trans('admin/setting.misc.api.requests'))->default(env('APP_API_SUBUSER_CREATE_RATELIMIT', config('http.rate_limit.subuser_create'))),
|
||||
|
||||
TextInput::make('APP_API_SUBUSER_CREATE_RATELIMIT_PERIOD')
|
||||
->label(trans('admin/setting.misc.api.period_length'))
|
||||
->required()
|
||||
->numeric()
|
||||
->minValue(1)
|
||||
->suffix(trans('admin/setting.misc.api.minutes'))->default(env('APP_API_SUBUSER_CREATE_RATELIMIT_PERIOD', config('http.rate_limit.subuser_create_period'))),
|
||||
Text::make(trans('admin/setting.misc.api.subuser_create_helper')),
|
||||
]),
|
||||
Fieldset::make()
|
||||
->label(trans('admin/setting.misc.api.file_pull_title'))
|
||||
->schema([
|
||||
TextInput::make('APP_API_FILE_PULL_RATELIMIT')
|
||||
->label(trans('admin/setting.misc.api.requests_per_period'))
|
||||
->required()
|
||||
->numeric()
|
||||
->minValue(1)
|
||||
->suffix(trans('admin/setting.misc.api.requests'))->default(env('APP_API_FILE_PULL_RATELIMIT', config('http.rate_limit.file_pull'))),
|
||||
|
||||
TextInput::make('APP_API_FILE_PULL_RATELIMIT_PERIOD')
|
||||
->label(trans('admin/setting.misc.api.period_length'))
|
||||
->required()
|
||||
->numeric()
|
||||
->minValue(1)
|
||||
->suffix(trans('admin/setting.misc.api.minutes'))->default(env('APP_API_FILE_PULL_RATELIMIT_PERIOD', config('http.rate_limit.file_pull_period'))),
|
||||
Text::make(trans('admin/setting.misc.api.file_pull_helper')),
|
||||
]),
|
||||
Fieldset::make()
|
||||
->label(trans('admin/setting.misc.api.default_title'))
|
||||
->schema([
|
||||
TextInput::make('APP_API_DEFAULT_RATELIMIT')
|
||||
->label(trans('admin/setting.misc.api.requests_per_period'))
|
||||
->required()
|
||||
->numeric()
|
||||
->minValue(1)
|
||||
->suffix(trans('admin/setting.misc.api.requests'))->default(env('APP_API_DEFAULT_RATELIMIT', config('http.rate_limit.default'))),
|
||||
|
||||
TextInput::make('APP_API_DEFAULT_RATELIMIT_PERIOD')
|
||||
->label(trans('admin/setting.misc.api.period_length'))
|
||||
->required()
|
||||
->numeric()
|
||||
->minValue(1)
|
||||
->suffix(trans('admin/setting.misc.api.minutes'))->default(env('APP_API_DEFAULT_RATELIMIT_PERIOD', config('http.rate_limit.default_period'))),
|
||||
Text::make(trans('admin/setting.misc.api.default_helper')),
|
||||
]),
|
||||
]),
|
||||
Section::make(trans('admin/setting.misc.server.title'))
|
||||
->description(trans('admin/setting.misc.server.helper'))
|
||||
@@ -760,8 +925,8 @@ class Settings extends Page implements HasSchemas
|
||||
->schema([
|
||||
Toggle::make('PANEL_EDITABLE_SERVER_DESCRIPTIONS')
|
||||
->label(trans('admin/setting.misc.server.edit_server_desc'))
|
||||
->onIcon('tabler-check')
|
||||
->offIcon('tabler-x')
|
||||
->onIcon(TablerIcon::Check)
|
||||
->offIcon(TablerIcon::X)
|
||||
->onColor('success')
|
||||
->offColor('danger')
|
||||
->live()
|
||||
@@ -769,13 +934,14 @@ class Settings extends Page implements HasSchemas
|
||||
->formatStateUsing(fn ($state): bool => (bool) $state)
|
||||
->afterStateUpdated(fn ($state, Set $set) => $set('PANEL_EDITABLE_SERVER_DESCRIPTIONS', (bool) $state))
|
||||
->default(env('PANEL_EDITABLE_SERVER_DESCRIPTIONS', config('panel.editable_server_descriptions'))),
|
||||
FileUpload::make('ConsoleFonts')
|
||||
FileUpload::make('console_font')
|
||||
->hint(trans('admin/setting.misc.server.console_font_hint'))
|
||||
->label(trans('admin/setting.misc.server.console_font_upload'))
|
||||
->directory('fonts')
|
||||
->disk('public')
|
||||
->columnSpan(1)
|
||||
->maxFiles(1)
|
||||
->acceptedFileTypes(['font/*'])
|
||||
->preserveFilenames(),
|
||||
]),
|
||||
Section::make(trans('admin/setting.misc.webhook.title'))
|
||||
@@ -803,9 +969,11 @@ class Settings extends Page implements HasSchemas
|
||||
|
||||
public function save(): void
|
||||
{
|
||||
abort_unless(user()?->can('update settings'), 403);
|
||||
|
||||
try {
|
||||
$data = $this->form->getState();
|
||||
unset($data['ConsoleFonts']);
|
||||
unset($data['console_font']);
|
||||
|
||||
$data = array_map(function ($value) {
|
||||
// Convert bools to a string, so they are correctly written to the .env file
|
||||
@@ -823,7 +991,6 @@ class Settings extends Page implements HasSchemas
|
||||
|
||||
$this->writeToEnvironment($data);
|
||||
|
||||
Artisan::call('config:clear');
|
||||
Artisan::call('queue:restart');
|
||||
|
||||
$this->redirect($this->getUrl());
|
||||
@@ -846,12 +1013,12 @@ class Settings extends Page implements HasSchemas
|
||||
{
|
||||
return [
|
||||
Action::make('save')
|
||||
->iconButton()->iconSize(IconSize::ExtraLarge)
|
||||
->icon('tabler-device-floppy')
|
||||
->hiddenLabel()
|
||||
->icon(TablerIcon::DeviceFloppy)
|
||||
->action('save')
|
||||
->tooltip(trans('filament-panels::resources/pages/edit-record.form.actions.save.label'))
|
||||
->authorize(fn () => user()?->can('update settings'))
|
||||
->keyBindings(['mod+s']),
|
||||
];
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Filament\Admin\Pages;
|
||||
|
||||
use App\Enums\TablerIcon;
|
||||
use App\Traits\ResolvesRecordDate;
|
||||
use Boquizo\FilamentLogViewer\Actions\BackAction;
|
||||
use Boquizo\FilamentLogViewer\Actions\DeleteAction;
|
||||
@@ -9,7 +10,6 @@ use Boquizo\FilamentLogViewer\Actions\DownloadAction;
|
||||
use Boquizo\FilamentLogViewer\Pages\ViewLog as BaseViewLog;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Support\Enums\IconSize;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
|
||||
class ViewLogs extends BaseViewLog
|
||||
@@ -20,20 +20,23 @@ class ViewLogs extends BaseViewLog
|
||||
{
|
||||
return [
|
||||
BackAction::make()
|
||||
->icon('tabler-arrow-left')->iconSize(IconSize::ExtraLarge)->iconButton(),
|
||||
->tooltip(trans('filament-log-viewer::log.table.actions.close.label'))
|
||||
->icon(TablerIcon::ArrowLeft)->iconButton(),
|
||||
DeleteAction::make(withTooltip: true)
|
||||
->iconSize(IconSize::ExtraLarge)->iconButton(),
|
||||
->icon(TablerIcon::Trash)->iconButton(),
|
||||
DownloadAction::make(withTooltip: true)
|
||||
->icon('tabler-file-download')->iconSize(IconSize::ExtraLarge)->iconButton(),
|
||||
->icon(TablerIcon::FileDownload)->iconButton(),
|
||||
Action::make('uploadLogs')
|
||||
->hiddenLabel()
|
||||
->icon('tabler-world-upload')->iconSize(IconSize::ExtraLarge)->iconButton()
|
||||
->icon(TablerIcon::WorldUpload)
|
||||
->requiresConfirmation()
|
||||
->tooltip(trans('admin/log.actions.upload_tooltip', ['url' => 'logs.pelican.dev']))
|
||||
->modalHeading(trans('admin/log.actions.upload_logs'))
|
||||
->modalDescription(fn () => trans('admin/log.actions.upload_logs_description', ['file' => $this->resolveRecordDate(), 'url' => 'https://logs.pelican.dev']))
|
||||
->action(function () {
|
||||
$logPath = storage_path('logs/' . $this->resolveRecordDate());
|
||||
$prefix = config('filament-log-viewer.pattern.prefix', 'laravel-');
|
||||
$extension = config('filament-log-viewer.pattern.extension', '.log');
|
||||
$logPath = storage_path('logs/' . $prefix . $this->resolveRecordDate() . $extension);
|
||||
|
||||
if (!file_exists($logPath)) {
|
||||
Notification::make()
|
||||
@@ -50,18 +53,12 @@ class ViewLogs extends BaseViewLog
|
||||
$uploadLines = $totalLines <= 1000 ? $lines : array_slice($lines, -1000);
|
||||
$content = implode("\n", $uploadLines);
|
||||
|
||||
$logUrl = 'https://logs.pelican.dev';
|
||||
try {
|
||||
$response = Http::timeout(10)->asMultipart()->post($logUrl, [
|
||||
[
|
||||
'name' => 'c',
|
||||
'contents' => $content,
|
||||
],
|
||||
[
|
||||
'name' => 'e',
|
||||
'contents' => '14d',
|
||||
],
|
||||
]);
|
||||
$response = Http::timeout(10)
|
||||
->asMultipart()
|
||||
->attach('c', $content)
|
||||
->attach('e', '14d')
|
||||
->post('https://logs.pelican.dev');
|
||||
|
||||
if ($response->failed()) {
|
||||
Notification::make()
|
||||
@@ -81,7 +78,7 @@ class ViewLogs extends BaseViewLog
|
||||
->body("{$url}")
|
||||
->success()
|
||||
->actions([
|
||||
Action::make('viewLogs')
|
||||
Action::make('exclude_viewLogs')
|
||||
->label(trans('admin/log.actions.view_logs'))
|
||||
->url($url)
|
||||
->openUrlInNewTab(true),
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Filament\Admin\Resources\ApiKeys;
|
||||
|
||||
use App\Enums\TablerIcon;
|
||||
use App\Filament\Admin\Resources\ApiKeys\Pages\CreateApiKey;
|
||||
use App\Filament\Admin\Resources\ApiKeys\Pages\ListApiKeys;
|
||||
use App\Filament\Admin\Resources\Users\Pages\EditUser;
|
||||
@@ -11,7 +12,9 @@ use App\Traits\Filament\CanCustomizePages;
|
||||
use App\Traits\Filament\CanCustomizeRelations;
|
||||
use App\Traits\Filament\CanModifyForm;
|
||||
use App\Traits\Filament\CanModifyTable;
|
||||
use BackedEnum;
|
||||
use Exception;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Forms\Components\TagsInput;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
@@ -19,8 +22,8 @@ use Filament\Forms\Components\ToggleButtons;
|
||||
use Filament\Resources\Pages\PageRegistration;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Components\Fieldset;
|
||||
use Filament\Schemas\Components\Section;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Support\Enums\IconSize;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
@@ -34,7 +37,7 @@ class ApiKeyResource extends Resource
|
||||
|
||||
protected static ?string $model = ApiKey::class;
|
||||
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'tabler-key';
|
||||
protected static string|BackedEnum|null $navigationIcon = TablerIcon::Key;
|
||||
|
||||
public static function getNavigationLabel(): string
|
||||
{
|
||||
@@ -77,7 +80,7 @@ class ApiKeyResource extends Resource
|
||||
->columns([
|
||||
TextColumn::make('key')
|
||||
->label(trans('admin/apikey.table.key'))
|
||||
->icon('tabler-clipboard-text')
|
||||
->icon(TablerIcon::ClipboardText)
|
||||
->state(fn (ApiKey $key) => $key->identifier . $key->token)
|
||||
->copyable(),
|
||||
TextColumn::make('memo')
|
||||
@@ -96,10 +99,12 @@ class ApiKeyResource extends Resource
|
||||
->url(fn (ApiKey $apiKey) => user()?->can('update', $apiKey->user) ? EditUser::getUrl(['record' => $apiKey->user]) : null),
|
||||
])
|
||||
->recordActions([
|
||||
DeleteAction::make()
|
||||
->iconButton()->iconSize(IconSize::ExtraLarge),
|
||||
DeleteAction::make(),
|
||||
])
|
||||
->emptyStateIcon('tabler-key')
|
||||
->toolbarActions([
|
||||
CreateAction::make(),
|
||||
])
|
||||
->emptyStateIcon(TablerIcon::Key)
|
||||
->emptyStateDescription('')
|
||||
->emptyStateHeading(trans('admin/apikey.empty'));
|
||||
}
|
||||
@@ -109,12 +114,44 @@ class ApiKeyResource extends Resource
|
||||
*/
|
||||
public static function defaultForm(Schema $schema): Schema
|
||||
{
|
||||
$permissionList = ApiKey::getPermissionList();
|
||||
|
||||
return $schema
|
||||
->components([
|
||||
Section::make(trans('admin/apikey.permissions.all'))
|
||||
->description(trans('admin/apikey.permissions.all_description'))
|
||||
->columnSpanFull()
|
||||
->schema([
|
||||
ToggleButtons::make('permissions_all')
|
||||
->hiddenLabel()
|
||||
->inline()
|
||||
->options([
|
||||
0 => trans('admin/apikey.permissions.none'),
|
||||
1 => trans('admin/apikey.permissions.read'),
|
||||
3 => trans('admin/apikey.permissions.read_write'),
|
||||
])
|
||||
->icons([
|
||||
0 => TablerIcon::BookOff,
|
||||
1 => TablerIcon::Book,
|
||||
3 => TablerIcon::Writing,
|
||||
])
|
||||
->colors([
|
||||
0 => 'success',
|
||||
1 => 'warning',
|
||||
3 => 'danger',
|
||||
])
|
||||
->live()
|
||||
->afterStateUpdated(function ($state, callable $set) use ($permissionList) {
|
||||
foreach ($permissionList as $resource) {
|
||||
$set('permissions_' . $resource, $state);
|
||||
}
|
||||
})
|
||||
->default(0),
|
||||
]),
|
||||
Fieldset::make('Permissions')
|
||||
->columnSpanFull()
|
||||
->schema(
|
||||
collect(ApiKey::getPermissionList())->map(fn ($resource) => ToggleButtons::make('permissions_' . $resource)
|
||||
collect($permissionList)->map(fn ($resource) => ToggleButtons::make('permissions_' . $resource)
|
||||
->label(str($resource)->replace('_', ' ')->title())->inline()
|
||||
->options([
|
||||
0 => trans('admin/apikey.permissions.none'),
|
||||
@@ -122,9 +159,9 @@ class ApiKeyResource extends Resource
|
||||
3 => trans('admin/apikey.permissions.read_write'),
|
||||
])
|
||||
->icons([
|
||||
0 => 'tabler-book-off',
|
||||
1 => 'tabler-book',
|
||||
3 => 'tabler-writing',
|
||||
0 => TablerIcon::BookOff,
|
||||
1 => TablerIcon::Book,
|
||||
3 => TablerIcon::Writing,
|
||||
])
|
||||
->colors([
|
||||
0 => 'success',
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Filament\Admin\Resources\ApiKeys\Pages;
|
||||
|
||||
use App\Enums\TablerIcon;
|
||||
use App\Filament\Admin\Resources\ApiKeys\ApiKeyResource;
|
||||
use App\Models\ApiKey;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
@@ -9,7 +10,6 @@ use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\ActionGroup;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
use Filament\Support\Enums\IconSize;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
@@ -26,9 +26,12 @@ class CreateApiKey extends CreateRecord
|
||||
protected function getDefaultHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
$this->getCreateFormAction()->formId('form')
|
||||
->iconButton()->iconSize(IconSize::ExtraLarge)
|
||||
->icon('tabler-file-plus'),
|
||||
Action::make('create')
|
||||
->hiddenLabel()
|
||||
->action('create')
|
||||
->keyBindings(['mod+s'])
|
||||
->tooltip(trans('filament-panels::resources/pages/create-record.form.actions.create.label'))
|
||||
->icon(TablerIcon::FilePlus),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -5,11 +5,7 @@ namespace App\Filament\Admin\Resources\ApiKeys\Pages;
|
||||
use App\Filament\Admin\Resources\ApiKeys\ApiKeyResource;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\ActionGroup;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
use Filament\Support\Enums\IconSize;
|
||||
|
||||
class ListApiKeys extends ListRecords
|
||||
{
|
||||
@@ -17,14 +13,4 @@ class ListApiKeys extends ListRecords
|
||||
use CanCustomizeHeaderWidgets;
|
||||
|
||||
protected static string $resource = ApiKeyResource::class;
|
||||
|
||||
/** @return array<Action|ActionGroup> */
|
||||
protected function getDefaultHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
CreateAction::make()
|
||||
->iconButton()->iconSize(IconSize::ExtraLarge)
|
||||
->icon('tabler-file-plus'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
162
app/Filament/Admin/Resources/BackupHosts/BackupHostResource.php
Normal file
162
app/Filament/Admin/Resources/BackupHosts/BackupHostResource.php
Normal file
@@ -0,0 +1,162 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources\BackupHosts;
|
||||
|
||||
use App\Enums\TablerIcon;
|
||||
use App\Extensions\BackupAdapter\BackupAdapterService;
|
||||
use App\Filament\Admin\Resources\BackupHosts\Pages\CreateBackupHost;
|
||||
use App\Filament\Admin\Resources\BackupHosts\Pages\EditBackupHost;
|
||||
use App\Filament\Admin\Resources\BackupHosts\Pages\ListBackupHosts;
|
||||
use App\Filament\Admin\Resources\BackupHosts\Pages\ViewBackupHost;
|
||||
use App\Filament\Admin\Resources\BackupHosts\RelationManagers\BackupsRelationManager;
|
||||
use App\Models\BackupHost;
|
||||
use App\Traits\Filament\CanCustomizePages;
|
||||
use App\Traits\Filament\CanCustomizeRelations;
|
||||
use App\Traits\Filament\CanModifyForm;
|
||||
use App\Traits\Filament\CanModifyTable;
|
||||
use BackedEnum;
|
||||
use Exception;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Actions\ViewAction;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Resources\Pages\PageRegistration;
|
||||
use Filament\Resources\RelationManagers\RelationManager;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Components\Section;
|
||||
use Filament\Schemas\Components\Utilities\Get;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
class BackupHostResource extends Resource
|
||||
{
|
||||
use CanCustomizePages;
|
||||
use CanCustomizeRelations;
|
||||
use CanModifyForm;
|
||||
use CanModifyTable;
|
||||
|
||||
protected static ?string $model = BackupHost::class;
|
||||
|
||||
protected static string|BackedEnum|null $navigationIcon = TablerIcon::FileZip;
|
||||
|
||||
protected static ?string $recordTitleAttribute = 'name';
|
||||
|
||||
public static function getNavigationBadge(): ?string
|
||||
{
|
||||
return (string) static::getEloquentQuery()->count() ?: null;
|
||||
}
|
||||
|
||||
public static function getNavigationLabel(): string
|
||||
{
|
||||
return static::getPluralModelLabel();
|
||||
}
|
||||
|
||||
public static function getModelLabel(): string
|
||||
{
|
||||
return trans_choice('admin/backuphost.model_label', 1);
|
||||
}
|
||||
|
||||
public static function getPluralModelLabel(): string
|
||||
{
|
||||
return trans_choice('admin/backuphost.model_label', 2);
|
||||
}
|
||||
|
||||
public static function getNavigationGroup(): ?string
|
||||
{
|
||||
return trans('admin/dashboard.advanced');
|
||||
}
|
||||
|
||||
/** @throws Exception */
|
||||
public static function defaultTable(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
TextColumn::make('name')
|
||||
->label(trans('admin/backuphost.name')),
|
||||
TextColumn::make('schema')
|
||||
->label(trans('admin/backuphost.schema'))
|
||||
->badge(),
|
||||
TextColumn::make('backups_count')
|
||||
->counts('backups')
|
||||
->label(trans('admin/backuphost.backups')),
|
||||
TextColumn::make('nodes.name')
|
||||
->badge()
|
||||
->placeholder(trans('admin/backuphost.all_nodes')),
|
||||
])
|
||||
->recordActions([
|
||||
ViewAction::make()
|
||||
->hidden(fn ($record) => static::getEditAuthorizationResponse($record)->allowed()),
|
||||
EditAction::make(),
|
||||
])
|
||||
->toolbarActions([
|
||||
CreateAction::make(),
|
||||
])
|
||||
->emptyStateIcon(TablerIcon::FileZip)
|
||||
->emptyStateDescription(trans('admin/backuphost.local_backups_only'))
|
||||
->emptyStateHeading(trans('admin/backuphost.no_backup_hosts'));
|
||||
}
|
||||
|
||||
/** @throws Exception */
|
||||
public static function defaultForm(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
TextInput::make('name')
|
||||
->label(trans('admin/backuphost.name'))
|
||||
->required(),
|
||||
Select::make('schema')
|
||||
->label(trans('admin/backuphost.schema'))
|
||||
->required()
|
||||
->selectablePlaceholder(false)
|
||||
->searchable()
|
||||
->options(fn (BackupAdapterService $service) => $service->getMappings())
|
||||
->live(onBlur: true),
|
||||
Select::make('node_ids')
|
||||
->label(trans('admin/backuphost.linked_nodes'))
|
||||
->multiple()
|
||||
->searchable()
|
||||
->preload()
|
||||
->relationship('nodes', 'name', fn (Builder $query) => $query->whereIn('nodes.id', user()?->accessibleNodes()->pluck('id'))),
|
||||
Section::make(trans('admin/backuphost.configuration'))
|
||||
->columnSpanFull()
|
||||
->columns()
|
||||
->schema(function (?BackupHost $backupHost, Get $get, BackupAdapterService $service) {
|
||||
$schema = $get('schema') ?? $backupHost?->schema;
|
||||
|
||||
if (!$schema) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$schema = $service->get($schema);
|
||||
|
||||
if ($schema) {
|
||||
return $schema->getConfigurationForm();
|
||||
}
|
||||
|
||||
return [];
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
/** @return class-string<RelationManager>[] */
|
||||
public static function getDefaultRelations(): array
|
||||
{
|
||||
return [
|
||||
BackupsRelationManager::class,
|
||||
];
|
||||
}
|
||||
|
||||
/** @return array<string, PageRegistration> */
|
||||
public static function getDefaultPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => ListBackupHosts::route('/'),
|
||||
'create' => CreateBackupHost::route('/create'),
|
||||
'view' => ViewBackupHost::route('/{record}'),
|
||||
'edit' => EditBackupHost::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources\BackupHosts\Pages;
|
||||
|
||||
use App\Filament\Admin\Resources\BackupHosts\BackupHostResource;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreateBackupHost extends CreateRecord
|
||||
{
|
||||
use CanCustomizeHeaderActions;
|
||||
use CanCustomizeHeaderWidgets;
|
||||
|
||||
protected static string $resource = BackupHostResource::class;
|
||||
|
||||
protected static bool $canCreateAnother = false;
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources\BackupHosts\Pages;
|
||||
|
||||
use App\Enums\TablerIcon;
|
||||
use App\Filament\Admin\Resources\BackupHosts\BackupHostResource;
|
||||
use App\Models\BackupHost;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\ActionGroup;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditBackupHost extends EditRecord
|
||||
{
|
||||
use CanCustomizeHeaderActions;
|
||||
use CanCustomizeHeaderWidgets;
|
||||
|
||||
protected static string $resource = BackupHostResource::class;
|
||||
|
||||
/** @return array<Action|ActionGroup> */
|
||||
protected function getDefaultHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
DeleteAction::make()
|
||||
->label(fn (BackupHost $backupHost) => $backupHost->backups()->count() > 0 ? trans('admin/backuphost.delete_help') : trans('filament-actions::delete.single.modal.actions.delete.label'))
|
||||
->disabled(fn (BackupHost $backupHost) => $backupHost->backups()->count() > 0)
|
||||
->hidden(fn () => BackupHost::count() === 1),
|
||||
Action::make('save')
|
||||
->hiddenLabel()
|
||||
->action('save')
|
||||
->keyBindings(['mod+s'])
|
||||
->tooltip(trans('filament-panels::resources/pages/edit-record.form.actions.save.label'))
|
||||
->icon(TablerIcon::DeviceFloppy),
|
||||
];
|
||||
}
|
||||
|
||||
protected function getFormActions(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources\BackupHosts\Pages;
|
||||
|
||||
use App\Filament\Admin\Resources\BackupHosts\BackupHostResource;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListBackupHosts extends ListRecords
|
||||
{
|
||||
use CanCustomizeHeaderActions;
|
||||
use CanCustomizeHeaderWidgets;
|
||||
|
||||
protected static string $resource = BackupHostResource::class;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources\BackupHosts\Pages;
|
||||
|
||||
use App\Filament\Admin\Resources\BackupHosts\BackupHostResource;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\ActionGroup;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Resources\Pages\ViewRecord;
|
||||
|
||||
class ViewBackupHost extends ViewRecord
|
||||
{
|
||||
use CanCustomizeHeaderActions;
|
||||
use CanCustomizeHeaderWidgets;
|
||||
|
||||
protected static string $resource = BackupHostResource::class;
|
||||
|
||||
/** @return array<Action|ActionGroup> */
|
||||
protected function getDefaultHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
EditAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources\BackupHosts\RelationManagers;
|
||||
|
||||
use App\Enums\TablerIcon;
|
||||
use App\Filament\Components\Tables\Columns\BytesColumn;
|
||||
use App\Filament\Components\Tables\Columns\DateTimeColumn;
|
||||
use Filament\Resources\RelationManagers\RelationManager;
|
||||
use Filament\Tables\Columns\IconColumn;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class BackupsRelationManager extends RelationManager
|
||||
{
|
||||
protected static string $relationship = 'backups';
|
||||
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->recordTitleAttribute('name')
|
||||
->heading(null)
|
||||
->columns([
|
||||
TextColumn::make('name')
|
||||
->label(trans('server/backup.actions.create.name'))
|
||||
->searchable(),
|
||||
BytesColumn::make('bytes')
|
||||
->label(trans('server/backup.size')),
|
||||
DateTimeColumn::make('created_at')
|
||||
->label(trans('server/backup.created_at'))
|
||||
->since()
|
||||
->sortable(),
|
||||
TextColumn::make('status')
|
||||
->label(trans('server/backup.status'))
|
||||
->badge(),
|
||||
IconColumn::make('is_locked')
|
||||
->label(trans('server/backup.is_locked'))
|
||||
->visibleFrom('md')
|
||||
->trueIcon(TablerIcon::Lock)
|
||||
->falseIcon(TablerIcon::LockOpen),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Filament\Admin\Resources\DatabaseHosts;
|
||||
|
||||
use App\Enums\TablerIcon;
|
||||
use App\Filament\Admin\Resources\DatabaseHosts\Pages\CreateDatabaseHost;
|
||||
use App\Filament\Admin\Resources\DatabaseHosts\Pages\EditDatabaseHost;
|
||||
use App\Filament\Admin\Resources\DatabaseHosts\Pages\ListDatabaseHosts;
|
||||
@@ -12,7 +13,10 @@ use App\Traits\Filament\CanCustomizePages;
|
||||
use App\Traits\Filament\CanCustomizeRelations;
|
||||
use App\Traits\Filament\CanModifyForm;
|
||||
use App\Traits\Filament\CanModifyTable;
|
||||
use BackedEnum;
|
||||
use Exception;
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Actions\ViewAction;
|
||||
@@ -37,7 +41,7 @@ class DatabaseHostResource extends Resource
|
||||
|
||||
protected static ?string $model = DatabaseHost::class;
|
||||
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'tabler-database';
|
||||
protected static string|BackedEnum|null $navigationIcon = TablerIcon::Database;
|
||||
|
||||
protected static ?string $recordTitleAttribute = 'name';
|
||||
|
||||
@@ -85,6 +89,7 @@ class DatabaseHostResource extends Resource
|
||||
->counts('databases')
|
||||
->label(trans('admin/databasehost.databases')),
|
||||
TextColumn::make('nodes.name')
|
||||
->label(trans('admin/databasehost.nodes'))
|
||||
->badge()
|
||||
->placeholder(trans('admin/databasehost.no_nodes')),
|
||||
])
|
||||
@@ -94,10 +99,13 @@ class DatabaseHostResource extends Resource
|
||||
->hidden(fn ($record) => static::getEditAuthorizationResponse($record)->allowed()),
|
||||
EditAction::make(),
|
||||
])
|
||||
->groupedBulkActions([
|
||||
DeleteBulkAction::make(),
|
||||
->toolbarActions([
|
||||
CreateAction::make(),
|
||||
BulkActionGroup::make([
|
||||
DeleteBulkAction::make('exclude_bulk_delete'),
|
||||
]),
|
||||
])
|
||||
->emptyStateIcon('tabler-database')
|
||||
->emptyStateIcon(TablerIcon::Database)
|
||||
->emptyStateDescription('')
|
||||
->emptyStateHeading(trans('admin/databasehost.no_database_hosts'));
|
||||
}
|
||||
|
||||
@@ -2,11 +2,13 @@
|
||||
|
||||
namespace App\Filament\Admin\Resources\DatabaseHosts\Pages;
|
||||
|
||||
use App\Enums\TablerIcon;
|
||||
use App\Filament\Admin\Resources\DatabaseHosts\DatabaseHostResource;
|
||||
use App\Services\Databases\Hosts\HostCreationService;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use Exception;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Forms\Components\Hidden;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
@@ -44,6 +46,17 @@ class CreateDatabaseHost extends CreateRecord
|
||||
$this->service = $service;
|
||||
}
|
||||
|
||||
protected function getCreateFormAction(): Action
|
||||
{
|
||||
$hasFormWrapper = $this->hasFormWrapper();
|
||||
|
||||
return Action::make('exclude_create')
|
||||
->label(trans('filament-panels::resources/pages/create-record.form.actions.create.label'))
|
||||
->submit($hasFormWrapper ? $this->getSubmitFormLivewireMethodName() : null)
|
||||
->action($hasFormWrapper ? null : $this->getSubmitFormLivewireMethodName())
|
||||
->keyBindings(['mod+s']);
|
||||
}
|
||||
|
||||
/** @return Step[]
|
||||
* @throws Exception
|
||||
*/
|
||||
@@ -175,7 +188,7 @@ class CreateDatabaseHost extends CreateRecord
|
||||
->title(trans('admin/databasehost.error'))
|
||||
->body($exception->getMessage())
|
||||
->color('danger')
|
||||
->icon('tabler-database')
|
||||
->icon(TablerIcon::Database)
|
||||
->danger()
|
||||
->send();
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Filament\Admin\Resources\DatabaseHosts\Pages;
|
||||
|
||||
use App\Enums\TablerIcon;
|
||||
use App\Filament\Admin\Resources\DatabaseHosts\DatabaseHostResource;
|
||||
use App\Models\DatabaseHost;
|
||||
use App\Services\Databases\Hosts\HostUpdateService;
|
||||
@@ -12,7 +13,6 @@ use Filament\Actions\ActionGroup;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
use Filament\Support\Enums\IconSize;
|
||||
use Filament\Support\Exceptions\Halt;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use PDOException;
|
||||
@@ -36,12 +36,14 @@ class EditDatabaseHost extends EditRecord
|
||||
{
|
||||
return [
|
||||
DeleteAction::make()
|
||||
->label(fn (DatabaseHost $databaseHost) => $databaseHost->databases()->count() > 0 ? trans('admin/databasehost.delete_help') : trans('filament-actions::delete.single.modal.actions.delete.label'))
|
||||
->disabled(fn (DatabaseHost $databaseHost) => $databaseHost->databases()->count() > 0)
|
||||
->iconButton()->iconSize(IconSize::ExtraLarge),
|
||||
$this->getSaveFormAction()->formId('form')
|
||||
->iconButton()->iconSize(IconSize::ExtraLarge)
|
||||
->icon('tabler-device-floppy'),
|
||||
->tooltip(fn (DatabaseHost $databaseHost) => $databaseHost->databases()->count() > 0 ? trans('admin/databasehost.delete_help') : trans('filament-actions::delete.single.modal.actions.delete.label'))
|
||||
->disabled(fn (DatabaseHost $databaseHost) => $databaseHost->databases()->count() > 0),
|
||||
Action::make('save')
|
||||
->hiddenLabel()
|
||||
->action('save')
|
||||
->keyBindings(['mod+s'])
|
||||
->tooltip(trans('filament-panels::resources/pages/edit-record.form.actions.save.label'))
|
||||
->icon(TablerIcon::DeviceFloppy),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -63,7 +65,7 @@ class EditDatabaseHost extends EditRecord
|
||||
->title(trans('admin/databasehost.error'))
|
||||
->body($exception->getMessage())
|
||||
->color('danger')
|
||||
->icon('tabler-database')
|
||||
->icon(TablerIcon::Database)
|
||||
->danger()
|
||||
->send();
|
||||
|
||||
|
||||
@@ -5,11 +5,7 @@ namespace App\Filament\Admin\Resources\DatabaseHosts\Pages;
|
||||
use App\Filament\Admin\Resources\DatabaseHosts\DatabaseHostResource;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\ActionGroup;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
use Filament\Support\Enums\IconSize;
|
||||
|
||||
class ListDatabaseHosts extends ListRecords
|
||||
{
|
||||
@@ -17,14 +13,4 @@ class ListDatabaseHosts extends ListRecords
|
||||
use CanCustomizeHeaderWidgets;
|
||||
|
||||
protected static string $resource = DatabaseHostResource::class;
|
||||
|
||||
/** @return array<Action|ActionGroup> */
|
||||
protected function getDefaultHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
CreateAction::make()
|
||||
->iconButton()->iconSize(IconSize::ExtraLarge)
|
||||
->icon('tabler-file-plus'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ use Filament\Actions\Action;
|
||||
use Filament\Actions\ActionGroup;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Resources\Pages\ViewRecord;
|
||||
use Filament\Support\Enums\IconSize;
|
||||
|
||||
class ViewDatabaseHost extends ViewRecord
|
||||
{
|
||||
@@ -22,8 +21,7 @@ class ViewDatabaseHost extends ViewRecord
|
||||
protected function getDefaultHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
EditAction::make()
|
||||
->iconButton()->iconSize(IconSize::ExtraLarge),
|
||||
EditAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ use Filament\Actions\ViewAction;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Resources\RelationManagers\RelationManager;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Support\Enums\IconSize;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
@@ -37,7 +36,7 @@ class DatabasesRelationManager extends RelationManager
|
||||
->formatStateUsing(fn (Database $record) => $record->remote === '%' ? trans('admin/databasehost.anywhere'). ' ( % )' : $record->remote),
|
||||
TextInput::make('max_connections')
|
||||
->label(trans('admin/databasehost.table.max_connections'))
|
||||
->formatStateUsing(fn (Database $record) => $record->max_connections === 0 ? trans('admin/databasehost.unlimited') : $record->max_connections),
|
||||
->formatStateUsing(fn (Database $record) => $record->max_connections ?: trans('admin/databasehost.unlimited')),
|
||||
TextInput::make('jdbc')
|
||||
->label(trans('admin/databasehost.table.connection_string'))
|
||||
->columnSpanFull()
|
||||
@@ -63,15 +62,15 @@ class DatabasesRelationManager extends RelationManager
|
||||
->url(fn (Database $database) => route('filament.admin.resources.servers.edit', ['record' => $database->server_id])),
|
||||
TextColumn::make('max_connections')
|
||||
->label(trans('admin/databasehost.table.max_connections'))
|
||||
->formatStateUsing(fn ($record) => $record->max_connections === 0 ? trans('admin/databasehost.unlimited') : $record->max_connections),
|
||||
->formatStateUsing(fn ($record) => $record->max_connections ?: trans('server/database.unlimited')),
|
||||
DateTimeColumn::make('created_at')
|
||||
->label(trans('admin/databasehost.table.created_at')),
|
||||
])
|
||||
->emptyStateHeading(trans('admin/databasehost.no_databases'))
|
||||
->recordActions([
|
||||
ViewAction::make()
|
||||
->color('primary'),
|
||||
DeleteAction::make()
|
||||
->iconButton()->iconSize(IconSize::ExtraLarge),
|
||||
DeleteAction::make(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Filament\Admin\Resources\Eggs;
|
||||
|
||||
use App\Enums\CustomizationKey;
|
||||
use App\Enums\TablerIcon;
|
||||
use App\Filament\Admin\Resources\Eggs\Pages\CreateEgg;
|
||||
use App\Filament\Admin\Resources\Eggs\Pages\EditEgg;
|
||||
use App\Filament\Admin\Resources\Eggs\Pages\ListEggs;
|
||||
@@ -10,6 +11,7 @@ use App\Filament\Admin\Resources\Eggs\RelationManagers\ServersRelationManager;
|
||||
use App\Models\Egg;
|
||||
use App\Traits\Filament\CanCustomizePages;
|
||||
use App\Traits\Filament\CanCustomizeRelations;
|
||||
use BackedEnum;
|
||||
use Filament\Resources\Pages\PageRegistration;
|
||||
use Filament\Resources\RelationManagers\RelationManager;
|
||||
use Filament\Resources\Resource;
|
||||
@@ -21,7 +23,7 @@ class EggResource extends Resource
|
||||
|
||||
protected static ?string $model = Egg::class;
|
||||
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'tabler-eggs';
|
||||
protected static string|BackedEnum|null $navigationIcon = TablerIcon::Eggs;
|
||||
|
||||
protected static ?string $recordTitleAttribute = 'name';
|
||||
|
||||
@@ -32,7 +34,11 @@ class EggResource extends Resource
|
||||
|
||||
public static function getNavigationGroup(): ?string
|
||||
{
|
||||
return user()?->getCustomization(CustomizationKey::TopNavigation) ? false : trans('admin/dashboard.server');
|
||||
if (user()?->getCustomization(CustomizationKey::TopNavigation)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return trans('admin/dashboard.server');
|
||||
}
|
||||
|
||||
public static function getNavigationLabel(): string
|
||||
|
||||
@@ -3,13 +3,14 @@
|
||||
namespace App\Filament\Admin\Resources\Eggs\Pages;
|
||||
|
||||
use App\Enums\EditorLanguages;
|
||||
use App\Enums\TablerIcon;
|
||||
use App\Filament\Admin\Resources\Eggs\EggResource;
|
||||
use App\Filament\Components\Forms\Fields\CopyFrom;
|
||||
use App\Filament\Components\Forms\Fields\MonacoEditor;
|
||||
use App\Models\EggVariable;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use Exception;
|
||||
use App\Traits\Filament\CanCustomizeTabs;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\ActionGroup;
|
||||
use Filament\Forms\Components\Checkbox;
|
||||
@@ -28,7 +29,6 @@ use Filament\Schemas\Components\Tabs\Tab;
|
||||
use Filament\Schemas\Components\Utilities\Get;
|
||||
use Filament\Schemas\Components\Utilities\Set;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Support\Enums\IconSize;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\Rules\Unique;
|
||||
@@ -37,6 +37,7 @@ class CreateEgg extends CreateRecord
|
||||
{
|
||||
use CanCustomizeHeaderActions;
|
||||
use CanCustomizeHeaderWidgets;
|
||||
use CanCustomizeTabs;
|
||||
|
||||
protected static string $resource = EggResource::class;
|
||||
|
||||
@@ -46,9 +47,12 @@ class CreateEgg extends CreateRecord
|
||||
protected function getDefaultHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
$this->getCreateFormAction()->formId('form')
|
||||
->iconButton()->iconSize(IconSize::ExtraLarge)
|
||||
->icon('tabler-file-plus'),
|
||||
Action::make('create')
|
||||
->hiddenLabel()
|
||||
->action('create')
|
||||
->keyBindings(['mod+s'])
|
||||
->tooltip(trans('filament-panels::resources/pages/create-record.form.actions.create.label'))
|
||||
->icon(TablerIcon::FilePlus),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -57,227 +61,233 @@ class CreateEgg extends CreateRecord
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
Tabs::make()->tabs([
|
||||
Tab::make('configuration')
|
||||
->label(trans('admin/egg.tabs.configuration'))
|
||||
->columns(['default' => 1, 'sm' => 1, 'md' => 2, 'lg' => 4])
|
||||
Tabs::make()
|
||||
->tabs($this->getTabs())
|
||||
->columnSpanFull()
|
||||
->persistTabInQueryString(),
|
||||
]);
|
||||
}
|
||||
|
||||
/** @return Tab[] */
|
||||
protected function getDefaultTabs(): array
|
||||
{
|
||||
return [
|
||||
Tab::make('configuration')
|
||||
->label(trans('admin/egg.tabs.configuration'))
|
||||
->columns(['default' => 1, 'sm' => 1, 'md' => 2, 'lg' => 4])
|
||||
->schema([
|
||||
TextInput::make('name')
|
||||
->label(trans('admin/egg.name'))
|
||||
->required()
|
||||
->maxLength(255)
|
||||
->columnSpan(['default' => 1, 'sm' => 1, 'md' => 2, 'lg' => 2])
|
||||
->helperText(trans('admin/egg.name_help')),
|
||||
TextInput::make('author')
|
||||
->label(trans('admin/egg.author'))
|
||||
->maxLength(255)
|
||||
->required()
|
||||
->email()
|
||||
->columnSpan(['default' => 1, 'sm' => 1, 'md' => 2, 'lg' => 2])
|
||||
->helperText(trans('admin/egg.author_help')),
|
||||
Textarea::make('description')
|
||||
->label(trans('admin/egg.description'))
|
||||
->rows(2)
|
||||
->columnSpanFull()
|
||||
->helperText(trans('admin/egg.description_help')),
|
||||
KeyValue::make('startup_commands')
|
||||
->label(trans('admin/egg.startup_commands'))
|
||||
->live()
|
||||
->columnSpanFull()
|
||||
->required()
|
||||
->reorderable()
|
||||
->addActionLabel(trans('admin/egg.add_startup'))
|
||||
->keyLabel(trans('admin/egg.startup_name'))
|
||||
->keyPlaceholder('Default')
|
||||
->valueLabel(trans('admin/egg.startup_command'))
|
||||
->valuePlaceholder('java -Xms128M -XX:MaxRAMPercentage=95.0 -jar {{SERVER_JARFILE}}')
|
||||
->helperText(trans('admin/egg.startup_help')),
|
||||
TagsInput::make('file_denylist')
|
||||
->label(trans('admin/egg.file_denylist'))
|
||||
->placeholder('denied-file.txt')
|
||||
->helperText(trans('admin/egg.file_denylist_help'))
|
||||
->columnSpan(['default' => 1, 'sm' => 1, 'md' => 2, 'lg' => 2]),
|
||||
TagsInput::make('features')
|
||||
->label(trans('admin/egg.features'))
|
||||
->columnSpan(['default' => 1, 'sm' => 1, 'md' => 1, 'lg' => 1]),
|
||||
Toggle::make('force_outgoing_ip')
|
||||
->label(trans('admin/egg.force_ip'))
|
||||
->hintIcon(TablerIcon::QuestionMark, trans('admin/egg.force_ip_help')),
|
||||
Hidden::make('script_is_privileged')
|
||||
->default(1),
|
||||
TagsInput::make('tags')
|
||||
->label(trans('admin/egg.tags'))
|
||||
->columnSpan(['default' => 1, 'sm' => 1, 'md' => 2, 'lg' => 2]),
|
||||
TextInput::make('update_url')
|
||||
->label(trans('admin/egg.update_url'))
|
||||
->hintIcon(TablerIcon::QuestionMark, trans('admin/egg.update_url_help'))
|
||||
->columnSpan(['default' => 1, 'sm' => 1, 'md' => 2, 'lg' => 2])
|
||||
->url(),
|
||||
KeyValue::make('docker_images')
|
||||
->label(trans('admin/egg.docker_images'))
|
||||
->live()
|
||||
->columnSpanFull()
|
||||
->required()
|
||||
->reorderable()
|
||||
->addActionLabel(trans('admin/egg.add_image'))
|
||||
->keyLabel(trans('admin/egg.docker_name'))
|
||||
->keyPlaceholder('Java 21')
|
||||
->valueLabel(trans('admin/egg.docker_uri'))
|
||||
->valuePlaceholder('ghcr.io/pelican-eggs/yolks:java_21')
|
||||
->helperText(trans('admin/egg.docker_help')),
|
||||
]),
|
||||
Tab::make('process_management')
|
||||
->label(trans('admin/egg.tabs.process_management'))
|
||||
->columns()
|
||||
->schema([
|
||||
CopyFrom::make('copy_process_from')
|
||||
->process(),
|
||||
TextInput::make('config_stop')
|
||||
->label(trans('admin/egg.stop_command'))
|
||||
->required()
|
||||
->maxLength(255)
|
||||
->helperText(trans('admin/egg.stop_command_help')),
|
||||
Textarea::make('config_startup')->rows(10)->json()
|
||||
->label(trans('admin/egg.start_config'))
|
||||
->default('{}')
|
||||
->helperText(trans('admin/egg.start_config_help')),
|
||||
Textarea::make('config_files')->rows(10)->json()
|
||||
->label(trans('admin/egg.config_files'))
|
||||
->default('{}')
|
||||
->helperText(trans('admin/egg.config_files_help')),
|
||||
Textarea::make('config_logs')->rows(10)->json()
|
||||
->label(trans('admin/egg.log_config'))
|
||||
->default('{}')
|
||||
->helperText(trans('admin/egg.log_config_help')),
|
||||
]),
|
||||
Tab::make('egg_variables')
|
||||
->label(trans('admin/egg.tabs.egg_variables'))
|
||||
->columnSpanFull()
|
||||
->schema([
|
||||
Repeater::make('variables')
|
||||
->hiddenLabel()
|
||||
->addActionLabel(trans('admin/egg.add_new_variable'))
|
||||
->grid()
|
||||
->relationship('variables')
|
||||
->orderColumn()
|
||||
->collapsible()->collapsed()
|
||||
->columnSpan(2)
|
||||
->defaultItems(0)
|
||||
->itemLabel(fn (array $state) => $state['name'])
|
||||
->mutateRelationshipDataBeforeCreateUsing(function (array $data): array {
|
||||
$data['default_value'] ??= '';
|
||||
$data['description'] ??= '';
|
||||
$data['rules'] ??= [];
|
||||
$data['user_viewable'] ??= '';
|
||||
$data['user_editable'] ??= '';
|
||||
|
||||
return $data;
|
||||
})
|
||||
->mutateRelationshipDataBeforeSaveUsing(function (array $data): array {
|
||||
$data['default_value'] ??= '';
|
||||
$data['description'] ??= '';
|
||||
$data['rules'] ??= [];
|
||||
$data['user_viewable'] ??= '';
|
||||
$data['user_editable'] ??= '';
|
||||
|
||||
return $data;
|
||||
})
|
||||
->schema([
|
||||
TextInput::make('name')
|
||||
->label(trans('admin/egg.name'))
|
||||
->required()
|
||||
->maxLength(255)
|
||||
->columnSpan(['default' => 1, 'sm' => 1, 'md' => 2, 'lg' => 2])
|
||||
->helperText(trans('admin/egg.name_help')),
|
||||
TextInput::make('author')
|
||||
->label(trans('admin/egg.author'))
|
||||
->maxLength(255)
|
||||
->required()
|
||||
->email()
|
||||
->columnSpan(['default' => 1, 'sm' => 1, 'md' => 2, 'lg' => 2])
|
||||
->helperText(trans('admin/egg.author_help')),
|
||||
Textarea::make('description')
|
||||
->label(trans('admin/egg.description'))
|
||||
->rows(2)
|
||||
->columnSpanFull()
|
||||
->helperText(trans('admin/egg.description_help')),
|
||||
KeyValue::make('startup_commands')
|
||||
->label(trans('admin/egg.startup_commands'))
|
||||
->live()
|
||||
->columnSpanFull()
|
||||
->required()
|
||||
->addActionLabel(trans('admin/egg.add_startup'))
|
||||
->keyLabel(trans('admin/egg.startup_name'))
|
||||
->keyPlaceholder('Default')
|
||||
->valueLabel(trans('admin/egg.startup_command'))
|
||||
->valuePlaceholder('java -Xms128M -XX:MaxRAMPercentage=95.0 -jar {{SERVER_JARFILE}}')
|
||||
->helperText(trans('admin/egg.startup_help')),
|
||||
TagsInput::make('file_denylist')
|
||||
->label(trans('admin/egg.file_denylist'))
|
||||
->placeholder('denied-file.txt')
|
||||
->helperText(trans('admin/egg.file_denylist_help'))
|
||||
->columnSpan(['default' => 1, 'sm' => 1, 'md' => 2, 'lg' => 2]),
|
||||
TagsInput::make('features')
|
||||
->label(trans('admin/egg.features'))
|
||||
->columnSpan(['default' => 1, 'sm' => 1, 'md' => 1, 'lg' => 1]),
|
||||
Toggle::make('force_outgoing_ip')
|
||||
->label(trans('admin/egg.force_ip'))
|
||||
->hintIcon('tabler-question-mark', trans('admin/egg.force_ip_help')),
|
||||
Hidden::make('script_is_privileged')
|
||||
->default(1),
|
||||
TagsInput::make('tags')
|
||||
->label(trans('admin/egg.tags'))
|
||||
->columnSpan(['default' => 1, 'sm' => 1, 'md' => 2, 'lg' => 2]),
|
||||
TextInput::make('update_url')
|
||||
->label(trans('admin/egg.update_url'))
|
||||
->hintIcon('tabler-question-mark', trans('admin/egg.update_url_help'))
|
||||
->columnSpan(['default' => 1, 'sm' => 1, 'md' => 2, 'lg' => 2])
|
||||
->url(),
|
||||
KeyValue::make('docker_images')
|
||||
->label(trans('admin/egg.docker_images'))
|
||||
->live()
|
||||
->columnSpanFull()
|
||||
->required()
|
||||
->addActionLabel(trans('admin/egg.add_image'))
|
||||
->keyLabel(trans('admin/egg.docker_name'))
|
||||
->keyPlaceholder('Java 21')
|
||||
->valueLabel(trans('admin/egg.docker_uri'))
|
||||
->valuePlaceholder('ghcr.io/pelican-eggs/yolks:java_21')
|
||||
->helperText(trans('admin/egg.docker_help')),
|
||||
]),
|
||||
|
||||
Tab::make('process_management')
|
||||
->label(trans('admin/egg.tabs.process_management'))
|
||||
->columns()
|
||||
->schema([
|
||||
CopyFrom::make('copy_process_from')
|
||||
->process(),
|
||||
TextInput::make('config_stop')
|
||||
->label(trans('admin/egg.stop_command'))
|
||||
->required()
|
||||
->debounce(750)
|
||||
->maxLength(255)
|
||||
->helperText(trans('admin/egg.stop_command_help')),
|
||||
Textarea::make('config_startup')->rows(10)->json()
|
||||
->label(trans('admin/egg.start_config'))
|
||||
->default('{}')
|
||||
->helperText(trans('admin/egg.start_config_help')),
|
||||
Textarea::make('config_files')->rows(10)->json()
|
||||
->label(trans('admin/egg.config_files'))
|
||||
->default('{}')
|
||||
->helperText(trans('admin/egg.config_files_help')),
|
||||
Textarea::make('config_logs')->rows(10)->json()
|
||||
->label(trans('admin/egg.log_config'))
|
||||
->default('{}')
|
||||
->helperText(trans('admin/egg.log_config_help')),
|
||||
]),
|
||||
Tab::make('egg_variables')
|
||||
->label(trans('admin/egg.tabs.egg_variables'))
|
||||
->columnSpanFull()
|
||||
->schema([
|
||||
Repeater::make('variables')
|
||||
->hiddenLabel()
|
||||
->addActionLabel(trans('admin/egg.add_new_variable'))
|
||||
->grid()
|
||||
->relationship('variables')
|
||||
->reorderable()->orderColumn()
|
||||
->collapsible()->collapsed()
|
||||
->columnSpan(2)
|
||||
->defaultItems(0)
|
||||
->itemLabel(fn (array $state) => $state['name'])
|
||||
->mutateRelationshipDataBeforeCreateUsing(function (array $data): array {
|
||||
$data['default_value'] ??= '';
|
||||
$data['description'] ??= '';
|
||||
$data['rules'] ??= [];
|
||||
$data['user_viewable'] ??= '';
|
||||
$data['user_editable'] ??= '';
|
||||
|
||||
return $data;
|
||||
})
|
||||
->mutateRelationshipDataBeforeSaveUsing(function (array $data): array {
|
||||
$data['default_value'] ??= '';
|
||||
$data['description'] ??= '';
|
||||
$data['rules'] ??= [];
|
||||
$data['user_viewable'] ??= '';
|
||||
$data['user_editable'] ??= '';
|
||||
|
||||
return $data;
|
||||
})
|
||||
->schema([
|
||||
TextInput::make('name')
|
||||
->label(trans('admin/egg.name'))
|
||||
->live()
|
||||
->debounce(750)
|
||||
->maxLength(255)
|
||||
->columnSpanFull()
|
||||
->afterStateUpdated(fn (Set $set, $state) => $set('env_variable', str($state)->trim()->snake()->upper()->toString()))
|
||||
->unique(modifyRuleUsing: fn (Unique $rule, Get $get) => $rule->where('egg_id', $get('../../id')))
|
||||
->validationMessages([
|
||||
'unique' => trans('admin/egg.error_unique'),
|
||||
])
|
||||
->required(),
|
||||
Textarea::make('description')->label(trans('admin/egg.description'))->columnSpanFull(),
|
||||
TextInput::make('env_variable')
|
||||
->label(trans('admin/egg.environment_variable'))
|
||||
->maxLength(255)
|
||||
->prefix('{{')
|
||||
->suffix('}}')
|
||||
->hintIcon('tabler-code', fn ($state) => "{{{$state}}}")
|
||||
->unique(modifyRuleUsing: fn (Unique $rule, Get $get) => $rule->where('egg_id', $get('../../id')))
|
||||
->rules(EggVariable::getRulesForField('env_variable'))
|
||||
->validationMessages([
|
||||
'unique' => trans('admin/egg.error_unique'),
|
||||
'required' => trans('admin/egg.error_required'),
|
||||
'*' => trans('admin/egg.error_reserved'),
|
||||
])
|
||||
->required(),
|
||||
TextInput::make('default_value')->label(trans('admin/egg.default_value')),
|
||||
Fieldset::make(trans('admin/egg.user_permissions'))
|
||||
->schema([
|
||||
Checkbox::make('user_viewable')->label(trans('admin/egg.viewable')),
|
||||
Checkbox::make('user_editable')->label(trans('admin/egg.editable')),
|
||||
]),
|
||||
TagsInput::make('rules')
|
||||
->label(trans('admin/egg.rules'))
|
||||
->columnSpanFull()
|
||||
->reorderable()
|
||||
->suggestions([
|
||||
'required',
|
||||
'nullable',
|
||||
'string',
|
||||
'integer',
|
||||
'numeric',
|
||||
'boolean',
|
||||
'alpha',
|
||||
'alpha_dash',
|
||||
'alpha_num',
|
||||
'url',
|
||||
'email',
|
||||
'regex:',
|
||||
'min:',
|
||||
'max:',
|
||||
'between:',
|
||||
'between:1024,65535',
|
||||
'in:',
|
||||
'in:true,false',
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
Tab::make('install_script')
|
||||
->label(trans('admin/egg.tabs.install_script'))
|
||||
->columns(3)
|
||||
->schema([
|
||||
CopyFrom::make('copy_script_from')
|
||||
->script(),
|
||||
TextInput::make('script_container')
|
||||
->label(trans('admin/egg.script_container'))
|
||||
->required()
|
||||
->maxLength(255)
|
||||
->default('ghcr.io/pelican-eggs/installers:debian'),
|
||||
Select::make('script_entry')
|
||||
->label(trans('admin/egg.script_entry'))
|
||||
->selectablePlaceholder(false)
|
||||
->default('bash')
|
||||
->options([
|
||||
'bash' => 'bash',
|
||||
'ash' => 'ash',
|
||||
'/bin/bash' => '/bin/bash',
|
||||
->columnSpanFull()
|
||||
->afterStateUpdated(fn (Set $set, $state) => $set('env_variable', str($state)->trim()->snake()->upper()->toString()))
|
||||
->unique(modifyRuleUsing: fn (Unique $rule, Get $get) => $rule->where('egg_id', $get('../../id')))
|
||||
->validationMessages([
|
||||
'unique' => trans('admin/egg.error_unique'),
|
||||
])
|
||||
->required(),
|
||||
MonacoEditor::make('script_install')
|
||||
->label(trans('admin/egg.script_install'))
|
||||
->language(EditorLanguages::shell)
|
||||
Textarea::make('description')->label(trans('admin/egg.description'))->columnSpanFull(),
|
||||
TextInput::make('env_variable')
|
||||
->label(trans('admin/egg.environment_variable'))
|
||||
->maxLength(255)
|
||||
->prefix('{{')
|
||||
->suffix('}}')
|
||||
->hintIcon(TablerIcon::Code, fn ($state) => "{{{$state}}}")
|
||||
->unique(modifyRuleUsing: fn (Unique $rule, Get $get) => $rule->where('egg_id', $get('../../id')))
|
||||
->rules(EggVariable::getRulesForField('env_variable'))
|
||||
->validationMessages([
|
||||
'unique' => trans('admin/egg.error_unique'),
|
||||
'required' => trans('admin/egg.error_required'),
|
||||
'*' => trans('admin/egg.error_reserved'),
|
||||
])
|
||||
->required(),
|
||||
TextInput::make('default_value')->label(trans('admin/egg.default_value')),
|
||||
Fieldset::make(trans('admin/egg.user_permissions'))
|
||||
->schema([
|
||||
Checkbox::make('user_viewable')->label(trans('admin/egg.viewable')),
|
||||
Checkbox::make('user_editable')->label(trans('admin/egg.editable')),
|
||||
]),
|
||||
TagsInput::make('rules')
|
||||
->label(trans('admin/egg.rules'))
|
||||
->columnSpanFull()
|
||||
->lazy(),
|
||||
->reorderable()
|
||||
->suggestions([
|
||||
'required',
|
||||
'nullable',
|
||||
'string',
|
||||
'integer',
|
||||
'numeric',
|
||||
'boolean',
|
||||
'alpha',
|
||||
'alpha_dash',
|
||||
'alpha_num',
|
||||
'url',
|
||||
'email',
|
||||
'regex:',
|
||||
'min:',
|
||||
'max:',
|
||||
'between:',
|
||||
'between:1024,65535',
|
||||
'in:',
|
||||
'in:true,false',
|
||||
]),
|
||||
]),
|
||||
|
||||
])->columnSpanFull()->persistTabInQueryString(),
|
||||
]);
|
||||
]),
|
||||
Tab::make('install_script')
|
||||
->label(trans('admin/egg.tabs.install_script'))
|
||||
->columns(3)
|
||||
->schema([
|
||||
CopyFrom::make('copy_script_from')
|
||||
->script(),
|
||||
TextInput::make('script_container')
|
||||
->label(trans('admin/egg.script_container'))
|
||||
->required()
|
||||
->maxLength(255)
|
||||
->default('ghcr.io/pelican-eggs/installers:debian'),
|
||||
Select::make('script_entry')
|
||||
->label(trans('admin/egg.script_entry'))
|
||||
->selectablePlaceholder(false)
|
||||
->default('bash')
|
||||
->options([
|
||||
'bash' => 'bash',
|
||||
'ash' => 'ash',
|
||||
'/bin/bash' => '/bin/bash',
|
||||
])
|
||||
->required(),
|
||||
MonacoEditor::make('script_install')
|
||||
->label(trans('admin/egg.script_install'))
|
||||
->language(EditorLanguages::shell)
|
||||
->columnSpanFull()
|
||||
->lazy(),
|
||||
]),
|
||||
];
|
||||
}
|
||||
|
||||
protected function handleRecordCreation(array $data): Model
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user