mirror of
https://github.com/pelican-dev/panel.git
synced 2026-02-07 15:39:43 +03:00
Compare commits
319 Commits
lance/clou
...
lance/1492
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
61a9cbbc6c | ||
|
|
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 | ||
|
|
242a75bf3d | ||
|
|
2ab4c81e2a | ||
|
|
5a47948a93 | ||
|
|
9d1e7f510f | ||
|
|
be55e75109 | ||
|
|
8b5f33ee71 | ||
|
|
014e866d0e | ||
|
|
4a1ecb1adc | ||
|
|
e2529ab436 | ||
|
|
cd3f3a97ac | ||
|
|
2f5790b121 | ||
|
|
59f0fe1959 | ||
|
|
fdd9faaaa3 | ||
|
|
9449d78144 | ||
|
|
a391d21043 | ||
|
|
b13fcfd644 | ||
|
|
760aaf9bfb | ||
|
|
1ab4ddb07c | ||
|
|
f278041bc0 | ||
|
|
cdc928a15b | ||
|
|
3939c409c1 | ||
|
|
091ca5447a | ||
|
|
57c4172c74 | ||
|
|
dfd6dbfe26 | ||
|
|
b4f331e4b2 | ||
|
|
7a95712ed0 | ||
|
|
b6aeb954c4 | ||
|
|
7c0d53c796 | ||
|
|
71bd267166 | ||
|
|
25d8adbcc6 | ||
|
|
27b896c6d2 | ||
|
|
bda2f9a699 | ||
|
|
04375439d7 | ||
|
|
0fe8917668 | ||
|
|
c312ef493f | ||
|
|
6c02f9a663 | ||
|
|
2dd6e3d4fc | ||
|
|
575e5bdb0d | ||
|
|
efa8eef57c | ||
|
|
d16e7dd876 | ||
|
|
897b95ec13 | ||
|
|
97f5a0f20b | ||
|
|
d0af45a0c7 | ||
|
|
78ab098d02 | ||
|
|
cdccca8fa2 | ||
|
|
bb33bcca4f | ||
|
|
611b8649e0 | ||
|
|
b1b723485f | ||
|
|
25c8ff3f1f | ||
|
|
07763d912b | ||
|
|
65bb99e2b0 | ||
|
|
a195b56f93 | ||
|
|
d78c977d75 | ||
|
|
5e25ea4a43 | ||
|
|
886836c60a | ||
|
|
f575e3edfa | ||
|
|
1a66b3fab4 | ||
|
|
0f1efcfd15 | ||
|
|
3f89c6ddd8 | ||
|
|
20cb7850ef | ||
|
|
108dad09fb | ||
|
|
445c9364bc | ||
|
|
acec117b1e | ||
|
|
89199dfbe5 | ||
|
|
216a3484f1 | ||
|
|
5c3b0919aa | ||
|
|
f4ee33fa4f | ||
|
|
d8368c4cec | ||
|
|
aa35d7d001 | ||
|
|
3c25b43b46 | ||
|
|
0891db5342 | ||
|
|
172436e012 | ||
|
|
2b5403a4da | ||
|
|
a30c45fbbe | ||
|
|
b06df23823 | ||
|
|
1ff965611e | ||
|
|
cec141889a | ||
|
|
6ed84b5584 | ||
|
|
49f24e37b6 | ||
|
|
e0c4e47a6c | ||
|
|
4bda7cba75 | ||
|
|
852f7beb39 | ||
|
|
d61583cd7b | ||
|
|
21f9f259d0 | ||
|
|
b2aff5445b | ||
|
|
1f26750a2a | ||
|
|
6d83c6d908 | ||
|
|
574a391e73 | ||
|
|
605fcbe61a | ||
|
|
0214b127e4 | ||
|
|
e6aa76ef2c | ||
|
|
d38075e3cb | ||
|
|
0fec6adc3e | ||
|
|
5e3c22ea5e | ||
|
|
d1a808a746 | ||
|
|
3bcdeea800 | ||
|
|
e6bd6e416f | ||
|
|
8e006ac32d | ||
|
|
430f28a847 | ||
|
|
1a4fa5e67a | ||
|
|
a65469b33b | ||
|
|
d587cf3ee5 | ||
|
|
2cd9fa2cde | ||
|
|
d735e858a2 | ||
|
|
317fa46894 | ||
|
|
e589f972fb | ||
|
|
266e3779d5 | ||
|
|
4652680a7b | ||
|
|
e99f7179c6 | ||
|
|
1f56b8e114 | ||
|
|
574e03a986 | ||
|
|
05f3422dda | ||
|
|
dbe4bdd62d | ||
|
|
f6710dbbe4 | ||
|
|
e4f807b297 | ||
|
|
cd965678b7 | ||
|
|
a58ae874f3 | ||
|
|
432fb8a514 | ||
|
|
bb02ec4c6c | ||
|
|
69b669e345 | ||
|
|
80993f38a9 | ||
|
|
19103b16b8 | ||
|
|
246997754e | ||
|
|
df75dbe2ad | ||
|
|
f02b58c320 | ||
|
|
8aa0fc7fc2 | ||
|
|
2fc30e14fd | ||
|
|
ec5fd3262a | ||
|
|
81178f81b4 | ||
|
|
5373f1e30a | ||
|
|
9f35f1c3ee | ||
|
|
a5858a6d9b | ||
|
|
e3b3c92dcb | ||
|
|
42c84c2df5 | ||
|
|
4792542f20 | ||
|
|
bb40a5273f | ||
|
|
e5c24fe8b6 | ||
|
|
c10280af4b | ||
|
|
6db1d82738 | ||
|
|
68f8244298 | ||
|
|
ce393af7a6 | ||
|
|
932809fec5 | ||
|
|
3d2390dbcc | ||
|
|
d5d50d4150 | ||
|
|
cba8717188 | ||
|
|
df4543a079 | ||
|
|
8dc99e6390 | ||
|
|
8f1ec20e96 | ||
|
|
61dcb9a3ba | ||
|
|
0e34886d7e | ||
|
|
806820592f | ||
|
|
1900c04b71 | ||
|
|
32eb1abd4a | ||
|
|
47557021fd | ||
|
|
2ef81eae1a | ||
|
|
420730ba1f | ||
|
|
925ab26fb4 | ||
|
|
2952e22619 | ||
|
|
079eaed010 | ||
|
|
6671d45651 | ||
|
|
3543b4773a | ||
|
|
02f788a659 | ||
|
|
7ace3978d8 | ||
|
|
8f277aaca0 | ||
|
|
76451fa0ad | ||
|
|
0104a08ba4 | ||
|
|
5eff006843 | ||
|
|
a8241bf9f3 | ||
|
|
4aae2562ea | ||
|
|
42db5b328a | ||
|
|
bc4dfb3e92 | ||
|
|
3b9c81534f | ||
|
|
f31aa78f6f | ||
|
|
b5ebd544f4 | ||
|
|
c77a37ec89 | ||
|
|
4d78e5dcd1 | ||
|
|
15075b6ab8 | ||
|
|
a8f233e204 | ||
|
|
795cad43b9 | ||
|
|
46934d7a85 | ||
|
|
06067f375c | ||
|
|
d1df53c683 | ||
|
|
b03d2cf919 | ||
|
|
27a8423f55 | ||
|
|
ad70934430 | ||
|
|
900f8d0fe1 | ||
|
|
6a4ac515a7 | ||
|
|
7c315ac995 | ||
|
|
49e9440e0f | ||
|
|
02e3e43f1e | ||
|
|
8eddef6f04 | ||
|
|
d2f1936bbf | ||
|
|
36863f94c0 | ||
|
|
75863c50d1 | ||
|
|
ec0727b406 | ||
|
|
5b2e9d94ca | ||
|
|
8840d109ef | ||
|
|
71225bd2dc | ||
|
|
bab8ec6e18 | ||
|
|
d307a2095b | ||
|
|
a777f4e0ff | ||
|
|
86a71afc6c | ||
|
|
88943563c7 | ||
|
|
20071a64fa | ||
|
|
d0d3418e03 | ||
|
|
083e3dc62a | ||
|
|
d7e60f2456 | ||
|
|
38e746240d | ||
|
|
986063dce4 | ||
|
|
71d0326cb2 | ||
|
|
62ca53eeaf | ||
|
|
9f2305f351 | ||
|
|
340d1b543c | ||
|
|
61098b11f2 | ||
|
|
4d03d6b948 | ||
|
|
1f67054777 | ||
|
|
4a9814f16c | ||
|
|
e0697d3288 | ||
|
|
d165da20ec | ||
|
|
ae27b179fe | ||
|
|
1113ffe0f7 | ||
|
|
5531bc0ba1 | ||
|
|
a3819122db | ||
|
|
c5528a61f3 | ||
|
|
5a7c6ac6e5 | ||
|
|
5e8cccef19 | ||
|
|
0ccb248d91 | ||
|
|
514d961c24 | ||
|
|
f8e802afcd | ||
|
|
556551b4f3 | ||
|
|
23ddded61e | ||
|
|
c5aa8a3980 | ||
|
|
21ac75efae | ||
|
|
9655700cde | ||
|
|
c9b7e979c0 | ||
|
|
77a3b0640d | ||
|
|
de4cb38766 | ||
|
|
74bd7f9991 | ||
|
|
ba7f814300 | ||
|
|
cdcd1c521e | ||
|
|
4d0aabe91e | ||
|
|
68f72b9b4d | ||
|
|
dca37ccc95 | ||
|
|
6a088d0c4f | ||
|
|
7731f16b0f | ||
|
|
9a1e7de4ae | ||
|
|
c61b6920b9 | ||
|
|
6107524522 | ||
|
|
57a13a2701 | ||
|
|
4dd414ad87 | ||
|
|
0156ac1509 | ||
|
|
387471716b | ||
|
|
1dc5ec027e | ||
|
|
b05eabfdb0 | ||
|
|
3039c1c698 |
7
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
7
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
@@ -64,10 +64,9 @@ body:
|
||||
label: Error Logs
|
||||
description: |
|
||||
Run the following command to collect logs on your system.
|
||||
|
||||
Wings: `sudo wings diagnostics`
|
||||
Panel: `tail -n 150 /var/www/pelican/storage/logs/laravel-$(date +%F).log | curl -X POST -F 'c=@-' paste.pelistuff.com`
|
||||
placeholder: "https://pelipaste.com/a1h6z"
|
||||
Wings: `sudo wings diagnostics --hastebin-url=https://logs.pelican.dev`
|
||||
Panel: `tail -n 300 /var/www/pelican/storage/logs/laravel-$(date +%F).log | curl --data-binary @- https://logs.pelican.dev`
|
||||
placeholder: "https://logs.pelican.dev/c17f750e"
|
||||
render: bash
|
||||
validations:
|
||||
required: false
|
||||
|
||||
4
.github/workflows/build.yaml
vendored
4
.github/workflows/build.yaml
vendored
@@ -11,9 +11,9 @@ jobs:
|
||||
name: UI
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
fail-fast: true
|
||||
matrix:
|
||||
node-version: [18, 20]
|
||||
node-version: [20, 22]
|
||||
steps:
|
||||
- name: Code Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
340
.github/workflows/ci.yaml
vendored
340
.github/workflows/ci.yaml
vendored
@@ -6,174 +6,30 @@ on:
|
||||
- main
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
APP_ENV: testing
|
||||
APP_DEBUG: "false"
|
||||
APP_KEY: ThisIsARandomStringForTests12345
|
||||
APP_TIMEZONE: UTC
|
||||
APP_URL: http://localhost/
|
||||
CACHE_DRIVER: array
|
||||
MAIL_MAILER: array
|
||||
SESSION_DRIVER: array
|
||||
QUEUE_CONNECTION: sync
|
||||
GUZZLE_TIMEOUT: 60
|
||||
GUZZLE_CONNECT_TIMEOUT: 60
|
||||
|
||||
jobs:
|
||||
mysql:
|
||||
name: MySQL
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
php: [8.2, 8.3, 8.4]
|
||||
database: ["mysql:8"]
|
||||
services:
|
||||
database:
|
||||
image: ${{ matrix.database }}
|
||||
env:
|
||||
MYSQL_ALLOW_EMPTY_PASSWORD: yes
|
||||
MYSQL_DATABASE: testing
|
||||
ports:
|
||||
- 3306
|
||||
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
|
||||
env:
|
||||
APP_ENV: testing
|
||||
APP_DEBUG: "false"
|
||||
APP_KEY: ThisIsARandomStringForTests12345
|
||||
APP_TIMEZONE: UTC
|
||||
APP_URL: http://localhost/
|
||||
CACHE_DRIVER: array
|
||||
MAIL_MAILER: array
|
||||
SESSION_DRIVER: array
|
||||
QUEUE_CONNECTION: sync
|
||||
DB_CONNECTION: mysql
|
||||
DB_HOST: 127.0.0.1
|
||||
DB_DATABASE: testing
|
||||
DB_USERNAME: root
|
||||
GUZZLE_TIMEOUT: 60
|
||||
GUZZLE_CONNECT_TIMEOUT: 60
|
||||
steps:
|
||||
- name: Code Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Get cache directory
|
||||
id: composer-cache
|
||||
run: |
|
||||
echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ steps.composer-cache.outputs.dir }}
|
||||
key: ${{ runner.os }}-composer-${{ matrix.php }}-${{ hashFiles('**/composer.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-composer-${{ matrix.php }}-
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php }}
|
||||
extensions: bcmath, curl, gd, mbstring, mysql, openssl, pdo, tokenizer, xml, zip
|
||||
tools: composer:v2
|
||||
coverage: none
|
||||
|
||||
- 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: Integration tests
|
||||
run: vendor/bin/pest tests/Integration
|
||||
env:
|
||||
DB_PORT: ${{ job.services.database.ports[3306] }}
|
||||
DB_USERNAME: root
|
||||
|
||||
mariadb:
|
||||
name: MariaDB
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
php: [8.2, 8.3, 8.4]
|
||||
database: ["mariadb:10.6", "mariadb:10.11", "mariadb:11.4"]
|
||||
services:
|
||||
database:
|
||||
image: ${{ matrix.database }}
|
||||
env:
|
||||
MYSQL_ALLOW_EMPTY_PASSWORD: yes
|
||||
MYSQL_DATABASE: testing
|
||||
ports:
|
||||
- 3306
|
||||
options: --health-cmd="mariadb-admin ping || mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
|
||||
env:
|
||||
APP_ENV: testing
|
||||
APP_DEBUG: "false"
|
||||
APP_KEY: ThisIsARandomStringForTests12345
|
||||
APP_TIMEZONE: UTC
|
||||
APP_URL: http://localhost/
|
||||
CACHE_DRIVER: array
|
||||
MAIL_MAILER: array
|
||||
SESSION_DRIVER: array
|
||||
QUEUE_CONNECTION: sync
|
||||
DB_CONNECTION: mariadb
|
||||
DB_HOST: 127.0.0.1
|
||||
DB_DATABASE: testing
|
||||
DB_USERNAME: root
|
||||
GUZZLE_TIMEOUT: 60
|
||||
GUZZLE_CONNECT_TIMEOUT: 60
|
||||
steps:
|
||||
- name: Code Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Get cache directory
|
||||
id: composer-cache
|
||||
run: |
|
||||
echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ steps.composer-cache.outputs.dir }}
|
||||
key: ${{ runner.os }}-composer-${{ matrix.php }}-${{ hashFiles('**/composer.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-composer-${{ matrix.php }}-
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php }}
|
||||
extensions: bcmath, curl, gd, mbstring, mysql, openssl, pdo, tokenizer, xml, zip
|
||||
tools: composer:v2
|
||||
coverage: none
|
||||
|
||||
- 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: Integration tests
|
||||
run: vendor/bin/pest tests/Integration
|
||||
env:
|
||||
DB_PORT: ${{ job.services.database.ports[3306] }}
|
||||
DB_USERNAME: root
|
||||
|
||||
sqlite:
|
||||
name: SQLite
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
fail-fast: true
|
||||
matrix:
|
||||
php: [8.2, 8.3, 8.4]
|
||||
php: [8.2, 8.3, 8.4, 8.5]
|
||||
env:
|
||||
APP_ENV: testing
|
||||
APP_DEBUG: "false"
|
||||
APP_KEY: ThisIsARandomStringForTests12345
|
||||
APP_TIMEZONE: UTC
|
||||
APP_URL: http://localhost/
|
||||
CACHE_DRIVER: array
|
||||
MAIL_MAILER: array
|
||||
SESSION_DRIVER: array
|
||||
QUEUE_CONNECTION: sync
|
||||
DB_CONNECTION: sqlite
|
||||
DB_DATABASE: testing.sqlite
|
||||
GUZZLE_TIMEOUT: 60
|
||||
GUZZLE_CONNECT_TIMEOUT: 60
|
||||
steps:
|
||||
- name: Code Checkout
|
||||
uses: actions/checkout@v4
|
||||
@@ -205,22 +61,161 @@ 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
|
||||
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
|
||||
|
||||
mysql:
|
||||
name: MySQL
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
php: [8.2, 8.3, 8.4, 8.5]
|
||||
database: ["mysql:8"]
|
||||
services:
|
||||
database:
|
||||
image: ${{ matrix.database }}
|
||||
env:
|
||||
MYSQL_ALLOW_EMPTY_PASSWORD: yes
|
||||
MYSQL_DATABASE: testing
|
||||
ports:
|
||||
- 3306
|
||||
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
|
||||
env:
|
||||
DB_CONNECTION: mysql
|
||||
DB_HOST: 127.0.0.1
|
||||
DB_DATABASE: testing
|
||||
DB_USERNAME: root
|
||||
steps:
|
||||
- name: Code Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Get cache directory
|
||||
id: composer-cache
|
||||
run: |
|
||||
echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ steps.composer-cache.outputs.dir }}
|
||||
key: ${{ runner.os }}-composer-${{ matrix.php }}-${{ hashFiles('**/composer.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-composer-${{ matrix.php }}-
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php }}
|
||||
extensions: bcmath, curl, gd, mbstring, mysql, openssl, pdo, tokenizer, xml, zip
|
||||
tools: composer:v2
|
||||
coverage: none
|
||||
|
||||
- name: Install dependencies
|
||||
run: composer install --no-interaction --no-suggest --no-progress --no-scripts
|
||||
|
||||
- name: Run Migrations
|
||||
run: php artisan migrate --force --seed
|
||||
env:
|
||||
DB_PORT: ${{ job.services.database.ports[3306] }}
|
||||
DB_USERNAME: root
|
||||
|
||||
- name: Unit tests
|
||||
run: vendor/bin/pest tests/Unit --parallel
|
||||
env:
|
||||
DB_HOST: UNIT_NO_DB
|
||||
SKIP_MIGRATIONS: true
|
||||
|
||||
- name: Integration tests
|
||||
run: vendor/bin/pest tests/Integration --parallel
|
||||
env:
|
||||
DB_PORT: ${{ job.services.database.ports[3306] }}
|
||||
DB_USERNAME: root
|
||||
|
||||
mariadb:
|
||||
name: MariaDB
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
php: [8.2, 8.3, 8.4, 8.5]
|
||||
database: ["mariadb:10.6", "mariadb:10.11", "mariadb:11.4"]
|
||||
services:
|
||||
database:
|
||||
image: ${{ matrix.database }}
|
||||
env:
|
||||
MYSQL_ALLOW_EMPTY_PASSWORD: yes
|
||||
MYSQL_DATABASE: testing
|
||||
ports:
|
||||
- 3306
|
||||
options: --health-cmd="mariadb-admin ping || mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
|
||||
env:
|
||||
DB_CONNECTION: mariadb
|
||||
DB_HOST: 127.0.0.1
|
||||
DB_DATABASE: testing
|
||||
DB_USERNAME: root
|
||||
steps:
|
||||
- name: Code Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Get cache directory
|
||||
id: composer-cache
|
||||
run: |
|
||||
echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ steps.composer-cache.outputs.dir }}
|
||||
key: ${{ runner.os }}-composer-${{ matrix.php }}-${{ hashFiles('**/composer.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-composer-${{ matrix.php }}-
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php }}
|
||||
extensions: bcmath, curl, gd, mbstring, mysql, openssl, pdo, tokenizer, xml, zip
|
||||
tools: composer:v2
|
||||
coverage: none
|
||||
|
||||
- name: Install dependencies
|
||||
run: composer install --no-interaction --no-suggest --no-progress --no-scripts
|
||||
|
||||
- name: Run Migrations
|
||||
run: php artisan migrate --force --seed
|
||||
env:
|
||||
DB_PORT: ${{ job.services.database.ports[3306] }}
|
||||
DB_USERNAME: root
|
||||
|
||||
- name: Unit tests
|
||||
run: vendor/bin/pest tests/Unit --parallel
|
||||
env:
|
||||
DB_HOST: UNIT_NO_DB
|
||||
SKIP_MIGRATIONS: true
|
||||
|
||||
- name: Integration tests
|
||||
run: vendor/bin/pest tests/Integration --parallel
|
||||
env:
|
||||
DB_PORT: ${{ job.services.database.ports[3306] }}
|
||||
DB_USERNAME: root
|
||||
|
||||
postgresql:
|
||||
name: PostgreSQL
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
fail-fast: true
|
||||
matrix:
|
||||
php: [8.2, 8.3, 8.4]
|
||||
php: [8.2, 8.3, 8.4, 8.5]
|
||||
database: ["postgres:14"]
|
||||
services:
|
||||
database:
|
||||
@@ -238,22 +233,11 @@ jobs:
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
env:
|
||||
APP_ENV: testing
|
||||
APP_DEBUG: "false"
|
||||
APP_KEY: ThisIsARandomStringForTests12345
|
||||
APP_TIMEZONE: UTC
|
||||
APP_URL: http://localhost/
|
||||
CACHE_DRIVER: array
|
||||
MAIL_MAILER: array
|
||||
SESSION_DRIVER: array
|
||||
QUEUE_CONNECTION: sync
|
||||
DB_CONNECTION: pgsql
|
||||
DB_HOST: 127.0.0.1
|
||||
DB_DATABASE: testing
|
||||
DB_USERNAME: postgres
|
||||
DB_PASSWORD: postgres
|
||||
GUZZLE_TIMEOUT: 60
|
||||
GUZZLE_CONNECT_TIMEOUT: 60
|
||||
steps:
|
||||
- name: Code Checkout
|
||||
uses: actions/checkout@v4
|
||||
@@ -269,6 +253,7 @@ jobs:
|
||||
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
|
||||
@@ -281,11 +266,14 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: composer install --no-interaction --no-suggest --no-progress --no-scripts
|
||||
|
||||
- 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
|
||||
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
|
||||
|
||||
7
.github/workflows/docker-publish.yml
vendored
7
.github/workflows/docker-publish.yml
vendored
@@ -66,8 +66,6 @@ jobs:
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
strategy:
|
||||
fail-fast: false
|
||||
# Start a temp local registry because workflow can not pull from localy loaded images
|
||||
services:
|
||||
registry:
|
||||
@@ -134,6 +132,11 @@ jobs:
|
||||
docker push localhost:5000/base-php:arm64
|
||||
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'"
|
||||
run: |
|
||||
sed -i "s/'version' => 'canary',/'version' => '${{ steps.build_info.outputs.version_tag }}',/" config/app.php
|
||||
|
||||
- name: Build and Push (tag)
|
||||
uses: docker/build-push-action@v6
|
||||
if: "github.event_name == 'release' && github.event.action == 'published'"
|
||||
|
||||
8
.github/workflows/lint.yaml
vendored
8
.github/workflows/lint.yaml
vendored
@@ -3,7 +3,7 @@ name: Lint
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- '**'
|
||||
- "**"
|
||||
|
||||
jobs:
|
||||
pint:
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: "8.3"
|
||||
php-version: "8.4"
|
||||
extensions: bcmath, curl, gd, mbstring, mysql, openssl, pdo, tokenizer, xml, zip
|
||||
tools: composer:v2
|
||||
coverage: none
|
||||
@@ -33,9 +33,9 @@ jobs:
|
||||
name: PHPStan
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
fail-fast: true
|
||||
matrix:
|
||||
php: [ 8.2, 8.3, 8.4 ]
|
||||
php: [8.2, 8.3, 8.4, 8.5]
|
||||
steps:
|
||||
- name: Code Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -21,9 +21,9 @@ yarn-error.log
|
||||
/.idea
|
||||
/.nova
|
||||
/.vscode
|
||||
/.ddev
|
||||
|
||||
public/assets/manifest.json
|
||||
/database/*.sqlite*
|
||||
filament-monaco-editor/
|
||||
_ide_helper*
|
||||
/.phpstorm.meta.php
|
||||
|
||||
57
Dockerfile
57
Dockerfile
@@ -2,7 +2,7 @@
|
||||
# Pelican Production Dockerfile
|
||||
|
||||
##
|
||||
# If you want to build this locally you want to run `docker build -f Dockerfile.dev`
|
||||
# If you want to build this locally you want to run `docker build -f Dockerfile.dev .`
|
||||
##
|
||||
|
||||
# ================================
|
||||
@@ -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,41 +62,40 @@ FROM --platform=$TARGETOS/$TARGETARCH localhost:5000/base-php:$TARGETARCH AS fin
|
||||
|
||||
WORKDIR /var/www/html
|
||||
|
||||
# Install additional required libraries
|
||||
RUN apk update && apk add --no-cache \
|
||||
caddy ca-certificates supervisor supercronic
|
||||
RUN apk add --no-cache \
|
||||
# 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
|
||||
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 /var/www/html/storage/app/public /var/run/supervisord /etc/supercronic \
|
||||
# Symlinks for env, database, and avatars
|
||||
&& 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 \
|
||||
# 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
|
||||
# Create and remove directories
|
||||
RUN mkdir -p /pelican-data/storage /pelican-data/plugins /var/run/supervisord \
|
||||
&& rm -rf /var/www/html/plugins \
|
||||
# Symlinks for env, database, storage, and plugins
|
||||
&& 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 \
|
||||
&& ln -s /pelican-data/plugins /var/www/html \
|
||||
# Allow www-data write permissions where necessary
|
||||
&& chown -R www-data: /pelican-data .env ./storage ./plugins ./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/
|
||||
|
||||
# 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 ./docker/entrypoint.sh
|
||||
COPY docker/entrypoint.sh /entrypoint.sh
|
||||
COPY docker/healthcheck.sh /healthcheck.sh
|
||||
|
||||
HEALTHCHECK --interval=5m --timeout=10s --start-period=5s --retries=3 \
|
||||
CMD curl -f http://localhost/up || exit 1
|
||||
CMD /bin/ash /healthcheck.sh
|
||||
|
||||
EXPOSE 80 443
|
||||
|
||||
@@ -104,5 +103,5 @@ VOLUME /pelican-data
|
||||
|
||||
USER www-data
|
||||
|
||||
ENTRYPOINT [ "/bin/ash", "docker/entrypoint.sh" ]
|
||||
ENTRYPOINT [ "/bin/ash", "/entrypoint.sh" ]
|
||||
CMD [ "supervisord", "-n", "-c", "/etc/supervisord.conf" ]
|
||||
|
||||
@@ -5,6 +5,6 @@ FROM --platform=$TARGETOS/$TARGETARCH php:8.4-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
|
||||
|
||||
@@ -5,7 +5,7 @@ FROM --platform=$TARGETOS/$TARGETARCH php:8.4-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
|
||||
|
||||
@@ -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
|
||||
@@ -67,40 +67,40 @@ FROM --platform=$TARGETOS/$TARGETARCH base AS final
|
||||
WORKDIR /var/www/html
|
||||
|
||||
# Install additional required libraries
|
||||
RUN apk update && apk add --no-cache \
|
||||
caddy ca-certificates supervisor supercronic
|
||||
RUN apk add --no-cache \
|
||||
# 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 --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 /var/www/html/storage/app/public /var/run/supervisord /etc/supercronic \
|
||||
# Symlinks for env, database, and avatars
|
||||
&& 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 \
|
||||
# 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
|
||||
# Create and remove directories
|
||||
RUN mkdir -p /pelican-data/storage /pelican-data/plugins /var/run/supervisord \
|
||||
&& rm -rf /var/www/html/plugins \
|
||||
# Symlinks for env, database, storage, and plugins
|
||||
&& 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 \
|
||||
&& ln -s /pelican-data/plugins /var/www/html \
|
||||
# Allow www-data write permissions where necessary
|
||||
&& chown -R www-data: /pelican-data .env ./storage ./plugins ./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/
|
||||
|
||||
# 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 ./docker/entrypoint.sh
|
||||
COPY docker/entrypoint.sh /entrypoint.sh
|
||||
COPY docker/healthcheck.sh /healthcheck.sh
|
||||
|
||||
HEALTHCHECK --interval=5m --timeout=10s --start-period=5s --retries=3 \
|
||||
CMD curl -f http://localhost/up || exit 1
|
||||
CMD /bin/ash /healthcheck.sh
|
||||
|
||||
EXPOSE 80 443
|
||||
|
||||
@@ -108,5 +108,5 @@ VOLUME /pelican-data
|
||||
|
||||
USER www-data
|
||||
|
||||
ENTRYPOINT [ "/bin/ash", "docker/entrypoint.sh" ]
|
||||
ENTRYPOINT [ "/bin/ash", "/entrypoint.sh" ]
|
||||
CMD [ "supervisord", "-n", "-c", "/etc/supervisord.conf" ]
|
||||
|
||||
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');
|
||||
}
|
||||
}
|
||||
@@ -2,10 +2,14 @@
|
||||
|
||||
namespace App\Console\Commands\Egg;
|
||||
|
||||
use App\Enums\EggFormat;
|
||||
use App\Models\Egg;
|
||||
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
|
||||
{
|
||||
@@ -23,6 +27,9 @@ class CheckEggUpdatesCommand extends Command
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws JsonException
|
||||
*/
|
||||
private function check(Egg $egg, EggExporterService $exporterService): void
|
||||
{
|
||||
if (is_null($egg->update_url)) {
|
||||
@@ -31,22 +38,24 @@ class CheckEggUpdatesCommand extends Command
|
||||
return;
|
||||
}
|
||||
|
||||
$currentJson = json_decode($exporterService->handle($egg->id));
|
||||
unset($currentJson->exported_at);
|
||||
$ext = strtolower(pathinfo(parse_url($egg->update_url, PHP_URL_PATH), PATHINFO_EXTENSION));
|
||||
$isYaml = in_array($ext, ['yaml', 'yml']);
|
||||
|
||||
$updatedEgg = file_get_contents($egg->update_url);
|
||||
assert($updatedEgg !== false);
|
||||
$updatedJson = json_decode($updatedEgg);
|
||||
unset($updatedJson->exported_at);
|
||||
$local = $isYaml
|
||||
? Yaml::parse($exporterService->handle($egg->id, EggFormat::YAML))
|
||||
: json_decode($exporterService->handle($egg->id, EggFormat::JSON), true);
|
||||
|
||||
if (md5(json_encode($currentJson, JSON_THROW_ON_ERROR)) === md5(json_encode($updatedJson, JSON_THROW_ON_ERROR))) {
|
||||
$this->info("$egg->name: Up-to-date");
|
||||
cache()->put("eggs.$egg->uuid.update", false, now()->addHour());
|
||||
$remote = Http::timeout(5)->connectTimeout(1)->get($egg->update_url)->throw()->body();
|
||||
$remote = $isYaml ? Yaml::parse($remote) : json_decode($remote, true);
|
||||
|
||||
return;
|
||||
}
|
||||
unset($local['exported_at'], $remote['exported_at']);
|
||||
|
||||
$this->warn("$egg->name: Found update");
|
||||
cache()->put("eggs.$egg->uuid.update", true, now()->addHour());
|
||||
$localHash = md5(json_encode($local, JSON_THROW_ON_ERROR));
|
||||
$remoteHash = md5(json_encode($remote, JSON_THROW_ON_ERROR));
|
||||
|
||||
$status = $localHash === $remoteHash ? 'Up-to-date' : 'Found update';
|
||||
$this->{($localHash === $remoteHash) ? 'info' : 'warn'}("$egg->name: $status");
|
||||
|
||||
cache()->put("eggs.$egg->uuid.update", $localHash !== $remoteHash, now()->addHour());
|
||||
}
|
||||
}
|
||||
|
||||
46
app/Console/Commands/Egg/UpdateEggIndexCommand.php
Normal file
46
app/Console/Commands/Egg/UpdateEggIndexCommand.php
Normal file
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands\Egg;
|
||||
|
||||
use Exception;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
|
||||
class UpdateEggIndexCommand extends Command
|
||||
{
|
||||
protected $signature = 'p:egg:update-index';
|
||||
|
||||
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();
|
||||
} catch (Exception $exception) {
|
||||
$this->error($exception->getMessage());
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
$index = [];
|
||||
foreach ($data['nests'] as $nest) {
|
||||
$nestName = $nest['nest_type'];
|
||||
|
||||
$this->info("Nest: $nestName");
|
||||
|
||||
$nestEggs = [];
|
||||
foreach ($nest['Eggs'] as $egg) {
|
||||
$eggName = $egg['egg']['name'];
|
||||
|
||||
$this->comment("Egg: $eggName");
|
||||
|
||||
$nestEggs[$egg['download_url']] = $eggName;
|
||||
}
|
||||
$index[$nestName] = $nestEggs;
|
||||
|
||||
$this->info('');
|
||||
}
|
||||
|
||||
cache()->forever('eggs.index', $index);
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ use App\Traits\EnvironmentWriterTrait;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Contracts\Console\Kernel;
|
||||
use Illuminate\Database\DatabaseManager;
|
||||
use PDOException;
|
||||
|
||||
class DatabaseSettingsCommand extends Command
|
||||
{
|
||||
@@ -105,7 +106,7 @@ class DatabaseSettingsCommand extends Command
|
||||
]);
|
||||
|
||||
$this->database->connection('_panel_command_test')->getPdo();
|
||||
} catch (\PDOException $exception) {
|
||||
} catch (PDOException $exception) {
|
||||
$this->output->error(sprintf('Unable to connect to the MySQL server using the provided credentials. The error returned was "%s".', $exception->getMessage()));
|
||||
$this->output->error(trans('commands.database_settings.DB_error_2'));
|
||||
|
||||
@@ -165,7 +166,7 @@ class DatabaseSettingsCommand extends Command
|
||||
]);
|
||||
|
||||
$this->database->connection('_panel_command_test')->getPdo();
|
||||
} catch (\PDOException $exception) {
|
||||
} catch (PDOException $exception) {
|
||||
$this->output->error(sprintf('Unable to connect to the MariaDB server using the provided credentials. The error returned was "%s".', $exception->getMessage()));
|
||||
$this->output->error(trans('commands.database_settings.DB_error_2'));
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Console\Commands\Environment;
|
||||
|
||||
use App\Exceptions\PanelException;
|
||||
use App\Traits\EnvironmentWriterTrait;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
@@ -28,7 +29,7 @@ class EmailSettingsCommand extends Command
|
||||
/**
|
||||
* Handle command execution.
|
||||
*
|
||||
* @throws \App\Exceptions\PanelException
|
||||
* @throws PanelException
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
|
||||
@@ -4,8 +4,8 @@ namespace App\Console\Commands\Maintenance;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Contracts\Filesystem\Filesystem;
|
||||
use Illuminate\Contracts\Filesystem\Factory as FilesystemFactory;
|
||||
use Illuminate\Contracts\Filesystem\Filesystem;
|
||||
use SplFileInfo;
|
||||
|
||||
class CleanServiceBackupFilesCommand extends Command
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace App\Console\Commands\Maintenance;
|
||||
use App\Models\Backup;
|
||||
use Carbon\CarbonImmutable;
|
||||
use Illuminate\Console\Command;
|
||||
use InvalidArgumentException;
|
||||
|
||||
class PruneOrphanedBackupsCommand extends Command
|
||||
{
|
||||
@@ -16,7 +17,7 @@ class PruneOrphanedBackupsCommand extends Command
|
||||
{
|
||||
$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 new InvalidArgumentException('The "--prune-age" argument must be a value greater than 0.');
|
||||
}
|
||||
|
||||
$query = Backup::query()
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Console\Commands\Node;
|
||||
|
||||
use App\Exceptions\Model\DataValidationException;
|
||||
use App\Models\Node;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
@@ -34,7 +35,7 @@ class MakeNodeCommand extends Command
|
||||
/**
|
||||
* Handle the command execution process.
|
||||
*
|
||||
* @throws \App\Exceptions\Model\DataValidationException
|
||||
* @throws DataValidationException
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
|
||||
@@ -17,7 +17,7 @@ class NodeConfigurationCommand extends Command
|
||||
{
|
||||
$column = ctype_digit((string) $this->argument('node')) ? 'id' : 'uuid';
|
||||
|
||||
/** @var \App\Models\Node $node */
|
||||
/** @var Node $node */
|
||||
$node = Node::query()->where($column, $this->argument('node'))->firstOr(function () {
|
||||
$this->error(trans('commands.node_config.error_not_exist'));
|
||||
|
||||
|
||||
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');
|
||||
}
|
||||
}
|
||||
25
app/Console/Commands/Plugin/ComposerPluginsCommand.php
Normal file
25
app/Console/Commands/Plugin/ComposerPluginsCommand.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands\Plugin;
|
||||
|
||||
use App\Services\Helpers\PluginService;
|
||||
use Exception;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class ComposerPluginsCommand extends Command
|
||||
{
|
||||
protected $signature = 'p:plugin:composer';
|
||||
|
||||
protected $description = 'Makes sure the needed composer packages for all installed plugins are available.';
|
||||
|
||||
public function handle(PluginService $pluginService): void
|
||||
{
|
||||
try {
|
||||
$pluginService->manageComposerPackages();
|
||||
} catch (Exception $exception) {
|
||||
report($exception);
|
||||
|
||||
$this->error($exception->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
37
app/Console/Commands/Plugin/DisablePluginCommand.php
Normal file
37
app/Console/Commands/Plugin/DisablePluginCommand.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands\Plugin;
|
||||
|
||||
use App\Models\Plugin;
|
||||
use App\Services\Helpers\PluginService;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class DisablePluginCommand extends Command
|
||||
{
|
||||
protected $signature = 'p:plugin:disable {id?}';
|
||||
|
||||
protected $description = 'Disables a plugin';
|
||||
|
||||
public function handle(PluginService $pluginService): void
|
||||
{
|
||||
$id = $this->argument('id') ?? $this->choice('Plugin', Plugin::pluck('name', 'id')->toArray());
|
||||
|
||||
$plugin = Plugin::find($id);
|
||||
|
||||
if (!$plugin) {
|
||||
$this->error('Plugin does not exist!');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$plugin->canDisable()) {
|
||||
$this->error("Plugin can't be disabled!");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$pluginService->disablePlugin($plugin);
|
||||
|
||||
$this->info('Plugin disabled.');
|
||||
}
|
||||
}
|
||||
43
app/Console/Commands/Plugin/InstallPluginCommand.php
Normal file
43
app/Console/Commands/Plugin/InstallPluginCommand.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
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
|
||||
{
|
||||
protected $signature = 'p:plugin:install {id?}';
|
||||
|
||||
protected $description = 'Installs a plugin';
|
||||
|
||||
public function handle(PluginService $pluginService): void
|
||||
{
|
||||
$id = $this->argument('id') ?? $this->choice('Plugin', Plugin::pluck('name', 'id')->toArray());
|
||||
|
||||
$plugin = Plugin::find($id);
|
||||
|
||||
if (!$plugin) {
|
||||
$this->error('Plugin does not exist!');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($plugin->status !== PluginStatus::NotInstalled) {
|
||||
$this->error('Plugin is already installed!');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$pluginService->installPlugin($plugin);
|
||||
|
||||
$this->info('Plugin installed and enabled.');
|
||||
} catch (Exception $exception) {
|
||||
$this->error('Could not install plugin: ' . $exception->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
28
app/Console/Commands/Plugin/ListPluginsCommand.php
Normal file
28
app/Console/Commands/Plugin/ListPluginsCommand.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands\Plugin;
|
||||
|
||||
use App\Models\Plugin;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class ListPluginsCommand extends Command
|
||||
{
|
||||
protected $signature = 'p:plugin:list';
|
||||
|
||||
protected $description = 'List all installed plugins';
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
$plugins = Plugin::query()->get(['name', 'author', 'status', 'version', 'panels', 'category']);
|
||||
|
||||
if (count($plugins) < 1) {
|
||||
$this->warn('No plugins installed');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->table(['Name', 'Author', 'Status', 'Version', 'Panels', 'Category'], $plugins->toArray());
|
||||
|
||||
$this->output->newLine();
|
||||
}
|
||||
}
|
||||
135
app/Console/Commands/Plugin/MakePluginCommand.php
Normal file
135
app/Console/Commands/Plugin/MakePluginCommand.php
Normal file
@@ -0,0 +1,135 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands\Plugin;
|
||||
|
||||
use App\Enums\PluginCategory;
|
||||
use App\Enums\PluginStatus;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Filesystem\Filesystem;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class MakePluginCommand extends Command
|
||||
{
|
||||
protected $signature = 'p:plugin:make
|
||||
{--name=}
|
||||
{--author=}
|
||||
{--description=}
|
||||
{--category=}
|
||||
{--url=}
|
||||
{--updateUrl=}
|
||||
{--panels=}
|
||||
{--panelVersion=}';
|
||||
|
||||
protected $description = 'Create a new plugin';
|
||||
|
||||
public function __construct(private Filesystem $filesystem)
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
$name = $this->option('name') ?? $this->ask('Name');
|
||||
$name = preg_replace('/[^A-Za-z0-9 ]/', '', Str::ascii($name));
|
||||
|
||||
$id = Str::slug($name);
|
||||
|
||||
if ($this->filesystem->exists(plugin_path($id))) {
|
||||
$this->error('Plugin with that name already exists!');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$author = $this->option('author') ?? $this->ask('Author', cache('plugin.author'));
|
||||
$author = preg_replace('/[^A-Za-z0-9 ]/', '', Str::ascii($author));
|
||||
cache()->forever('plugin.author', $author);
|
||||
|
||||
$namespace = Str::studly($author) . '\\' . Str::studly($name);
|
||||
$class = Str::studly($name . 'Plugin');
|
||||
|
||||
if (class_exists('\\' . $namespace . '\\' . $class)) {
|
||||
$this->error('Plugin class with that name already exists!');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->info('Creating Plugin "' . $name . '" (' . $id . ') by ' . $author);
|
||||
|
||||
$description = $this->option('description') ?? $this->ask('Description (can be empty)');
|
||||
|
||||
$category = $this->option('category') ?? $this->choice('Category', collect(PluginCategory::cases())->mapWithKeys(fn (PluginCategory $category) => [$category->value => $category->getLabel()])->toArray(), PluginCategory::Plugin->value);
|
||||
|
||||
if (!PluginCategory::tryFrom($category)) {
|
||||
$this->error('Unknown plugin category!');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$url = $this->option('url') ?? $this->ask('URL (can be empty)');
|
||||
$updateUrl = $this->option('updateUrl') ?? $this->ask('Update URL (can be empty)');
|
||||
|
||||
$panels = $this->option('panels');
|
||||
if (!$panels) {
|
||||
if ($this->confirm('Should the plugin be available on all panels?', true)) {
|
||||
$panels = null;
|
||||
} else {
|
||||
$panels = $this->choice('Panels (comma separated list)', [
|
||||
'admin' => 'Admin Area',
|
||||
'server' => 'Client Area',
|
||||
'app' => 'Server List',
|
||||
], multiple: true);
|
||||
}
|
||||
}
|
||||
$panels = is_string($panels) ? explode(',', $panels) : $panels;
|
||||
|
||||
$panelVersion = $this->option('panelVersion');
|
||||
if (!$panelVersion) {
|
||||
$panelVersion = $this->ask('Required panel version (leave empty for no constraint)', config('app.version') === 'canary' ? null : config('app.version'));
|
||||
|
||||
if ($panelVersion && $this->confirm("Should the version constraint be minimal instead of strict? ($panelVersion or higher instead of only $panelVersion)")) {
|
||||
$panelVersion = "^$panelVersion";
|
||||
}
|
||||
}
|
||||
|
||||
$composerPackages = null;
|
||||
// TODO: ask for composer packages?
|
||||
|
||||
// Create base directory
|
||||
$this->filesystem->makeDirectory(plugin_path($id));
|
||||
|
||||
// Write plugin.json
|
||||
$this->filesystem->put(plugin_path($id, 'plugin.json'), json_encode([
|
||||
'id' => $id,
|
||||
'name' => $name,
|
||||
'author' => $author,
|
||||
'version' => '1.0.0',
|
||||
'description' => $description,
|
||||
'category' => $category,
|
||||
'url' => $url,
|
||||
'update_url' => $updateUrl,
|
||||
'namespace' => $namespace,
|
||||
'class' => $class,
|
||||
'panels' => $panels,
|
||||
'panel_version' => $panelVersion,
|
||||
'composer_packages' => $composerPackages,
|
||||
'meta' => [
|
||||
'status' => PluginStatus::Enabled,
|
||||
'status_message' => null,
|
||||
],
|
||||
], JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
|
||||
|
||||
// Create src directory and create main class
|
||||
$this->filesystem->makeDirectory(plugin_path($id, 'src'));
|
||||
$this->filesystem->put(plugin_path($id, 'src', $class . '.php'), Str::replace(['$namespace$', '$class$', '$id$'], [$namespace, $class, $id], file_get_contents(__DIR__ . '/Plugin.stub')));
|
||||
|
||||
// Create Providers directory and create service provider
|
||||
$this->filesystem->makeDirectory(plugin_path($id, 'src', 'Providers'));
|
||||
$this->filesystem->put(plugin_path($id, 'src', 'Providers', $class . 'Provider.php'), Str::replace(['$namespace$', '$class$'], [$namespace, $class], file_get_contents(__DIR__ . '/PluginProvider.stub')));
|
||||
|
||||
// Create config directory and create config file
|
||||
$this->filesystem->makeDirectory(plugin_path($id, 'config'));
|
||||
$this->filesystem->put(plugin_path($id, 'config', $id . '.php'), Str::replace(['$name$'], [$name], file_get_contents(__DIR__ . '/PluginConfig.stub')));
|
||||
|
||||
$this->info('Plugin created.');
|
||||
}
|
||||
}
|
||||
25
app/Console/Commands/Plugin/Plugin.stub
Normal file
25
app/Console/Commands/Plugin/Plugin.stub
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace $namespace$;
|
||||
|
||||
use Filament\Contracts\Plugin;
|
||||
use Filament\Panel;
|
||||
|
||||
class $class$ implements Plugin
|
||||
{
|
||||
public function getId(): string
|
||||
{
|
||||
return '$id$';
|
||||
}
|
||||
|
||||
public function register(Panel $panel): void
|
||||
{
|
||||
// Allows you to use any configuration option that is available to the panel.
|
||||
// This includes registering resources, custom pages, themes, render hooks and more.
|
||||
}
|
||||
|
||||
public function boot(Panel $panel): void
|
||||
{
|
||||
// Is run only when the panel that the plugin is being registered to is actually in-use. It is executed by a middleware class.
|
||||
}
|
||||
}
|
||||
5
app/Console/Commands/Plugin/PluginConfig.stub
Normal file
5
app/Console/Commands/Plugin/PluginConfig.stub
Normal file
@@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
// Config values for $name$
|
||||
];
|
||||
18
app/Console/Commands/Plugin/PluginProvider.stub
Normal file
18
app/Console/Commands/Plugin/PluginProvider.stub
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace $namespace$\Providers;
|
||||
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class $class$Provider extends ServiceProvider
|
||||
{
|
||||
public function register(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
public function boot(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
48
app/Console/Commands/Plugin/UninstallPluginCommand.php
Normal file
48
app/Console/Commands/Plugin/UninstallPluginCommand.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
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
|
||||
{
|
||||
protected $signature = 'p:plugin:uninstall {id?} {--delete : Delete the plugin files}';
|
||||
|
||||
protected $description = 'Uninstalls a plugin';
|
||||
|
||||
public function handle(PluginService $pluginService): void
|
||||
{
|
||||
$id = $this->argument('id') ?? $this->choice('Plugin', Plugin::pluck('name', 'id')->toArray());
|
||||
|
||||
$plugin = Plugin::find($id);
|
||||
|
||||
if (!$plugin) {
|
||||
$this->error('Plugin does not exist!');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($plugin->status === PluginStatus::NotInstalled) {
|
||||
$this->error('Plugin is not installed!');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$deleteFiles = $this->option('delete');
|
||||
if ($this->input->isInteractive() && !$deleteFiles) {
|
||||
$deleteFiles = $this->confirm('Do you also want to delete the plugin files?');
|
||||
}
|
||||
|
||||
try {
|
||||
$pluginService->uninstallPlugin($plugin, $deleteFiles);
|
||||
|
||||
$this->info('Plugin uninstalled' . ($deleteFiles ? ' and files deleted' : '') . '.');
|
||||
} catch (Exception $exception) {
|
||||
$this->error('Could not uninstall plugin: ' . $exception->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
42
app/Console/Commands/Plugin/UpdatePluginCommand.php
Normal file
42
app/Console/Commands/Plugin/UpdatePluginCommand.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands\Plugin;
|
||||
|
||||
use App\Models\Plugin;
|
||||
use App\Services\Helpers\PluginService;
|
||||
use Exception;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class UpdatePluginCommand extends Command
|
||||
{
|
||||
protected $signature = 'p:plugin:update {id?}';
|
||||
|
||||
protected $description = 'Updates a plugin';
|
||||
|
||||
public function handle(PluginService $pluginService): void
|
||||
{
|
||||
$id = $this->argument('id') ?? $this->choice('Plugin', Plugin::pluck('name', 'id')->toArray());
|
||||
|
||||
$plugin = Plugin::find($id);
|
||||
|
||||
if (!$plugin) {
|
||||
$this->error('Plugin does not exist!');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$plugin->isUpdateAvailable()) {
|
||||
$this->error("Plugin doesn't need updating!");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$pluginService->updatePlugin($plugin);
|
||||
|
||||
$this->info('Plugin updated.');
|
||||
} catch (Exception $exception) {
|
||||
$this->error('Could not update plugin: ' . $exception->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,10 +2,10 @@
|
||||
|
||||
namespace App\Console\Commands\Schedule;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use App\Models\Schedule;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use App\Services\Schedules\ProcessScheduleService;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Throwable;
|
||||
|
||||
class ProcessRunnableCommand extends Command
|
||||
@@ -64,7 +64,7 @@ class ProcessRunnableCommand extends Command
|
||||
} catch (Throwable $exception) {
|
||||
logger()->error($exception, ['schedule_id' => $schedule->id]);
|
||||
|
||||
$this->error(trans('commands.schedule.process.no_tasks') . " #$schedule->id: " . $exception->getMessage());
|
||||
$this->error(trans('commands.schedule.process.error_message', ['schedules' => " #$schedule->id: " . $exception->getMessage()]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
namespace App\Console\Commands\Server;
|
||||
|
||||
use App\Models\Server;
|
||||
use App\Repositories\Daemon\DaemonServerRepository;
|
||||
use Exception;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Illuminate\Validation\Factory as ValidatorFactory;
|
||||
use App\Repositories\Daemon\DaemonPowerRepository;
|
||||
use Exception;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
class BulkPowerActionCommand extends Command
|
||||
{
|
||||
@@ -19,7 +19,7 @@ class BulkPowerActionCommand extends Command
|
||||
|
||||
protected $description = 'Perform bulk power management on large groupings of servers or nodes at once.';
|
||||
|
||||
public function handle(DaemonPowerRepository $powerRepository, ValidatorFactory $validator): void
|
||||
public function handle(DaemonServerRepository $serverRepository, ValidatorFactory $validator): void
|
||||
{
|
||||
$action = $this->argument('action');
|
||||
$nodes = empty($this->option('nodes')) ? [] : explode(',', $this->option('nodes'));
|
||||
@@ -52,7 +52,7 @@ class BulkPowerActionCommand extends Command
|
||||
|
||||
$bar = $this->output->createProgressBar($count);
|
||||
|
||||
$this->getQueryBuilder($servers, $nodes)->get()->each(function ($server, int $index) use ($action, $powerRepository, &$bar): mixed {
|
||||
$this->getQueryBuilder($servers, $nodes)->get()->each(function ($server, int $index) use ($action, $serverRepository, &$bar): mixed {
|
||||
$bar->clear();
|
||||
|
||||
if (!$server instanceof Server) {
|
||||
@@ -60,7 +60,7 @@ class BulkPowerActionCommand extends Command
|
||||
}
|
||||
|
||||
try {
|
||||
$powerRepository->setServer($server)->send($action);
|
||||
$serverRepository->setServer($server)->power($action);
|
||||
} catch (Exception $exception) {
|
||||
$this->output->error(trans('command/messages.server.power.action_failed', [
|
||||
'name' => $server->name,
|
||||
|
||||
@@ -1,193 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use App\Console\Kernel;
|
||||
use Symfony\Component\Process\Process;
|
||||
use Symfony\Component\Console\Helper\ProgressBar;
|
||||
|
||||
class UpgradeCommand extends Command
|
||||
{
|
||||
protected const DEFAULT_URL = 'https://github.com/pelican-dev/panel/releases/%s/panel.tar.gz';
|
||||
|
||||
protected $signature = 'p:upgrade
|
||||
{--user= : The user that PHP runs under. All files will be owned by this user.}
|
||||
{--group= : The group that PHP runs under. All files will be owned by this group.}
|
||||
{--url= : The specific archive to download.}
|
||||
{--release= : A specific version to download from GitHub. Leave blank to use latest.}
|
||||
{--skip-download : If set no archive will be downloaded.}';
|
||||
|
||||
protected $description = 'Downloads a new archive from GitHub and then executes the normal upgrade commands.';
|
||||
|
||||
/**
|
||||
* Executes an upgrade command which will run through all of our standard
|
||||
* Panel commands and enable users to basically just download
|
||||
* the archive and execute this and be done.
|
||||
*
|
||||
* This places the application in maintenance mode as well while the commands
|
||||
* are being executed.
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
$skipDownload = $this->option('skip-download');
|
||||
if (!$skipDownload) {
|
||||
$this->output->warning(trans('commands.upgrade.integrity'));
|
||||
$this->output->comment(trans('commands.upgrade.source_url'));
|
||||
$this->line($this->getUrl());
|
||||
}
|
||||
|
||||
$user = 'www-data';
|
||||
$group = 'www-data';
|
||||
if ($this->input->isInteractive()) {
|
||||
if (!$skipDownload) {
|
||||
$skipDownload = !$this->confirm(trans('commands.upgrade.skipDownload'), true);
|
||||
}
|
||||
|
||||
if (is_null($this->option('user'))) {
|
||||
$userDetails = function_exists('posix_getpwuid') ? posix_getpwuid(fileowner('public')) : [];
|
||||
$user = $userDetails['name'] ?? 'www-data';
|
||||
|
||||
$message = trans('commands.upgrade.webserver_user', ['user' => $user]);
|
||||
if (!$this->confirm($message, true)) {
|
||||
$user = $this->anticipate(
|
||||
trans('commands.upgrade.name_webserver'),
|
||||
[
|
||||
'www-data',
|
||||
'nginx',
|
||||
'apache',
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (is_null($this->option('group'))) {
|
||||
$groupDetails = function_exists('posix_getgrgid') ? posix_getgrgid(filegroup('public')) : [];
|
||||
$group = $groupDetails['name'] ?? 'www-data';
|
||||
|
||||
$message = trans('commands.upgrade.group_webserver', ['group' => $user]);
|
||||
if (!$this->confirm($message, true)) {
|
||||
$group = $this->anticipate(
|
||||
trans('commands.upgrade.group_webserver_question'),
|
||||
[
|
||||
'www-data',
|
||||
'nginx',
|
||||
'apache',
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$this->confirm(trans('commands.upgrade.are_your_sure'))) {
|
||||
$this->warn(trans('commands.upgrade.terminated'));
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ini_set('output_buffering', '0');
|
||||
$bar = $this->output->createProgressBar($skipDownload ? 9 : 10);
|
||||
$bar->start();
|
||||
|
||||
if (!$skipDownload) {
|
||||
$this->withProgress($bar, function () {
|
||||
$this->line("\$upgrader> curl -L \"{$this->getUrl()}\" | tar -xzv");
|
||||
$process = Process::fromShellCommandline("curl -L \"{$this->getUrl()}\" | tar -xzv");
|
||||
$process->run(function ($type, $buffer) {
|
||||
$this->{$type === Process::ERR ? 'error' : 'line'}($buffer);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
$this->withProgress($bar, function () {
|
||||
$this->line('$upgrader> php artisan down');
|
||||
$this->call('down');
|
||||
});
|
||||
|
||||
$this->withProgress($bar, function () {
|
||||
$this->line('$upgrader> chmod -R 755 storage bootstrap/cache');
|
||||
$process = new Process(['chmod', '-R', '755', 'storage', 'bootstrap/cache']);
|
||||
$process->run(function ($type, $buffer) {
|
||||
$this->{$type === Process::ERR ? 'error' : 'line'}($buffer);
|
||||
});
|
||||
});
|
||||
|
||||
$this->withProgress($bar, function () {
|
||||
$command = ['composer', 'install', '--no-ansi'];
|
||||
if (config('app.env') === 'production' && !config('app.debug')) {
|
||||
$command[] = '--optimize-autoloader';
|
||||
$command[] = '--no-dev';
|
||||
}
|
||||
|
||||
$this->line('$upgrader> ' . implode(' ', $command));
|
||||
$process = new Process($command);
|
||||
$process->setTimeout(10 * 60);
|
||||
$process->run(function ($type, $buffer) {
|
||||
$this->line($buffer);
|
||||
});
|
||||
});
|
||||
|
||||
/** @var \Illuminate\Foundation\Application $app */
|
||||
$app = require __DIR__ . '/../../../bootstrap/app.php';
|
||||
/** @var \App\Console\Kernel $kernel */
|
||||
$kernel = $app->make(Kernel::class);
|
||||
$kernel->bootstrap();
|
||||
$this->setLaravel($app);
|
||||
|
||||
$this->withProgress($bar, function () {
|
||||
$this->line('$upgrader> php artisan view:clear');
|
||||
$this->call('view:clear');
|
||||
});
|
||||
|
||||
$this->withProgress($bar, function () {
|
||||
$this->line('$upgrader> php artisan config:clear');
|
||||
$this->call('config:clear');
|
||||
});
|
||||
|
||||
$this->withProgress($bar, function () {
|
||||
$this->line('$upgrader> php artisan migrate --force --seed');
|
||||
$this->call('migrate', ['--force' => true, '--seed' => true]);
|
||||
});
|
||||
|
||||
$this->withProgress($bar, function () use ($user, $group) {
|
||||
$this->line("\$upgrader> chown -R {$user}:{$group} *");
|
||||
$process = Process::fromShellCommandline("chown -R {$user}:{$group} *", $this->getLaravel()->basePath());
|
||||
$process->setTimeout(10 * 60);
|
||||
$process->run(function ($type, $buffer) {
|
||||
$this->{$type === Process::ERR ? 'error' : 'line'}($buffer);
|
||||
});
|
||||
});
|
||||
|
||||
$this->withProgress($bar, function () {
|
||||
$this->line('$upgrader> php artisan queue:restart');
|
||||
$this->call('queue:restart');
|
||||
});
|
||||
|
||||
$this->withProgress($bar, function () {
|
||||
$this->line('$upgrader> php artisan up');
|
||||
$this->call('up');
|
||||
});
|
||||
|
||||
$this->newLine(2);
|
||||
$this->info(trans('commands.upgrade.success'));
|
||||
}
|
||||
|
||||
protected function withProgress(ProgressBar $bar, \Closure $callback): void
|
||||
{
|
||||
$bar->clear();
|
||||
$callback();
|
||||
$bar->advance();
|
||||
$bar->display();
|
||||
}
|
||||
|
||||
protected function getUrl(): string
|
||||
{
|
||||
if ($this->option('url')) {
|
||||
return $this->option('url');
|
||||
}
|
||||
|
||||
return sprintf(self::DEFAULT_URL, $this->option('release') ? 'download/v' . $this->option('release') : 'latest/download');
|
||||
}
|
||||
}
|
||||
@@ -3,8 +3,8 @@
|
||||
namespace App\Console\Commands\User;
|
||||
|
||||
use App\Models\User;
|
||||
use Webmozart\Assert\Assert;
|
||||
use Illuminate\Console\Command;
|
||||
use Webmozart\Assert\Assert;
|
||||
|
||||
class DeleteUserCommand extends Command
|
||||
{
|
||||
@@ -35,7 +35,7 @@ class DeleteUserCommand extends Command
|
||||
if ($this->input->isInteractive()) {
|
||||
$tableValues = [];
|
||||
foreach ($results as $user) {
|
||||
$tableValues[] = [$user->id, $user->email, $user->name];
|
||||
$tableValues[] = [$user->id, $user->email, $user->username];
|
||||
}
|
||||
|
||||
$this->table(['User ID', 'Email', 'Name'], $tableValues);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Console\Commands\User;
|
||||
|
||||
use App\Exceptions\Model\DataValidationException;
|
||||
use App\Models\User;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
@@ -14,20 +15,22 @@ class DisableTwoFactorCommand extends Command
|
||||
/**
|
||||
* Handle command execution process.
|
||||
*
|
||||
* @throws \App\Exceptions\Model\DataValidationException
|
||||
* @throws DataValidationException
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
if ($this->input->isInteractive()) {
|
||||
$this->output->warning(trans('command/messages.user.2fa_help_text.0') . trans('command/messages.user.2fa_help_text.1'));
|
||||
$this->output->warning(trans('command/messages.user.2fa_help_text'));
|
||||
}
|
||||
|
||||
$email = $this->option('email') ?? $this->ask(trans('command/messages.user.ask_email'));
|
||||
|
||||
$user = User::query()->where('email', $email)->firstOrFail();
|
||||
$user->use_totp = false;
|
||||
$user->totp_secret = null;
|
||||
$user->save();
|
||||
$user = User::where('email', $email)->firstOrFail();
|
||||
$user->update([
|
||||
'mfa_app_secret' => null,
|
||||
'mfa_app_recovery_codes' => null,
|
||||
'mfa_email_enabled' => false,
|
||||
]);
|
||||
|
||||
$this->info(trans('command/messages.user.2fa_disabled', ['email' => $user->email]));
|
||||
}
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
|
||||
namespace App\Console\Commands\User;
|
||||
|
||||
use App\Exceptions\Model\DataValidationException;
|
||||
use App\Services\Users\UserCreationService;
|
||||
use Exception;
|
||||
use Illuminate\Console\Command;
|
||||
use App\Services\Users\UserCreationService;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class MakeUserCommand extends Command
|
||||
@@ -25,7 +26,7 @@ class MakeUserCommand extends Command
|
||||
* Handle command request to create a new user.
|
||||
*
|
||||
* @throws Exception
|
||||
* @throws \App\Exceptions\Model\DataValidationException
|
||||
* @throws DataValidationException
|
||||
*/
|
||||
public function handle(): int
|
||||
{
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Console;
|
||||
|
||||
use App\Console\Commands\Egg\CheckEggUpdatesCommand;
|
||||
use App\Console\Commands\Egg\UpdateEggIndexCommand;
|
||||
use App\Console\Commands\Maintenance\CleanServiceBackupFilesCommand;
|
||||
use App\Console\Commands\Maintenance\PruneImagesCommand;
|
||||
use App\Console\Commands\Maintenance\PruneOrphanedBackupsCommand;
|
||||
@@ -41,7 +42,9 @@ class Kernel extends ConsoleKernel
|
||||
|
||||
$schedule->command(CleanServiceBackupFilesCommand::class)->daily();
|
||||
$schedule->command(PruneImagesCommand::class)->daily();
|
||||
$schedule->command(CheckEggUpdatesCommand::class)->hourly();
|
||||
|
||||
$schedule->command(CheckEggUpdatesCommand::class)->daily();
|
||||
$schedule->command(UpdateEggIndexCommand::class)->daily();
|
||||
|
||||
if (config('backups.prune_age')) {
|
||||
// Every 30 minutes, run the backup pruning command so that any abandoned backups can be deleted.
|
||||
|
||||
@@ -2,12 +2,13 @@
|
||||
|
||||
namespace App\Contracts\Http;
|
||||
|
||||
use App\Enums\SubuserPermission;
|
||||
|
||||
interface ClientPermissionsRequest
|
||||
{
|
||||
/**
|
||||
* Returns the permissions string indicating which permission should be used to
|
||||
* validate that the authenticated user has permission to perform this action against
|
||||
* the given resource (server).
|
||||
* Returns the permission used to validate that the authenticated user may perform
|
||||
* this action against the given resource (server).
|
||||
*/
|
||||
public function permission(): string;
|
||||
public function permission(): SubuserPermission|string;
|
||||
}
|
||||
|
||||
18
app/Contracts/Plugins/HasPluginSettings.php
Normal file
18
app/Contracts/Plugins/HasPluginSettings.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace App\Contracts\Plugins;
|
||||
|
||||
use Filament\Schemas\Components\Component;
|
||||
|
||||
interface HasPluginSettings
|
||||
{
|
||||
/**
|
||||
* @return Component[]
|
||||
*/
|
||||
public function getSettingsForm(): array;
|
||||
|
||||
/**
|
||||
* @param array<mixed, mixed> $data
|
||||
*/
|
||||
public function saveSettings(array $data): void;
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -32,6 +33,6 @@ enum BackupStatus: string implements HasColor, HasIcon, HasLabel
|
||||
|
||||
public function getLabel(): string
|
||||
{
|
||||
return str($this->value)->headline();
|
||||
return trans('server/backup.backup_status.' . $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;
|
||||
@@ -23,20 +24,20 @@ 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,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -68,7 +69,7 @@ enum ContainerStatus: string implements HasColor, HasIcon, HasLabel
|
||||
|
||||
public function getLabel(): string
|
||||
{
|
||||
return str($this->value)->title();
|
||||
return trans('server/console.status.' . $this->value);
|
||||
}
|
||||
|
||||
public function isOffline(): bool
|
||||
@@ -88,7 +89,7 @@ enum ContainerStatus: string implements HasColor, HasIcon, HasLabel
|
||||
|
||||
public function isStartable(): bool
|
||||
{
|
||||
return !in_array($this, [ContainerStatus::Running, ContainerStatus::Starting, ContainerStatus::Stopping, ContainerStatus::Restarting]);
|
||||
return !in_array($this, [ContainerStatus::Running, ContainerStatus::Starting, ContainerStatus::Stopping, ContainerStatus::Restarting, ContainerStatus::Missing]);
|
||||
}
|
||||
|
||||
public function isRestartable(): bool
|
||||
@@ -97,18 +98,16 @@ enum ContainerStatus: string implements HasColor, HasIcon, HasLabel
|
||||
return true;
|
||||
}
|
||||
|
||||
return !in_array($this, [ContainerStatus::Offline]);
|
||||
return !in_array($this, [ContainerStatus::Offline, ContainerStatus::Missing]);
|
||||
}
|
||||
|
||||
public function isStoppable(): bool
|
||||
{
|
||||
return !in_array($this, [ContainerStatus::Starting, ContainerStatus::Stopping, ContainerStatus::Restarting, ContainerStatus::Exited, ContainerStatus::Offline]);
|
||||
return !in_array($this, [ContainerStatus::Starting, ContainerStatus::Stopping, ContainerStatus::Restarting, ContainerStatus::Exited, ContainerStatus::Offline, ContainerStatus::Missing]);
|
||||
}
|
||||
|
||||
public function isKillable(): bool
|
||||
{
|
||||
// [ContainerStatus::Restarting, ContainerStatus::Removing, ContainerStatus::Dead, ContainerStatus::Created]
|
||||
|
||||
return !in_array($this, [ContainerStatus::Offline, ContainerStatus::Running, ContainerStatus::Exited]);
|
||||
return !in_array($this, [ContainerStatus::Offline, ContainerStatus::Running, ContainerStatus::Exited, ContainerStatus::Missing]);
|
||||
}
|
||||
}
|
||||
|
||||
9
app/Enums/CustomRenderHooks.php
Normal file
9
app/Enums/CustomRenderHooks.php
Normal file
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
enum CustomRenderHooks: string
|
||||
{
|
||||
case FooterStart = 'pelican::footer.start';
|
||||
case FooterEnd = 'pelican::footer.end';
|
||||
}
|
||||
40
app/Enums/CustomizationKey.php
Normal file
40
app/Enums/CustomizationKey.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
enum CustomizationKey: string
|
||||
{
|
||||
case ConsoleRows = 'console_rows';
|
||||
case ConsoleFont = 'console_font';
|
||||
case ConsoleFontSize = 'console_font_size';
|
||||
case ConsoleGraphPeriod = 'console_graph_period';
|
||||
case TopNavigation = 'top_navigation';
|
||||
case DashboardLayout = 'dashboard_layout';
|
||||
|
||||
case ButtonStyle = 'button_style';
|
||||
|
||||
public function getDefaultValue(): string|int|bool
|
||||
{
|
||||
return match ($this) {
|
||||
self::ConsoleRows => 30,
|
||||
self::ConsoleFont => 'monospace',
|
||||
self::ConsoleFontSize => 14,
|
||||
self::ConsoleGraphPeriod => 30,
|
||||
self::TopNavigation => config('panel.filament.default-navigation', 'sidebar'),
|
||||
self::DashboardLayout => 'grid',
|
||||
self::ButtonStyle => true,
|
||||
};
|
||||
}
|
||||
|
||||
/** @return array<string, string|int|bool> */
|
||||
public static function getDefaultCustomization(): array
|
||||
{
|
||||
$default = [];
|
||||
|
||||
foreach (self::cases() as $key) {
|
||||
$default[$key->value] = $key->getDefaultValue();
|
||||
}
|
||||
|
||||
return $default;
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ enum EditorLanguages: string implements HasLabel
|
||||
case bat = 'bat';
|
||||
case bicep = 'bicep';
|
||||
case cameligo = 'cameligo';
|
||||
case coljure = 'coljure';
|
||||
case clojure = 'clojure';
|
||||
case coffeescript = 'coffeescript';
|
||||
case c = 'c';
|
||||
case cpp = 'cpp';
|
||||
|
||||
9
app/Enums/EggFormat.php
Normal file
9
app/Enums/EggFormat.php
Normal file
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
enum EggFormat: string
|
||||
{
|
||||
case YAML = 'yaml';
|
||||
case JSON = 'json';
|
||||
}
|
||||
9
app/Enums/HeaderActionPosition.php
Normal file
9
app/Enums/HeaderActionPosition.php
Normal file
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
enum HeaderActionPosition: string
|
||||
{
|
||||
case Before = 'before';
|
||||
case After = 'after';
|
||||
}
|
||||
9
app/Enums/HeaderWidgetPosition.php
Normal file
9
app/Enums/HeaderWidgetPosition.php
Normal file
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
enum HeaderWidgetPosition: string
|
||||
{
|
||||
case Before = 'before';
|
||||
case After = 'after';
|
||||
}
|
||||
28
app/Enums/PluginCategory.php
Normal file
28
app/Enums/PluginCategory.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
use BackedEnum;
|
||||
use Filament\Support\Contracts\HasIcon;
|
||||
use Filament\Support\Contracts\HasLabel;
|
||||
|
||||
enum PluginCategory: string implements HasIcon, HasLabel
|
||||
{
|
||||
case Plugin = 'plugin';
|
||||
case Theme = 'theme';
|
||||
case Language = 'language';
|
||||
|
||||
public function getIcon(): BackedEnum
|
||||
{
|
||||
return match ($this) {
|
||||
self::Plugin => TablerIcon::Package,
|
||||
self::Theme => TablerIcon::Palette,
|
||||
self::Language => TablerIcon::Language,
|
||||
};
|
||||
}
|
||||
|
||||
public function getLabel(): string
|
||||
{
|
||||
return trans('admin/plugin.category_enum.' . $this->value);
|
||||
}
|
||||
}
|
||||
44
app/Enums/PluginStatus.php
Normal file
44
app/Enums/PluginStatus.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
use BackedEnum;
|
||||
use Filament\Support\Contracts\HasColor;
|
||||
use Filament\Support\Contracts\HasIcon;
|
||||
use Filament\Support\Contracts\HasLabel;
|
||||
|
||||
enum PluginStatus: string implements HasColor, HasIcon, HasLabel
|
||||
{
|
||||
case NotInstalled = 'not_installed';
|
||||
case Disabled = 'disabled';
|
||||
case Enabled = 'enabled';
|
||||
case Errored = 'errored';
|
||||
case Incompatible = 'incompatible';
|
||||
|
||||
public function getIcon(): BackedEnum
|
||||
{
|
||||
return match ($this) {
|
||||
self::NotInstalled => TablerIcon::HeartOff,
|
||||
self::Disabled => TablerIcon::HeartX,
|
||||
self::Enabled => TablerIcon::HeartCheck,
|
||||
self::Errored => TablerIcon::HeartBroken,
|
||||
self::Incompatible => TablerIcon::HeartCancel,
|
||||
};
|
||||
}
|
||||
|
||||
public function getColor(): string
|
||||
{
|
||||
return match ($this) {
|
||||
self::NotInstalled => 'gray',
|
||||
self::Disabled => 'warning',
|
||||
self::Enabled => 'success',
|
||||
self::Errored => 'danger',
|
||||
self::Incompatible => 'danger',
|
||||
};
|
||||
}
|
||||
|
||||
public function getLabel(): string
|
||||
{
|
||||
return trans('admin/plugin.status_enum.' . $this->value);
|
||||
}
|
||||
}
|
||||
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::perMinute(5),
|
||||
self::BackupRestore => Limit::perMinutes(15, 3),
|
||||
self::DatabaseCreate => Limit::perMinute(2),
|
||||
self::SubuserCreate => Limit::perMinutes(15, 10),
|
||||
self::FilePull => Limit::perMinutes(10, 5),
|
||||
default => Limit::perMinute(2),
|
||||
};
|
||||
}
|
||||
|
||||
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,7 @@ namespace App\Enums;
|
||||
enum RolePermissionModels: string
|
||||
{
|
||||
case ApiKey = 'apiKey';
|
||||
case Allocation = 'allocation';
|
||||
case DatabaseHost = 'databaseHost';
|
||||
case Database = 'database';
|
||||
case Egg = 'egg';
|
||||
@@ -34,4 +35,9 @@ enum RolePermissionModels: string
|
||||
{
|
||||
return RolePermissionPrefixes::Update->value . ' ' . $this->value;
|
||||
}
|
||||
|
||||
public function delete(): string
|
||||
{
|
||||
return RolePermissionPrefixes::Delete->value . ' ' . $this->value;
|
||||
}
|
||||
}
|
||||
|
||||
27
app/Enums/ScheduleStatus.php
Normal file
27
app/Enums/ScheduleStatus.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
use Filament\Support\Contracts\HasColor;
|
||||
use Filament\Support\Contracts\HasLabel;
|
||||
|
||||
enum ScheduleStatus: string implements HasColor, HasLabel
|
||||
{
|
||||
case Inactive = 'inactive';
|
||||
case Processing = 'processing';
|
||||
case Active = 'active';
|
||||
|
||||
public function getColor(): string
|
||||
{
|
||||
return match ($this) {
|
||||
self::Inactive => 'danger',
|
||||
self::Processing => 'warning',
|
||||
self::Active => 'success',
|
||||
};
|
||||
}
|
||||
|
||||
public function getLabel(): string
|
||||
{
|
||||
return trans('server/schedule.schedule_status.' . $this->value);
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,50 @@
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
enum ServerResourceType
|
||||
use App\Models\Server;
|
||||
|
||||
enum ServerResourceType: string
|
||||
{
|
||||
case Unit;
|
||||
case Percentage;
|
||||
case Time;
|
||||
case Uptime = 'uptime';
|
||||
case CPU = 'cpu_absolute';
|
||||
case Memory = 'memory_bytes';
|
||||
case Disk = 'disk_bytes';
|
||||
|
||||
case CPULimit = 'cpu';
|
||||
case MemoryLimit = 'memory';
|
||||
case DiskLimit = 'disk';
|
||||
|
||||
/**
|
||||
* @return int resource amount in bytes
|
||||
*/
|
||||
public function getResourceAmount(Server $server): int
|
||||
{
|
||||
if ($this->isLimit()) {
|
||||
$resourceAmount = $server->{$this->value} ?? 0;
|
||||
|
||||
if (!$this->isPercentage()) {
|
||||
// Our limits are entered as MiB/ MB so we need to convert them to bytes
|
||||
$resourceAmount *= config('panel.use_binary_prefix') ? 1024 * 1024 : 1000 * 1000;
|
||||
}
|
||||
|
||||
return $resourceAmount;
|
||||
}
|
||||
|
||||
return $server->retrieveResources()[$this->value] ?? 0;
|
||||
}
|
||||
|
||||
public function isLimit(): bool
|
||||
{
|
||||
return $this === ServerResourceType::CPULimit || $this === ServerResourceType::MemoryLimit || $this === ServerResourceType::DiskLimit;
|
||||
}
|
||||
|
||||
public function isTime(): bool
|
||||
{
|
||||
return $this === ServerResourceType::Uptime;
|
||||
}
|
||||
|
||||
public function isPercentage(): bool
|
||||
{
|
||||
return $this === ServerResourceType::CPU || $this === ServerResourceType::CPULimit;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,35 +2,40 @@
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
use BackedEnum;
|
||||
use Filament\Support\Contracts\HasColor;
|
||||
use Filament\Support\Contracts\HasIcon;
|
||||
use Filament\Support\Contracts\HasLabel;
|
||||
|
||||
enum ServerState: string implements HasColor, HasIcon, HasLabel
|
||||
{
|
||||
case Normal = 'normal';
|
||||
case Installing = 'installing';
|
||||
case InstallFailed = 'install_failed';
|
||||
case ReinstallFailed = 'reinstall_failed';
|
||||
case Suspended = 'suspended';
|
||||
case RestoringBackup = 'restoring_backup';
|
||||
|
||||
public function getIcon(): string
|
||||
public function getIcon(): BackedEnum
|
||||
{
|
||||
return match ($this) {
|
||||
self::Normal => 'tabler-heart',
|
||||
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(): string
|
||||
public function getColor(bool $hex = false): string
|
||||
{
|
||||
if ($hex) {
|
||||
return match ($this) {
|
||||
self::Installing, self::RestoringBackup => '#2563EB',
|
||||
self::Suspended => '#D97706',
|
||||
self::InstallFailed, self::ReinstallFailed => '#EF4444',
|
||||
};
|
||||
}
|
||||
|
||||
return match ($this) {
|
||||
self::Normal => 'primary',
|
||||
self::Installing => 'primary',
|
||||
self::InstallFailed => 'danger',
|
||||
self::ReinstallFailed => 'danger',
|
||||
|
||||
11
app/Enums/StartupVariableType.php
Normal file
11
app/Enums/StartupVariableType.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
enum StartupVariableType: string
|
||||
{
|
||||
case Text = 'text';
|
||||
case Number = 'number';
|
||||
case Select = 'select';
|
||||
case Toggle = 'toggle';
|
||||
}
|
||||
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';
|
||||
}
|
||||
90
app/Enums/SubuserPermission.php
Normal file
90
app/Enums/SubuserPermission.php
Normal file
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
use BackedEnum;
|
||||
|
||||
enum SubuserPermission: string
|
||||
{
|
||||
case WebsocketConnect = 'websocket.connect';
|
||||
|
||||
case ControlConsole = 'control.console';
|
||||
case ControlStart = 'control.start';
|
||||
case ControlStop = 'control.stop';
|
||||
case ControlRestart = 'control.restart';
|
||||
|
||||
case FileRead = 'file.read';
|
||||
case FileReadContent = 'file.read-content';
|
||||
case FileCreate = 'file.create';
|
||||
case FileUpdate = 'file.update';
|
||||
case FileDelete = 'file.delete';
|
||||
case FileArchive = 'file.archive';
|
||||
case FileSftp = 'file.sftp';
|
||||
|
||||
case BackupRead = 'backup.read';
|
||||
case BackupCreate = 'backup.create';
|
||||
case BackupDelete = 'backup.delete';
|
||||
case BackupDownload = 'backup.download';
|
||||
case BackupRestore = 'backup.restore';
|
||||
|
||||
case ScheduleRead = 'schedule.read';
|
||||
case ScheduleCreate = 'schedule.create';
|
||||
case ScheduleUpdate = 'schedule.update';
|
||||
case ScheduleDelete = 'schedule.delete';
|
||||
|
||||
case UserRead = 'user.read';
|
||||
case UserCreate = 'user.create';
|
||||
case UserUpdate = 'user.update';
|
||||
case UserDelete = 'user.delete';
|
||||
|
||||
case DatabaseRead = 'database.read';
|
||||
case DatabaseCreate = 'database.create';
|
||||
case DatabaseUpdate = 'database.update';
|
||||
case DatabaseDelete = 'database.delete';
|
||||
case DatabaseViewPassword = 'database.view-password';
|
||||
|
||||
case AllocationRead = 'allocation.read';
|
||||
case AllocationCreate = 'allocation.create';
|
||||
case AllocationUpdate = 'allocation.update';
|
||||
case AllocationDelete = 'allocation.delete';
|
||||
|
||||
case ActivityRead = 'activity.read';
|
||||
|
||||
case StartupRead = 'startup.read';
|
||||
case StartupUpdate = 'startup.update';
|
||||
case StartupDockerImage = 'startup.docker-image';
|
||||
|
||||
case SettingsRename = 'settings.rename';
|
||||
case SettingsDescription = 'settings.description';
|
||||
case SettingsReinstall = 'settings.reinstall';
|
||||
|
||||
/** @return string[] */
|
||||
public function split(): array
|
||||
{
|
||||
return explode('.', $this->value, 2);
|
||||
}
|
||||
|
||||
public function isHidden(): bool
|
||||
{
|
||||
return $this === self::WebsocketConnect;
|
||||
}
|
||||
|
||||
public function getIcon(): ?BackedEnum
|
||||
{
|
||||
[$group, $permission] = $this->split();
|
||||
|
||||
return match ($group) {
|
||||
'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,
|
||||
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
35
app/Enums/WebhookType.php
Normal file
35
app/Enums/WebhookType.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
use BackedEnum;
|
||||
use Filament\Support\Contracts\HasColor;
|
||||
use Filament\Support\Contracts\HasIcon;
|
||||
use Filament\Support\Contracts\HasLabel;
|
||||
|
||||
enum WebhookType: string implements HasColor, HasIcon, HasLabel
|
||||
{
|
||||
case Regular = 'regular';
|
||||
case Discord = 'discord';
|
||||
|
||||
public function getLabel(): string
|
||||
{
|
||||
return trans('admin/webhook.' . $this->value);
|
||||
}
|
||||
|
||||
public function getColor(): ?string
|
||||
{
|
||||
return match ($this) {
|
||||
self::Regular => null,
|
||||
self::Discord => 'blurple',
|
||||
};
|
||||
}
|
||||
|
||||
public function getIcon(): BackedEnum
|
||||
{
|
||||
return match ($this) {
|
||||
self::Regular => TablerIcon::WorldWww,
|
||||
self::Discord => TablerIcon::BrandDiscord,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
namespace App\Events;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
use App\Models\ActivityLog;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class ActivityLogged extends Event
|
||||
{
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
namespace App\Events\Auth;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Events\Event;
|
||||
use App\Models\User;
|
||||
|
||||
class ProvidedAuthenticationToken extends Event
|
||||
{
|
||||
|
||||
@@ -4,13 +4,14 @@ namespace App\Exceptions;
|
||||
|
||||
use Exception;
|
||||
use Filament\Notifications\Notification;
|
||||
use Illuminate\Container\Container;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Container\Container;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
@@ -28,7 +29,7 @@ class DisplayException extends PanelException implements HttpExceptionInterface
|
||||
/**
|
||||
* DisplayException constructor.
|
||||
*/
|
||||
public function __construct(string $message, ?\Throwable $previous = null, protected string $level = self::LEVEL_ERROR, int $code = 0)
|
||||
public function __construct(string $message, ?Throwable $previous = null, protected string $level = self::LEVEL_ERROR, int $code = 0)
|
||||
{
|
||||
parent::__construct($message, $code, $previous);
|
||||
}
|
||||
@@ -79,11 +80,11 @@ class DisplayException extends PanelException implements HttpExceptionInterface
|
||||
* Log the exception to the logs using the defined error level only if the previous
|
||||
* exception is set.
|
||||
*
|
||||
* @throws \Throwable
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function report(): void
|
||||
{
|
||||
if (!$this->getPrevious() instanceof \Exception || !Handler::isReportable($this->getPrevious())) {
|
||||
if (!$this->getPrevious() instanceof Exception || !Handler::isReportable($this->getPrevious())) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,24 +2,27 @@
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Support\Collection;
|
||||
use Exception;
|
||||
use Illuminate\Auth\Access\AuthorizationException;
|
||||
use Illuminate\Auth\AuthenticationException;
|
||||
use Illuminate\Container\Container;
|
||||
use Illuminate\Database\Connection;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Foundation\Application;
|
||||
use Illuminate\Auth\AuthenticationException;
|
||||
use Illuminate\Session\TokenMismatchException;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Illuminate\Auth\Access\AuthorizationException;
|
||||
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||
use Symfony\Component\HttpKernel\Exception\HttpException;
|
||||
use Symfony\Component\Mailer\Exception\TransportException;
|
||||
use Illuminate\Foundation\Application;
|
||||
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Session\TokenMismatchException;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use PDOException;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\Exception\HttpException;
|
||||
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
|
||||
use Symfony\Component\Mailer\Exception\TransportException;
|
||||
use Throwable;
|
||||
|
||||
class Handler extends ExceptionHandler
|
||||
@@ -79,7 +82,7 @@ class Handler extends ExceptionHandler
|
||||
$this->dontReport = [];
|
||||
}
|
||||
|
||||
$this->reportable(function (\PDOException $ex) {
|
||||
$this->reportable(function (PDOException $ex) {
|
||||
$ex = $this->generateCleanedExceptionStack($ex);
|
||||
});
|
||||
|
||||
@@ -88,7 +91,7 @@ class Handler extends ExceptionHandler
|
||||
});
|
||||
}
|
||||
|
||||
private function generateCleanedExceptionStack(\Throwable $exception): string
|
||||
private function generateCleanedExceptionStack(Throwable $exception): string
|
||||
{
|
||||
$cleanedStack = '';
|
||||
foreach ($exception->getTrace() as $index => $item) {
|
||||
@@ -117,11 +120,11 @@ class Handler extends ExceptionHandler
|
||||
/**
|
||||
* Render an exception into an HTTP response.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param Request $request
|
||||
*
|
||||
* @throws \Throwable
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function render($request, \Throwable $e): Response
|
||||
public function render($request, Throwable $e): Response
|
||||
{
|
||||
$connections = $this->container->make(Connection::class);
|
||||
|
||||
@@ -143,7 +146,7 @@ class Handler extends ExceptionHandler
|
||||
* Transform a validation exception into a consistent format to be returned for
|
||||
* calls to the API.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param Request $request
|
||||
*/
|
||||
public function invalidJson($request, ValidationException $exception): JsonResponse
|
||||
{
|
||||
@@ -249,7 +252,7 @@ class Handler extends ExceptionHandler
|
||||
/**
|
||||
* Return an array of exceptions that should not be reported.
|
||||
*/
|
||||
public static function isReportable(\Exception $exception): bool
|
||||
public static function isReportable(Exception $exception): bool
|
||||
{
|
||||
return (new self(Container::getInstance()))->shouldReport($exception);
|
||||
}
|
||||
@@ -257,7 +260,7 @@ class Handler extends ExceptionHandler
|
||||
/**
|
||||
* Convert an authentication exception into an unauthenticated response.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param Request $request
|
||||
*/
|
||||
protected function unauthenticated($request, AuthenticationException $exception): JsonResponse|RedirectResponse
|
||||
{
|
||||
@@ -291,7 +294,7 @@ class Handler extends ExceptionHandler
|
||||
*
|
||||
* @return array<mixed>
|
||||
*/
|
||||
public static function toArray(\Throwable $e): array
|
||||
public static function toArray(Throwable $e): array
|
||||
{
|
||||
return self::exceptionToArray($e);
|
||||
}
|
||||
|
||||
@@ -4,13 +4,14 @@ namespace App\Exceptions\Http;
|
||||
|
||||
use Illuminate\Http\Response;
|
||||
use Symfony\Component\HttpKernel\Exception\HttpException;
|
||||
use Throwable;
|
||||
|
||||
class HttpForbiddenException extends HttpException
|
||||
{
|
||||
/**
|
||||
* HttpForbiddenException constructor.
|
||||
*/
|
||||
public function __construct(?string $message = null, ?\Throwable $previous = null)
|
||||
public function __construct(?string $message = null, ?Throwable $previous = null)
|
||||
{
|
||||
parent::__construct(Response::HTTP_FORBIDDEN, $message, $previous);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace App\Exceptions\Http\Server;
|
||||
use App\Enums\ServerState;
|
||||
use App\Models\Server;
|
||||
use Symfony\Component\HttpKernel\Exception\ConflictHttpException;
|
||||
use Throwable;
|
||||
|
||||
class ServerStateConflictException extends ConflictHttpException
|
||||
{
|
||||
@@ -12,7 +13,7 @@ class ServerStateConflictException extends ConflictHttpException
|
||||
* Exception thrown when the server is in an unsupported state for API access or
|
||||
* certain operations within the codebase.
|
||||
*/
|
||||
public function __construct(Server $server, ?\Throwable $previous = null)
|
||||
public function __construct(Server $server, ?Throwable $previous = null)
|
||||
{
|
||||
$message = 'This server is currently in an unsupported state, please try again later.';
|
||||
if ($server->isSuspended()) {
|
||||
|
||||
@@ -2,13 +2,15 @@
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
use Spatie\Ignition\Contracts\Solution;
|
||||
use App\Exceptions\Solutions\ManifestDoesNotExistSolution;
|
||||
use Exception;
|
||||
use Spatie\Ignition\Contracts\ProvidesSolution;
|
||||
use Spatie\Ignition\Contracts\Solution;
|
||||
|
||||
class ManifestDoesNotExistException extends \Exception implements ProvidesSolution
|
||||
class ManifestDoesNotExistException extends Exception implements ProvidesSolution
|
||||
{
|
||||
public function getSolution(): Solution
|
||||
{
|
||||
return new Solutions\ManifestDoesNotExistSolution();
|
||||
return new ManifestDoesNotExistSolution();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
|
||||
namespace App\Exceptions\Model;
|
||||
|
||||
use Illuminate\Support\MessageBag;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Contracts\Validation\Validator;
|
||||
use App\Exceptions\PanelException;
|
||||
use Illuminate\Contracts\Support\MessageProvider;
|
||||
use Illuminate\Contracts\Validation\Validator;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\MessageBag;
|
||||
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
|
||||
|
||||
class DataValidationException extends PanelException implements HttpExceptionInterface, MessageProvider
|
||||
|
||||
@@ -2,4 +2,6 @@
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
class PanelException extends \Exception {}
|
||||
use Exception;
|
||||
|
||||
class PanelException extends Exception {}
|
||||
|
||||
7
app/Exceptions/Repository/FileExistsException.php
Normal file
7
app/Exceptions/Repository/FileExistsException.php
Normal file
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions\Repository;
|
||||
|
||||
use Exception;
|
||||
|
||||
class FileExistsException extends Exception {}
|
||||
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions\Service\Deployment;
|
||||
|
||||
use App\Exceptions\DisplayException;
|
||||
|
||||
class NoViableNodeException extends DisplayException {}
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
namespace App\Exceptions\Service;
|
||||
|
||||
use Illuminate\Http\Response;
|
||||
use App\Exceptions\DisplayException;
|
||||
use Illuminate\Http\Response;
|
||||
|
||||
class HasActiveServersException extends DisplayException
|
||||
{
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Exceptions\Service;
|
||||
|
||||
use App\Exceptions\DisplayException;
|
||||
use Throwable;
|
||||
|
||||
class ServiceLimitExceededException extends DisplayException
|
||||
{
|
||||
@@ -10,7 +11,7 @@ class ServiceLimitExceededException extends DisplayException
|
||||
* Exception thrown when something goes over a defined limit, such as allocated
|
||||
* ports, tasks, databases, etc.
|
||||
*/
|
||||
public function __construct(string $message, ?\Throwable $previous = null)
|
||||
public function __construct(string $message, ?Throwable $previous = null)
|
||||
{
|
||||
parent::__construct($message, $previous, self::LEVEL_WARNING);
|
||||
}
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions\Service\User;
|
||||
|
||||
use App\Exceptions\DisplayException;
|
||||
|
||||
class TwoFactorAuthenticationTokenInvalid extends DisplayException
|
||||
{
|
||||
public string $title = 'Invalid 2FA Code';
|
||||
|
||||
public string $icon = 'tabler-2fa';
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct('The provided two-factor authentication token was not valid.');
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Extensions\Avatar;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
abstract class AvatarProvider
|
||||
{
|
||||
/**
|
||||
* @var array<string, static>
|
||||
*/
|
||||
protected static array $providers = [];
|
||||
|
||||
public static function getProvider(string $id): ?self
|
||||
{
|
||||
return Arr::get(static::$providers, $id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, static>
|
||||
*/
|
||||
public static function getAll(): array
|
||||
{
|
||||
return static::$providers;
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
static::$providers[$this->getId()] = $this;
|
||||
}
|
||||
|
||||
abstract public function getId(): string;
|
||||
|
||||
abstract public function get(User $user): ?string;
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return Str::title($this->getId());
|
||||
}
|
||||
}
|
||||
14
app/Extensions/Avatar/AvatarSchemaInterface.php
Normal file
14
app/Extensions/Avatar/AvatarSchemaInterface.php
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace App\Extensions\Avatar;
|
||||
|
||||
use App\Models\User;
|
||||
|
||||
interface AvatarSchemaInterface
|
||||
{
|
||||
public function getId(): string;
|
||||
|
||||
public function getName(): string;
|
||||
|
||||
public function get(User $user): ?string;
|
||||
}
|
||||
55
app/Extensions/Avatar/AvatarService.php
Normal file
55
app/Extensions/Avatar/AvatarService.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace App\Extensions\Avatar;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class AvatarService
|
||||
{
|
||||
/** @var AvatarSchemaInterface[] */
|
||||
private array $schemas = [];
|
||||
|
||||
public function __construct(
|
||||
private readonly bool $allowUploadedAvatars,
|
||||
private readonly string $activeSchema,
|
||||
) {}
|
||||
|
||||
public function get(string $id): ?AvatarSchemaInterface
|
||||
{
|
||||
return array_get($this->schemas, $id);
|
||||
}
|
||||
|
||||
public function getActiveSchema(): ?AvatarSchemaInterface
|
||||
{
|
||||
return $this->get($this->activeSchema);
|
||||
}
|
||||
|
||||
public function getAvatarUrl(User $user): ?string
|
||||
{
|
||||
if ($this->allowUploadedAvatars) {
|
||||
$path = "avatars/$user->id.png";
|
||||
|
||||
if (Storage::disk('public')->exists($path)) {
|
||||
return Storage::url($path);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->getActiveSchema()?->get($user);
|
||||
}
|
||||
|
||||
public function register(AvatarSchemaInterface $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();
|
||||
}
|
||||
}
|
||||
@@ -1,24 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\Extensions\Avatar\Providers;
|
||||
namespace App\Extensions\Avatar\Schemas;
|
||||
|
||||
use App\Extensions\Avatar\AvatarProvider;
|
||||
use App\Extensions\Avatar\AvatarSchemaInterface;
|
||||
use App\Models\User;
|
||||
|
||||
class GravatarProvider extends AvatarProvider
|
||||
class GravatarSchema implements AvatarSchemaInterface
|
||||
{
|
||||
public function getId(): string
|
||||
{
|
||||
return 'gravatar';
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return 'Gravatar';
|
||||
}
|
||||
|
||||
public function get(User $user): string
|
||||
{
|
||||
return 'https://gravatar.com/avatar/' . md5($user->email);
|
||||
}
|
||||
|
||||
public static function register(): self
|
||||
{
|
||||
return new self();
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Extensions\Avatar\Providers;
|
||||
namespace App\Extensions\Avatar\Schemas;
|
||||
|
||||
use App\Extensions\Avatar\AvatarProvider;
|
||||
use App\Extensions\Avatar\AvatarSchemaInterface;
|
||||
use App\Models\User;
|
||||
|
||||
class UiAvatarsProvider extends AvatarProvider
|
||||
class UiAvatarsSchema implements AvatarSchemaInterface
|
||||
{
|
||||
public function getId(): string
|
||||
{
|
||||
@@ -22,9 +22,4 @@ class UiAvatarsProvider extends AvatarProvider
|
||||
// UI Avatars is the default of filament so just return null here
|
||||
return null;
|
||||
}
|
||||
|
||||
public static function register(): self
|
||||
{
|
||||
return new self();
|
||||
}
|
||||
}
|
||||
@@ -2,15 +2,16 @@
|
||||
|
||||
namespace App\Extensions\Backups;
|
||||
|
||||
use Closure;
|
||||
use App\Extensions\Filesystem\S3Filesystem;
|
||||
use Aws\S3\S3Client;
|
||||
use Closure;
|
||||
use Illuminate\Foundation\Application;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Str;
|
||||
use Webmozart\Assert\Assert;
|
||||
use Illuminate\Foundation\Application;
|
||||
use InvalidArgumentException;
|
||||
use League\Flysystem\FilesystemAdapter;
|
||||
use App\Extensions\Filesystem\S3Filesystem;
|
||||
use League\Flysystem\InMemory\InMemoryFilesystemAdapter;
|
||||
use Webmozart\Assert\Assert;
|
||||
|
||||
class BackupManager
|
||||
{
|
||||
@@ -64,7 +65,7 @@ class BackupManager
|
||||
$config = $this->getConfig($name);
|
||||
|
||||
if (empty($config['adapter'])) {
|
||||
throw new \InvalidArgumentException("Backup disk [$name] does not have a configured adapter.");
|
||||
throw new InvalidArgumentException("Backup disk [$name] does not have a configured adapter.");
|
||||
}
|
||||
|
||||
$adapter = $config['adapter'];
|
||||
@@ -82,7 +83,7 @@ class BackupManager
|
||||
return $instance;
|
||||
}
|
||||
|
||||
throw new \InvalidArgumentException("Adapter [$adapter] is not supported.");
|
||||
throw new InvalidArgumentException("Adapter [$adapter] is not supported.");
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
48
app/Extensions/Captcha/CaptchaService.php
Normal file
48
app/Extensions/Captcha/CaptchaService.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace App\Extensions\Captcha;
|
||||
|
||||
use App\Extensions\Captcha\Schemas\CaptchaSchemaInterface;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class CaptchaService
|
||||
{
|
||||
/** @var array<string, CaptchaSchemaInterface> */
|
||||
private array $schemas = [];
|
||||
|
||||
/**
|
||||
* @return CaptchaSchemaInterface[]
|
||||
*/
|
||||
public function getAll(): array
|
||||
{
|
||||
return $this->schemas;
|
||||
}
|
||||
|
||||
public function get(string $id): ?CaptchaSchemaInterface
|
||||
{
|
||||
return array_get($this->schemas, $id);
|
||||
}
|
||||
|
||||
public function register(CaptchaSchemaInterface $schema): void
|
||||
{
|
||||
if (array_key_exists($schema->getId(), $this->schemas)) {
|
||||
return;
|
||||
}
|
||||
|
||||
config()->set('captcha.' . Str::lower($schema->getId()), $schema->getConfig());
|
||||
$this->schemas[$schema->getId()] = $schema;
|
||||
}
|
||||
|
||||
/** @return Collection<CaptchaSchemaInterface> */
|
||||
public function getActiveSchemas(): Collection
|
||||
{
|
||||
return collect($this->schemas)
|
||||
->filter(fn (CaptchaSchemaInterface $schema) => $schema->isEnabled());
|
||||
}
|
||||
|
||||
public function getActiveSchema(): ?CaptchaSchemaInterface
|
||||
{
|
||||
return $this->getActiveSchemas()->first();
|
||||
}
|
||||
}
|
||||
@@ -1,118 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Extensions\Captcha\Providers;
|
||||
|
||||
use Filament\Forms\Components\Component;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Illuminate\Foundation\Application;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
abstract class CaptchaProvider
|
||||
{
|
||||
/**
|
||||
* @var array<string, static>
|
||||
*/
|
||||
protected static array $providers = [];
|
||||
|
||||
/**
|
||||
* @return self|static[]
|
||||
*/
|
||||
public static function get(?string $id = null): array|self
|
||||
{
|
||||
return $id ? static::$providers[$id] : static::$providers;
|
||||
}
|
||||
|
||||
protected function __construct(protected Application $app)
|
||||
{
|
||||
if (array_key_exists($this->getId(), static::$providers)) {
|
||||
if (!$this->app->runningUnitTests()) {
|
||||
logger()->warning("Tried to create duplicate Captcha provider with id '{$this->getId()}'");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
config()->set('captcha.' . Str::lower($this->getId()), $this->getConfig());
|
||||
|
||||
static::$providers[$this->getId()] = $this;
|
||||
}
|
||||
|
||||
abstract public function getId(): string;
|
||||
|
||||
abstract public function getComponent(): Component;
|
||||
|
||||
/**
|
||||
* @return array<string, string|string[]|bool|null>
|
||||
*/
|
||||
public function getConfig(): array
|
||||
{
|
||||
$id = Str::upper($this->getId());
|
||||
|
||||
return [
|
||||
'site_key' => env("CAPTCHA_{$id}_SITE_KEY"),
|
||||
'secret_key' => env("CAPTCHA_{$id}_SECRET_KEY"),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Component[]
|
||||
*/
|
||||
public function getSettingsForm(): array
|
||||
{
|
||||
$id = Str::upper($this->getId());
|
||||
|
||||
return [
|
||||
TextInput::make("CAPTCHA_{$id}_SITE_KEY")
|
||||
->label('Site Key')
|
||||
->placeholder('Site Key')
|
||||
->columnSpan(2)
|
||||
->required()
|
||||
->password()
|
||||
->revealable()
|
||||
->autocomplete(false)
|
||||
->default(env("CAPTCHA_{$id}_SITE_KEY")),
|
||||
TextInput::make("CAPTCHA_{$id}_SECRET_KEY")
|
||||
->label('Secret Key')
|
||||
->placeholder('Secret Key')
|
||||
->columnSpan(2)
|
||||
->required()
|
||||
->password()
|
||||
->revealable()
|
||||
->autocomplete(false)
|
||||
->default(env("CAPTCHA_{$id}_SECRET_KEY")),
|
||||
];
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return Str::title($this->getId());
|
||||
}
|
||||
|
||||
public function getIcon(): ?string
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public function isEnabled(): bool
|
||||
{
|
||||
$id = Str::upper($this->getId());
|
||||
|
||||
return env("CAPTCHA_{$id}_ENABLED", false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, string|bool>
|
||||
*/
|
||||
public function validateResponse(?string $captchaResponse = null): array
|
||||
{
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => 'validateResponse not defined',
|
||||
];
|
||||
}
|
||||
|
||||
public function verifyDomain(string $hostname, ?string $requestUrl = null): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Extensions\Captcha\Providers;
|
||||
|
||||
use App\Filament\Components\Forms\Fields\TurnstileCaptcha;
|
||||
use Exception;
|
||||
use Filament\Forms\Components\Component;
|
||||
use Filament\Forms\Components\Placeholder;
|
||||
use Filament\Forms\Components\Toggle;
|
||||
use Illuminate\Foundation\Application;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\HtmlString;
|
||||
|
||||
class TurnstileProvider extends CaptchaProvider
|
||||
{
|
||||
public function getId(): string
|
||||
{
|
||||
return 'turnstile';
|
||||
}
|
||||
|
||||
public function getComponent(): Component
|
||||
{
|
||||
return TurnstileCaptcha::make('turnstile');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, string|string[]|bool|null>
|
||||
*/
|
||||
public function getConfig(): array
|
||||
{
|
||||
return array_merge(parent::getConfig(), [
|
||||
'verify_domain' => env('CAPTCHA_TURNSTILE_VERIFY_DOMAIN'),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Component[]
|
||||
*/
|
||||
public function getSettingsForm(): array
|
||||
{
|
||||
return array_merge(parent::getSettingsForm(), [
|
||||
Toggle::make('CAPTCHA_TURNSTILE_VERIFY_DOMAIN')
|
||||
->label(trans('admin/setting.captcha.verify'))
|
||||
->columnSpan(2)
|
||||
->inline(false)
|
||||
->onIcon('tabler-check')
|
||||
->offIcon('tabler-x')
|
||||
->onColor('success')
|
||||
->offColor('danger')
|
||||
->default(env('CAPTCHA_TURNSTILE_VERIFY_DOMAIN', true)),
|
||||
Placeholder::make('info')
|
||||
->label(trans('admin/setting.captcha.info_label'))
|
||||
->columnSpan(2)
|
||||
->content(new HtmlString(trans('admin/setting.captcha.info'))),
|
||||
|
||||
]);
|
||||
}
|
||||
|
||||
public function getIcon(): string
|
||||
{
|
||||
return 'tabler-brand-cloudflare';
|
||||
}
|
||||
|
||||
public static function register(Application $app): self
|
||||
{
|
||||
return new self($app);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, string|bool>
|
||||
*/
|
||||
public function validateResponse(?string $captchaResponse = null): array
|
||||
{
|
||||
$captchaResponse ??= request()->get('cf-turnstile-response');
|
||||
|
||||
if (!$secret = env('CAPTCHA_TURNSTILE_SECRET_KEY')) {
|
||||
throw new Exception('Turnstile secret key is not defined.');
|
||||
}
|
||||
|
||||
$response = Http::asJson()
|
||||
->timeout(15)
|
||||
->connectTimeout(5)
|
||||
->throw()
|
||||
->post('https://challenges.cloudflare.com/turnstile/v0/siteverify', [
|
||||
'secret' => $secret,
|
||||
'response' => $captchaResponse,
|
||||
]);
|
||||
|
||||
return count($response->json()) ? $response->json() : [
|
||||
'success' => false,
|
||||
'message' => 'Unknown error occurred, please try again',
|
||||
];
|
||||
}
|
||||
|
||||
public function verifyDomain(string $hostname, ?string $requestUrl = null): bool
|
||||
{
|
||||
if (!env('CAPTCHA_TURNSTILE_VERIFY_DOMAIN', true)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$requestUrl ??= request()->url;
|
||||
$requestUrl = parse_url($requestUrl);
|
||||
|
||||
return $hostname === array_get($requestUrl, 'host');
|
||||
}
|
||||
}
|
||||
59
app/Extensions/Captcha/Schemas/BaseSchema.php
Normal file
59
app/Extensions/Captcha/Schemas/BaseSchema.php
Normal file
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace App\Extensions\Captcha\Schemas;
|
||||
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Schemas\Components\Component;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
abstract class BaseSchema
|
||||
{
|
||||
abstract public function getId(): string;
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return Str::upper($this->getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, string|string[]|bool|null>
|
||||
*/
|
||||
public function getConfig(): array
|
||||
{
|
||||
$id = Str::upper($this->getId());
|
||||
|
||||
return [
|
||||
'site_key' => env("CAPTCHA_{$id}_SITE_KEY"),
|
||||
'secret_key' => env("CAPTCHA_{$id}_SECRET_KEY"),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Component[]
|
||||
*/
|
||||
public function getSettingsForm(): array
|
||||
{
|
||||
$id = Str::upper($this->getId());
|
||||
|
||||
return [
|
||||
TextInput::make("CAPTCHA_{$id}_SITE_KEY")
|
||||
->label('Site Key')
|
||||
->placeholder('Site Key')
|
||||
->columnSpan(2)
|
||||
->required()
|
||||
->password()
|
||||
->revealable()
|
||||
->autocomplete(false)
|
||||
->default(env("CAPTCHA_{$id}_SITE_KEY")),
|
||||
TextInput::make("CAPTCHA_{$id}_SECRET_KEY")
|
||||
->label('Secret Key')
|
||||
->placeholder('Secret Key')
|
||||
->columnSpan(2)
|
||||
->required()
|
||||
->password()
|
||||
->revealable()
|
||||
->autocomplete(false)
|
||||
->default(env("CAPTCHA_{$id}_SECRET_KEY")),
|
||||
];
|
||||
}
|
||||
}
|
||||
31
app/Extensions/Captcha/Schemas/CaptchaSchemaInterface.php
Normal file
31
app/Extensions/Captcha/Schemas/CaptchaSchemaInterface.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\Extensions\Captcha\Schemas;
|
||||
|
||||
use BackedEnum;
|
||||
use Filament\Schemas\Components\Component;
|
||||
|
||||
interface CaptchaSchemaInterface
|
||||
{
|
||||
public function getId(): string;
|
||||
|
||||
public function getName(): string;
|
||||
|
||||
/**
|
||||
* @return array<string, string|string[]|bool|null>
|
||||
*/
|
||||
public function getConfig(): array;
|
||||
|
||||
public function isEnabled(): bool;
|
||||
|
||||
public function getFormComponent(): Component;
|
||||
|
||||
/**
|
||||
* @return Component[]
|
||||
*/
|
||||
public function getSettingsForm(): array;
|
||||
|
||||
public function getIcon(): null|string|BackedEnum;
|
||||
|
||||
public function validateResponse(?string $captchaResponse = null): void;
|
||||
}
|
||||
@@ -1,11 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Components\Forms\Fields;
|
||||
namespace App\Extensions\Captcha\Schemas\Turnstile;
|
||||
|
||||
use App\Rules\ValidTurnstileCaptcha;
|
||||
use Filament\Forms\Components\Field;
|
||||
|
||||
class TurnstileCaptcha extends Field
|
||||
class Component extends Field
|
||||
{
|
||||
protected string $viewIdentifier = 'turnstile';
|
||||
|
||||
@@ -19,8 +18,6 @@ class TurnstileCaptcha extends Field
|
||||
|
||||
$this->required();
|
||||
|
||||
$this->after(function (TurnstileCaptcha $component) {
|
||||
$component->rule(new ValidTurnstileCaptcha());
|
||||
});
|
||||
$this->rule(new Rule());
|
||||
}
|
||||
}
|
||||
23
app/Extensions/Captcha/Schemas/Turnstile/Rule.php
Normal file
23
app/Extensions/Captcha/Schemas/Turnstile/Rule.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace App\Extensions\Captcha\Schemas\Turnstile;
|
||||
|
||||
use App\Extensions\Captcha\CaptchaService;
|
||||
use Closure;
|
||||
use Exception;
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
use Illuminate\Support\Facades\App;
|
||||
|
||||
class Rule implements ValidationRule
|
||||
{
|
||||
public function validate(string $attribute, mixed $value, Closure $fail): void
|
||||
{
|
||||
try {
|
||||
App::call(fn (CaptchaService $service) => $service->get('turnstile')->validateResponse($value));
|
||||
} catch (Exception $exception) {
|
||||
report($exception);
|
||||
|
||||
$fail('Captcha validation failed: ' . $exception->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
119
app/Extensions/Captcha/Schemas/Turnstile/TurnstileSchema.php
Normal file
119
app/Extensions/Captcha/Schemas/Turnstile/TurnstileSchema.php
Normal file
@@ -0,0 +1,119 @@
|
||||
<?php
|
||||
|
||||
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;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\HtmlString;
|
||||
|
||||
class TurnstileSchema extends BaseSchema implements CaptchaSchemaInterface
|
||||
{
|
||||
public function getId(): string
|
||||
{
|
||||
return 'turnstile';
|
||||
}
|
||||
|
||||
public function isEnabled(): bool
|
||||
{
|
||||
return env('CAPTCHA_TURNSTILE_ENABLED', false);
|
||||
}
|
||||
|
||||
public function getFormComponent(): Component
|
||||
{
|
||||
return Component::make('turnstile');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, string|string[]|bool|null>
|
||||
*/
|
||||
public function getConfig(): array
|
||||
{
|
||||
return array_merge(parent::getConfig(), [
|
||||
'verify_domain' => env('CAPTCHA_TURNSTILE_VERIFY_DOMAIN'),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Filament\Support\Components\Component[]
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function getSettingsForm(): array
|
||||
{
|
||||
return array_merge(parent::getSettingsForm(), [
|
||||
Toggle::make('CAPTCHA_TURNSTILE_VERIFY_DOMAIN')
|
||||
->label(trans('admin/setting.captcha.verify'))
|
||||
->columnSpan(2)
|
||||
->inline(false)
|
||||
->onIcon(TablerIcon::Check)
|
||||
->offIcon(TablerIcon::X)
|
||||
->onColor('success')
|
||||
->offColor('danger')
|
||||
->default(env('CAPTCHA_TURNSTILE_VERIFY_DOMAIN', true)),
|
||||
TextEntry::make('info')
|
||||
->label(trans('admin/setting.captcha.info_label'))
|
||||
->columnSpan(2)
|
||||
->state(new HtmlString(trans('admin/setting.captcha.info'))),
|
||||
]);
|
||||
}
|
||||
|
||||
public function getIcon(): BackedEnum
|
||||
{
|
||||
return TablerIcon::BrandCloudflare;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function validateResponse(?string $captchaResponse = null): void
|
||||
{
|
||||
$captchaResponse ??= request()->get('cf-turnstile-response');
|
||||
|
||||
if (!$secret = env('CAPTCHA_TURNSTILE_SECRET_KEY')) {
|
||||
throw new Exception('Turnstile secret key is not defined.');
|
||||
}
|
||||
|
||||
$response = Http::asJson()
|
||||
->timeout(15)
|
||||
->connectTimeout(5)
|
||||
->throw()
|
||||
->post('https://challenges.cloudflare.com/turnstile/v0/siteverify', [
|
||||
'secret' => $secret,
|
||||
'response' => $captchaResponse,
|
||||
])
|
||||
->json();
|
||||
|
||||
if (!$response['success']) {
|
||||
match ($response['error-codes'][0] ?? null) {
|
||||
'missing-input-secret' => throw new Exception('The secret parameter was not passed.'),
|
||||
'invalid-input-secret' => throw new Exception('The secret parameter was invalid, did not exist, or is a testing secret key with a non-testing response.'),
|
||||
'missing-input-response' => throw new Exception('The response parameter (token) was not passed.'),
|
||||
'invalid-input-response' => throw new Exception('The response parameter (token) is invalid or has expired.'),
|
||||
'bad-request' => throw new Exception('The request was rejected because it was malformed.'),
|
||||
'timeout-or-duplicate' => throw new Exception('The response parameter (token) has already been validated before.'),
|
||||
default => throw new Exception('An internal error happened while validating the response.'),
|
||||
};
|
||||
}
|
||||
|
||||
if (!$this->verifyDomain($response['hostname'] ?? '')) {
|
||||
throw new Exception('Domain verification failed.');
|
||||
}
|
||||
}
|
||||
|
||||
private function verifyDomain(string $hostname): bool
|
||||
{
|
||||
if (!env('CAPTCHA_TURNSTILE_VERIFY_DOMAIN', true)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$requestUrl = parse_url(request()->url());
|
||||
|
||||
return $hostname === array_get($requestUrl, 'host');
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Extensions\Features;
|
||||
|
||||
use Filament\Actions\Action;
|
||||
use Illuminate\Foundation\Application;
|
||||
|
||||
abstract class FeatureProvider
|
||||
{
|
||||
/**
|
||||
* @var array<string, static>
|
||||
*/
|
||||
protected static array $providers = [];
|
||||
|
||||
/**
|
||||
* @param string[] $id
|
||||
* @return self|static[]
|
||||
*/
|
||||
public static function getProviders(string|array|null $id = null): array|self
|
||||
{
|
||||
if (is_array($id)) {
|
||||
return array_intersect_key(static::$providers, array_flip($id));
|
||||
}
|
||||
|
||||
return $id ? static::$providers[$id] : static::$providers;
|
||||
}
|
||||
|
||||
protected function __construct(protected Application $app)
|
||||
{
|
||||
if (array_key_exists($this->getId(), static::$providers)) {
|
||||
if (!$this->app->runningUnitTests()) {
|
||||
logger()->warning("Tried to create duplicate Feature provider with id '{$this->getId()}'");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
static::$providers[$this->getId()] = $this;
|
||||
}
|
||||
|
||||
abstract public function getId(): string;
|
||||
|
||||
/**
|
||||
* A matching subset string (case-insensitive) from the console output
|
||||
*
|
||||
* @return array<string>
|
||||
*/
|
||||
abstract public function getListeners(): array;
|
||||
|
||||
abstract public function getAction(): Action;
|
||||
}
|
||||
15
app/Extensions/Features/FeatureSchemaInterface.php
Normal file
15
app/Extensions/Features/FeatureSchemaInterface.php
Normal file
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace App\Extensions\Features;
|
||||
|
||||
use Filament\Actions\Action;
|
||||
|
||||
interface FeatureSchemaInterface
|
||||
{
|
||||
/** @return string[] */
|
||||
public function getListeners(): array;
|
||||
|
||||
public function getId(): string;
|
||||
|
||||
public function getAction(): Action;
|
||||
}
|
||||
52
app/Extensions/Features/FeatureService.php
Normal file
52
app/Extensions/Features/FeatureService.php
Normal file
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace App\Extensions\Features;
|
||||
|
||||
class FeatureService
|
||||
{
|
||||
/** @var FeatureSchemaInterface[] */
|
||||
private array $schemas = [];
|
||||
|
||||
/**
|
||||
* @return FeatureSchemaInterface[]
|
||||
*/
|
||||
public function getAll(): array
|
||||
{
|
||||
return $this->schemas;
|
||||
}
|
||||
|
||||
public function get(string $id): ?FeatureSchemaInterface
|
||||
{
|
||||
return array_get($this->schemas, $id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ?string[] $features
|
||||
* @return FeatureSchemaInterface[]
|
||||
*/
|
||||
public function getActiveSchemas(?array $features = []): array
|
||||
{
|
||||
return collect($this->schemas)->only($features)->all();
|
||||
}
|
||||
|
||||
public function register(FeatureSchemaInterface $schema): void
|
||||
{
|
||||
if (array_key_exists($schema->getId(), $this->schemas)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->schemas[$schema->getId()] = $schema;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ?string[] $features
|
||||
* @return array<string, array<string>>
|
||||
*/
|
||||
public function getMappings(?array $features = []): array
|
||||
{
|
||||
return collect($this->getActiveSchemas($features))
|
||||
->mapWithKeys(fn (FeatureSchemaInterface $schema) => [
|
||||
$schema->getId() => $schema->getListeners(),
|
||||
])->all();
|
||||
}
|
||||
}
|
||||
@@ -1,32 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace App\Extensions\Features;
|
||||
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\Permission;
|
||||
use App\Models\Server;
|
||||
use App\Models\ServerVariable;
|
||||
use App\Repositories\Daemon\DaemonPowerRepository;
|
||||
use App\Repositories\Daemon\DaemonServerRepository;
|
||||
use Closure;
|
||||
use Exception;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Facades\Filament;
|
||||
use Filament\Forms\Components\Placeholder;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Infolists\Components\TextEntry;
|
||||
use Filament\Notifications\Notification;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Foundation\Application;
|
||||
use Illuminate\Support\Facades\Blade;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Support\HtmlString;
|
||||
|
||||
class GSLToken extends FeatureProvider
|
||||
class GSLTokenSchema implements FeatureSchemaInterface
|
||||
{
|
||||
public function __construct(protected Application $app)
|
||||
{
|
||||
parent::__construct($app);
|
||||
}
|
||||
|
||||
/** @return array<string> */
|
||||
public function getListeners(): array
|
||||
{
|
||||
@@ -41,6 +37,9 @@ class GSLToken extends FeatureProvider
|
||||
return 'gsl_token';
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function getAction(): Action
|
||||
{
|
||||
/** @var Server $server */
|
||||
@@ -56,9 +55,9 @@ class GSLToken extends FeatureProvider
|
||||
->modalHeading('Invalid GSL token')
|
||||
->modalDescription('It seems like your Gameserver Login Token (GSL token) is invalid or has expired.')
|
||||
->modalSubmitActionLabel('Update GSL Token')
|
||||
->disabledForm(fn () => !auth()->user()->can(Permission::ACTION_STARTUP_UPDATE, $server))
|
||||
->form([
|
||||
Placeholder::make('info')
|
||||
->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.'))),
|
||||
TextInput::make('gsltoken')
|
||||
->label('GSL Token')
|
||||
@@ -75,13 +74,12 @@ class GSLToken extends FeatureProvider
|
||||
}
|
||||
},
|
||||
])
|
||||
->hintIcon('tabler-code')
|
||||
->hintIcon(TablerIcon::Code, fn () => implode('|', $serverVariable->variable->rules))
|
||||
->label(fn () => $serverVariable->variable->name)
|
||||
->hintIconTooltip(fn () => implode('|', $serverVariable->variable->rules))
|
||||
->prefix(fn () => '{{' . $serverVariable->variable->env_variable . '}}')
|
||||
->helperText(fn () => empty($serverVariable->variable->description) ? '—' : $serverVariable->variable->description),
|
||||
])
|
||||
->action(function (array $data, DaemonPowerRepository $powerRepository) use ($server, $serverVariable) {
|
||||
->action(function (array $data, DaemonServerRepository $serverRepository) use ($server, $serverVariable) {
|
||||
/** @var Server $server */
|
||||
$server = Filament::getTenant();
|
||||
try {
|
||||
@@ -103,7 +101,7 @@ class GSLToken extends FeatureProvider
|
||||
->log();
|
||||
}
|
||||
|
||||
$powerRepository->setServer($server)->send('restart');
|
||||
$serverRepository->setServer($server)->power('restart');
|
||||
|
||||
Notification::make()
|
||||
->title('GSL Token updated')
|
||||
@@ -119,9 +117,4 @@ class GSLToken extends FeatureProvider
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static function register(Application $app): self
|
||||
{
|
||||
return new self($app);
|
||||
}
|
||||
}
|
||||
@@ -1,26 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Extensions\Features;
|
||||
namespace App\Extensions\Features\Schemas;
|
||||
|
||||
use App\Enums\SubuserPermission;
|
||||
use App\Extensions\Features\FeatureSchemaInterface;
|
||||
use App\Facades\Activity;
|
||||
use App\Models\Permission;
|
||||
use App\Models\Server;
|
||||
use App\Repositories\Daemon\DaemonPowerRepository;
|
||||
use App\Repositories\Daemon\DaemonServerRepository;
|
||||
use Exception;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Facades\Filament;
|
||||
use Filament\Forms\Components\Placeholder;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Infolists\Components\TextEntry;
|
||||
use Filament\Notifications\Notification;
|
||||
use Illuminate\Foundation\Application;
|
||||
|
||||
class JavaVersion extends FeatureProvider
|
||||
class JavaVersionSchema implements FeatureSchemaInterface
|
||||
{
|
||||
public function __construct(protected Application $app)
|
||||
{
|
||||
parent::__construct($app);
|
||||
}
|
||||
|
||||
/** @return array<string> */
|
||||
public function getListeners(): array
|
||||
{
|
||||
@@ -49,9 +44,9 @@ class JavaVersion extends FeatureProvider
|
||||
->modalHeading('Unsupported Java Version')
|
||||
->modalDescription('This server is currently running an unsupported version of Java and cannot be started.')
|
||||
->modalSubmitActionLabel('Update Docker Image')
|
||||
->disabledForm(fn () => !auth()->user()->can(Permission::ACTION_STARTUP_DOCKER_IMAGE, $server))
|
||||
->form([
|
||||
Placeholder::make('java')
|
||||
->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.'),
|
||||
Select::make('image')
|
||||
->label('Docker Image')
|
||||
@@ -61,10 +56,9 @@ class JavaVersion extends FeatureProvider
|
||||
->default(fn () => $server->image)
|
||||
->notIn(fn () => $server->image)
|
||||
->required()
|
||||
->preload()
|
||||
->native(false),
|
||||
->preload(),
|
||||
])
|
||||
->action(function (array $data, DaemonPowerRepository $powerRepository) use ($server) {
|
||||
->action(function (array $data, DaemonServerRepository $serverRepository) use ($server) {
|
||||
try {
|
||||
$new = $data['image'];
|
||||
$original = $server->image;
|
||||
@@ -76,7 +70,7 @@ class JavaVersion extends FeatureProvider
|
||||
->log();
|
||||
}
|
||||
|
||||
$powerRepository->setServer($server)->send('restart');
|
||||
$serverRepository->setServer($server)->power('restart');
|
||||
|
||||
Notification::make()
|
||||
->title('Docker image updated')
|
||||
@@ -92,9 +86,4 @@ class JavaVersion extends FeatureProvider
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static function register(Application $app): self
|
||||
{
|
||||
return new self($app);
|
||||
}
|
||||
}
|
||||
@@ -1,25 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace App\Extensions\Features;
|
||||
namespace App\Extensions\Features\Schemas;
|
||||
|
||||
use App\Extensions\Features\FeatureSchemaInterface;
|
||||
use App\Models\Server;
|
||||
use App\Repositories\Daemon\DaemonFileRepository;
|
||||
use App\Repositories\Daemon\DaemonPowerRepository;
|
||||
use App\Repositories\Daemon\DaemonServerRepository;
|
||||
use Exception;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Facades\Filament;
|
||||
use Filament\Notifications\Notification;
|
||||
use Illuminate\Foundation\Application;
|
||||
use Illuminate\Support\Facades\Blade;
|
||||
use Illuminate\Support\HtmlString;
|
||||
|
||||
class MinecraftEula extends FeatureProvider
|
||||
class MinecraftEulaSchema implements FeatureSchemaInterface
|
||||
{
|
||||
public function __construct(protected Application $app)
|
||||
{
|
||||
parent::__construct($app);
|
||||
}
|
||||
|
||||
/** @return array<string> */
|
||||
public function getListeners(): array
|
||||
{
|
||||
@@ -40,14 +35,14 @@ class MinecraftEula extends FeatureProvider
|
||||
->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')
|
||||
->action(function (DaemonFileRepository $fileRepository, DaemonPowerRepository $powerRepository) {
|
||||
->action(function (DaemonFileRepository $fileRepository, DaemonServerRepository $serverRepository) {
|
||||
try {
|
||||
/** @var Server $server */
|
||||
$server = Filament::getTenant();
|
||||
|
||||
$fileRepository->setServer($server)->putContent('eula.txt', 'eula=true');
|
||||
|
||||
$powerRepository->setServer($server)->send('restart');
|
||||
$serverRepository->setServer($server)->power('restart');
|
||||
|
||||
Notification::make()
|
||||
->title('Minecraft EULA accepted')
|
||||
@@ -63,9 +58,4 @@ class MinecraftEula extends FeatureProvider
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static function register(Application $app): self
|
||||
{
|
||||
return new self($app);
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace App\Extensions\Features;
|
||||
namespace App\Extensions\Features\Schemas;
|
||||
|
||||
use App\Enums\TablerIcon;
|
||||
use App\Extensions\Features\FeatureSchemaInterface;
|
||||
use Filament\Actions\Action;
|
||||
use Illuminate\Foundation\Application;
|
||||
use Illuminate\Support\Facades\Blade;
|
||||
use Illuminate\Support\HtmlString;
|
||||
|
||||
class PIDLimit extends FeatureProvider
|
||||
class PIDLimitSchema implements FeatureSchemaInterface
|
||||
{
|
||||
public function __construct(protected Application $app)
|
||||
{
|
||||
parent::__construct($app);
|
||||
}
|
||||
|
||||
/** @return array<string> */
|
||||
public function getListeners(): array
|
||||
{
|
||||
@@ -36,10 +32,10 @@ class PIDLimit extends FeatureProvider
|
||||
{
|
||||
return Action::make($this->getId())
|
||||
->requiresConfirmation()
|
||||
->icon('tabler-alert-triangle')
|
||||
->modalHeading(fn () => auth()->user()->isAdmin() ? 'Memory or process limit reached...' : 'Possible resource limit reached...')
|
||||
->icon(TablerIcon::AlertTriangle)
|
||||
->modalHeading(fn () => user()?->isAdmin() ? 'Memory or process limit reached...' : 'Possible resource limit reached...')
|
||||
->modalDescription(new HtmlString(Blade::render(
|
||||
auth()->user()->isAdmin() ? <<<'HTML'
|
||||
user()?->isAdmin() ? <<<'HTML'
|
||||
<p>
|
||||
This server has reached the maximum process or memory limit.
|
||||
</p>
|
||||
@@ -68,9 +64,4 @@ class PIDLimit extends FeatureProvider
|
||||
->modalCancelActionLabel('Close')
|
||||
->action(fn () => null);
|
||||
}
|
||||
|
||||
public static function register(Application $app): self
|
||||
{
|
||||
return new self($app);
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace App\Extensions\Features;
|
||||
namespace App\Extensions\Features\Schemas;
|
||||
|
||||
use App\Extensions\Features\FeatureSchemaInterface;
|
||||
use Filament\Actions\Action;
|
||||
use Illuminate\Foundation\Application;
|
||||
use Illuminate\Support\Facades\Blade;
|
||||
use Illuminate\Support\HtmlString;
|
||||
|
||||
class SteamDiskSpace extends FeatureProvider
|
||||
class SteamDiskSpaceSchema implements FeatureSchemaInterface
|
||||
{
|
||||
public function __construct(protected Application $app)
|
||||
{
|
||||
parent::__construct($app);
|
||||
}
|
||||
|
||||
/** @return array<string> */
|
||||
public function getListeners(): array
|
||||
{
|
||||
@@ -34,7 +29,7 @@ class SteamDiskSpace extends FeatureProvider
|
||||
->requiresConfirmation()
|
||||
->modalHeading('Out of available disk space...')
|
||||
->modalDescription(new HtmlString(Blade::render(
|
||||
auth()->user()->isAdmin() ? <<<'HTML'
|
||||
user()?->isAdmin() ? <<<'HTML'
|
||||
<p>
|
||||
This server has run out of available disk space and cannot complete the install or update
|
||||
process.
|
||||
@@ -56,9 +51,4 @@ class SteamDiskSpace extends FeatureProvider
|
||||
->modalCancelActionLabel('Close')
|
||||
->action(fn () => null);
|
||||
}
|
||||
|
||||
public static function register(Application $app): self
|
||||
{
|
||||
return new self($app);
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ use App\Models\ApiKey;
|
||||
use Laravel\Sanctum\NewAccessToken as SanctumAccessToken;
|
||||
|
||||
/**
|
||||
* @property \App\Models\ApiKey $accessToken
|
||||
* @property ApiKey $accessToken
|
||||
*/
|
||||
class NewAccessToken extends SanctumAccessToken
|
||||
{
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Extensions\Lcobucci\JWT\Encoding;
|
||||
|
||||
use DateTimeImmutable;
|
||||
use Lcobucci\JWT\ClaimsFormatter;
|
||||
use Lcobucci\JWT\Token\RegisteredClaims;
|
||||
|
||||
@@ -20,7 +21,7 @@ final class TimestampDates implements ClaimsFormatter
|
||||
continue;
|
||||
}
|
||||
|
||||
assert($claims[$claim] instanceof \DateTimeImmutable);
|
||||
assert($claims[$claim] instanceof DateTimeImmutable);
|
||||
$claims[$claim] = $claims[$claim]->getTimestamp();
|
||||
}
|
||||
|
||||
|
||||
42
app/Extensions/OAuth/OAuthSchemaInterface.php
Normal file
42
app/Extensions/OAuth/OAuthSchemaInterface.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
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
|
||||
{
|
||||
public function getId(): string;
|
||||
|
||||
public function getName(): string;
|
||||
|
||||
public function getConfigKey(): string;
|
||||
|
||||
/** @return ?class-string */
|
||||
public function getSocialiteProvider(): ?string;
|
||||
|
||||
/**
|
||||
* @return array<string, string|string[]|bool|null>
|
||||
*/
|
||||
public function getServiceConfig(): array;
|
||||
|
||||
/** @return Component[] */
|
||||
public function getSettingsForm(): array;
|
||||
|
||||
/** @return Step[] */
|
||||
public function getSetupSteps(): array;
|
||||
|
||||
public function getIcon(): null|string|BackedEnum;
|
||||
|
||||
public function getHexColor(): ?string;
|
||||
|
||||
public function isEnabled(): bool;
|
||||
|
||||
public function shouldCreateMissingUser(OAuthUser $user): bool;
|
||||
|
||||
public function shouldLinkMissingUser(User $user, OAuthUser $oauthUser): bool;
|
||||
}
|
||||
71
app/Extensions/OAuth/OAuthService.php
Normal file
71
app/Extensions/OAuth/OAuthService.php
Normal file
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace App\Extensions\OAuth;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Event;
|
||||
use Laravel\Socialite\Contracts\User as OAuthUser;
|
||||
use SocialiteProviders\Manager\SocialiteWasCalled;
|
||||
|
||||
class OAuthService
|
||||
{
|
||||
/** @var OAuthSchemaInterface[] */
|
||||
private array $schemas = [];
|
||||
|
||||
/** @return OAuthSchemaInterface[] */
|
||||
public function getAll(): array
|
||||
{
|
||||
return $this->schemas;
|
||||
}
|
||||
|
||||
public function get(string $id): ?OAuthSchemaInterface
|
||||
{
|
||||
return array_get($this->schemas, $id);
|
||||
}
|
||||
|
||||
/** @return OAuthSchemaInterface[] */
|
||||
public function getEnabled(): array
|
||||
{
|
||||
return collect($this->schemas)
|
||||
->filter(fn (OAuthSchemaInterface $schema) => $schema->isEnabled())
|
||||
->all();
|
||||
}
|
||||
|
||||
public function register(OAuthSchemaInterface $schema): void
|
||||
{
|
||||
if (array_key_exists($schema->getId(), $this->schemas)) {
|
||||
return;
|
||||
}
|
||||
|
||||
config()->set('services.' . $schema->getId(), array_merge($schema->getServiceConfig(), ['redirect' => '/auth/oauth/callback/' . $schema->getId()]));
|
||||
|
||||
if ($schema->getSocialiteProvider()) {
|
||||
Event::listen(fn (SocialiteWasCalled $event) => $event->extendSocialite($schema->getId(), $schema->getSocialiteProvider()));
|
||||
}
|
||||
|
||||
$this->schemas[$schema->getId()] = $schema;
|
||||
}
|
||||
|
||||
public function linkUser(User $user, OAuthSchemaInterface $schema, OAuthUser $oauthUser): User
|
||||
{
|
||||
$oauth = $user->oauth ?? [];
|
||||
$oauth[$schema->getId()] = $oauthUser->getId();
|
||||
|
||||
$user->update(['oauth' => $oauth]);
|
||||
|
||||
return $user->refresh();
|
||||
}
|
||||
|
||||
public function unlinkUser(User $user, OAuthSchemaInterface $schema): User
|
||||
{
|
||||
$oauth = $user->oauth ?? [];
|
||||
if (!isset($oauth[$schema->getId()])) {
|
||||
return $user;
|
||||
}
|
||||
|
||||
unset($oauth[$schema->getId()]);
|
||||
$user->update(['oauth' => $oauth]);
|
||||
|
||||
return $user->refresh();
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user