mirror of
https://github.com/pelican-dev/panel.git
synced 2026-02-24 19:08:53 +03:00
Compare commits
141 Commits
v1.0.0-bet
...
v1.0.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
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
|
||||
|
||||
163
.github/workflows/ci.yaml
vendored
163
.github/workflows/ci.yaml
vendored
@@ -6,12 +6,75 @@ 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:
|
||||
sqlite:
|
||||
name: SQLite
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
php: [8.2, 8.3, 8.4]
|
||||
env:
|
||||
DB_CONNECTION: sqlite
|
||||
DB_DATABASE: testing.sqlite
|
||||
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: Create SQLite file
|
||||
run: touch database/testing.sqlite
|
||||
|
||||
- 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
|
||||
|
||||
mysql:
|
||||
name: MySQL
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
fail-fast: true
|
||||
matrix:
|
||||
php: [8.2, 8.3, 8.4]
|
||||
database: ["mysql:8"]
|
||||
@@ -25,21 +88,10 @@ jobs:
|
||||
- 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
|
||||
@@ -84,7 +136,7 @@ jobs:
|
||||
name: MariaDB
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
fail-fast: true
|
||||
matrix:
|
||||
php: [8.2, 8.3, 8.4]
|
||||
database: ["mariadb:10.6", "mariadb:10.11", "mariadb:11.4"]
|
||||
@@ -98,21 +150,10 @@ jobs:
|
||||
- 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
|
||||
@@ -153,72 +194,11 @@ jobs:
|
||||
DB_PORT: ${{ job.services.database.ports[3306] }}
|
||||
DB_USERNAME: root
|
||||
|
||||
sqlite:
|
||||
name: SQLite
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
php: [8.2, 8.3, 8.4]
|
||||
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
|
||||
|
||||
- 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: Create SQLite file
|
||||
run: touch database/testing.sqlite
|
||||
|
||||
- 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
|
||||
|
||||
postgresql:
|
||||
name: PostgreSQL
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
fail-fast: true
|
||||
matrix:
|
||||
php: [8.2, 8.3, 8.4]
|
||||
database: ["postgres:14"]
|
||||
@@ -238,22 +218,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
|
||||
|
||||
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'"
|
||||
|
||||
2
.github/workflows/lint.yaml
vendored
2
.github/workflows/lint.yaml
vendored
@@ -33,7 +33,7 @@ jobs:
|
||||
name: PHPStan
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
fail-fast: true
|
||||
matrix:
|
||||
php: [ 8.2, 8.3, 8.4 ]
|
||||
steps:
|
||||
|
||||
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
|
||||
|
||||
14
Dockerfile
14
Dockerfile
@@ -63,8 +63,8 @@ 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 \
|
||||
caddy ca-certificates supervisor supercronic fcgi
|
||||
|
||||
COPY --chown=root:www-data --chmod=640 --from=composerbuild /build .
|
||||
COPY --chown=root:www-data --chmod=640 --from=yarnbuild /build/public ./public
|
||||
@@ -85,7 +85,8 @@ RUN chown root:www-data ./ \
|
||||
&& 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
|
||||
&& chmod -R u+rwX,g+rwX,o-rwx /pelican-data ./storage ./bootstrap/cache /var/run/supervisord \
|
||||
&& chown -R www-data: /usr/local/etc/php/
|
||||
|
||||
# Configure Supervisor
|
||||
COPY docker/supervisord.conf /etc/supervisord.conf
|
||||
@@ -93,10 +94,11 @@ COPY docker/Caddyfile /etc/caddy/Caddyfile
|
||||
# Add Laravel scheduler to crontab
|
||||
COPY docker/crontab /etc/supercronic/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 +106,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" ]
|
||||
|
||||
@@ -67,8 +67,8 @@ 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 \
|
||||
caddy ca-certificates supervisor supercronic fcgi coreutils
|
||||
|
||||
COPY --chown=root:www-data --chmod=640 --from=composerbuild /build .
|
||||
COPY --chown=root:www-data --chmod=640 --from=yarnbuild /build/public ./public
|
||||
@@ -89,7 +89,8 @@ RUN chown root:www-data ./ \
|
||||
&& 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
|
||||
&& chmod -R u+rwX,g+rwX,o-rwx /pelican-data ./storage ./bootstrap/cache /var/run/supervisord \
|
||||
&& chown -R www-data: /usr/local/etc/php/
|
||||
|
||||
# Configure Supervisor
|
||||
COPY docker/supervisord.conf /etc/supervisord.conf
|
||||
@@ -97,10 +98,11 @@ COPY docker/Caddyfile /etc/caddy/Caddyfile
|
||||
# Add Laravel scheduler to crontab
|
||||
COPY docker/crontab /etc/supercronic/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 +110,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" ]
|
||||
|
||||
@@ -2,10 +2,13 @@
|
||||
|
||||
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 JsonException;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
class CheckEggUpdatesCommand extends Command
|
||||
{
|
||||
@@ -23,6 +26,9 @@ class CheckEggUpdatesCommand extends Command
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws JsonException
|
||||
*/
|
||||
private function check(Egg $egg, EggExporterService $exporterService): void
|
||||
{
|
||||
if (is_null($egg->update_url)) {
|
||||
@@ -31,22 +37,26 @@ 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 = file_get_contents($egg->update_url);
|
||||
assert($remote !== false);
|
||||
|
||||
return;
|
||||
}
|
||||
$remote = $isYaml ? Yaml::parse($remote) : json_decode($remote, true);
|
||||
|
||||
$this->warn("$egg->name: Found update");
|
||||
cache()->put("eggs.$egg->uuid.update", true, now()->addHour());
|
||||
unset($local['exported_at'], $remote['exported_at']);
|
||||
|
||||
$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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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'));
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -2,10 +2,13 @@
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use App\Console\Kernel;
|
||||
use Symfony\Component\Process\Process;
|
||||
use Closure;
|
||||
use Exception;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Foundation\Application;
|
||||
use Symfony\Component\Console\Helper\ProgressBar;
|
||||
use Symfony\Component\Process\Process;
|
||||
|
||||
class UpgradeCommand extends Command
|
||||
{
|
||||
@@ -28,7 +31,7 @@ class UpgradeCommand extends Command
|
||||
* This places the application in maintenance mode as well while the commands
|
||||
* are being executed.
|
||||
*
|
||||
* @throws \Exception
|
||||
* @throws Exception
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
@@ -129,9 +132,9 @@ class UpgradeCommand extends Command
|
||||
});
|
||||
});
|
||||
|
||||
/** @var \Illuminate\Foundation\Application $app */
|
||||
/** @var Application $app */
|
||||
$app = require __DIR__ . '/../../../bootstrap/app.php';
|
||||
/** @var \App\Console\Kernel $kernel */
|
||||
/** @var Kernel $kernel */
|
||||
$kernel = $app->make(Kernel::class);
|
||||
$kernel->bootstrap();
|
||||
$this->setLaravel($app);
|
||||
@@ -174,7 +177,7 @@ class UpgradeCommand extends Command
|
||||
$this->info(trans('commands.upgrade.success'));
|
||||
}
|
||||
|
||||
protected function withProgress(ProgressBar $bar, \Closure $callback): void
|
||||
protected function withProgress(ProgressBar $bar, Closure $callback): void
|
||||
{
|
||||
$bar->clear();
|
||||
$callback();
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -32,6 +32,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,7 +68,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
|
||||
|
||||
37
app/Enums/CustomizationKey.php
Normal file
37
app/Enums/CustomizationKey.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?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';
|
||||
|
||||
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',
|
||||
};
|
||||
}
|
||||
|
||||
/** @return array<string, string|int|bool> */
|
||||
public static function getDefaultCustomization(): array
|
||||
{
|
||||
$default = [];
|
||||
|
||||
foreach (self::cases() as $key) {
|
||||
$default[$key->value] = $key->getDefaultValue();
|
||||
}
|
||||
|
||||
return $default;
|
||||
}
|
||||
}
|
||||
@@ -1,141 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
use Filament\Support\Contracts\HasLabel;
|
||||
|
||||
enum EditorLanguages: string implements HasLabel
|
||||
{
|
||||
case plaintext = 'plaintext';
|
||||
case abap = 'abap';
|
||||
case apex = 'apex';
|
||||
case azcali = 'azcali';
|
||||
case bat = 'bat';
|
||||
case bicep = 'bicep';
|
||||
case cameligo = 'cameligo';
|
||||
case coljure = 'coljure';
|
||||
case coffeescript = 'coffeescript';
|
||||
case c = 'c';
|
||||
case cpp = 'cpp';
|
||||
case csharp = 'csharp';
|
||||
case csp = 'csp';
|
||||
case css = 'css';
|
||||
case cypher = 'cypher';
|
||||
case dart = 'dart';
|
||||
case dockerfile = 'dockerfile';
|
||||
case ecl = 'ecl';
|
||||
case elixir = 'elixir';
|
||||
case flow9 = 'flow9';
|
||||
case fsharp = 'fsharp';
|
||||
case go = 'go';
|
||||
case graphql = 'graphql';
|
||||
case handlebars = 'handlebars';
|
||||
case hcl = 'hcl';
|
||||
case html = 'html';
|
||||
case ini = 'ini';
|
||||
case java = 'java';
|
||||
case javascript = 'javascript';
|
||||
case julia = 'julia';
|
||||
case json = 'json';
|
||||
case kotlin = 'kotlin';
|
||||
case less = 'less';
|
||||
case lexon = 'lexon';
|
||||
case lua = 'lua';
|
||||
case liquid = 'liquid';
|
||||
case m3 = 'm3';
|
||||
case markdown = 'markdown';
|
||||
case mdx = 'mdx';
|
||||
case mips = 'mips';
|
||||
case msdax = 'msdax';
|
||||
case mysql = 'mysql';
|
||||
case objectivec = 'objective-c';
|
||||
case pascal = 'pascal';
|
||||
case pascaligo = 'pascaligo';
|
||||
case perl = 'perl';
|
||||
case pgsql = 'pgsql';
|
||||
case php = 'php';
|
||||
case pla = 'pla';
|
||||
case postiats = 'postiats';
|
||||
case powerquery = 'powerquery';
|
||||
case powershell = 'powershell';
|
||||
case proto = 'proto';
|
||||
case pug = 'pug';
|
||||
case python = 'python';
|
||||
case qsharp = 'qsharp';
|
||||
case r = 'r';
|
||||
case razor = 'razor';
|
||||
case redis = 'redis';
|
||||
case redshift = 'redshift';
|
||||
case restructuredtext = 'restructuredtext';
|
||||
case ruby = 'ruby';
|
||||
case rust = 'rust';
|
||||
case sb = 'sb';
|
||||
case scala = 'scala';
|
||||
case scheme = 'scheme';
|
||||
case scss = 'scss';
|
||||
case shell = 'shell';
|
||||
case sol = 'sol';
|
||||
case aes = 'aes';
|
||||
case sparql = 'sparql';
|
||||
case sql = 'sql';
|
||||
case st = 'st';
|
||||
case swift = 'swift';
|
||||
case systemverilog = 'systemverilog';
|
||||
case verilog = 'verilog';
|
||||
case tcl = 'tcl';
|
||||
case twig = 'twig';
|
||||
case typescript = 'typescript';
|
||||
case typespec = 'typespec';
|
||||
case vb = 'vb';
|
||||
case wgsl = 'wgsl';
|
||||
case xml = 'xml';
|
||||
case yaml = 'yaml';
|
||||
|
||||
public static function fromWithAlias(string $match): self
|
||||
{
|
||||
return match ($match) {
|
||||
'h' => self::c,
|
||||
|
||||
'cc', 'hpp' => self::cpp,
|
||||
|
||||
'cs' => self::csharp,
|
||||
|
||||
'class' => self::java,
|
||||
|
||||
'htm' => self::html,
|
||||
|
||||
'js', 'mjs', 'cjs' => self::javascript,
|
||||
|
||||
'kt', 'kts' => self::kotlin,
|
||||
|
||||
'md' => self::markdown,
|
||||
|
||||
'm' => self::objectivec,
|
||||
|
||||
'pl', 'pm' => self::perl,
|
||||
|
||||
'php3', 'php4', 'php5', 'phtml' => self::php,
|
||||
|
||||
'py', 'pyc', 'pyo', 'pyi' => self::python,
|
||||
|
||||
'rdata', 'rds' => self::r,
|
||||
|
||||
'rb', 'erb' => self::ruby,
|
||||
|
||||
'sc' => self::scala,
|
||||
|
||||
'sh', 'zsh' => self::shell,
|
||||
|
||||
'ts', 'tsx' => self::typescript,
|
||||
|
||||
'yml' => self::yaml,
|
||||
|
||||
default => self::tryFrom($match) ?? self::plaintext,
|
||||
};
|
||||
}
|
||||
|
||||
public function getLabel(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
}
|
||||
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';
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ 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';
|
||||
@@ -18,7 +17,6 @@ enum ServerState: string implements HasColor, HasIcon, HasLabel
|
||||
public function getIcon(): string
|
||||
{
|
||||
return match ($this) {
|
||||
self::Normal => 'tabler-heart',
|
||||
self::Installing => 'tabler-heart-bolt',
|
||||
self::InstallFailed => 'tabler-heart-x',
|
||||
self::ReinstallFailed => 'tabler-heart-x',
|
||||
@@ -31,14 +29,13 @@ enum ServerState: string implements HasColor, HasIcon, HasLabel
|
||||
{
|
||||
if ($hex) {
|
||||
return match ($this) {
|
||||
self::Normal, self::Installing, self::RestoringBackup => '#2563EB',
|
||||
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';
|
||||
}
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
use Filament\Support\Contracts\HasLabel;
|
||||
use Filament\Support\Contracts\HasColor;
|
||||
use Filament\Support\Contracts\HasIcon;
|
||||
use Filament\Support\Contracts\HasLabel;
|
||||
|
||||
enum WebhookType: string implements HasColor, HasIcon, HasLabel
|
||||
{
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions\Http;
|
||||
|
||||
use Illuminate\Http\Response;
|
||||
use Symfony\Component\HttpKernel\Exception\HttpException;
|
||||
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
|
||||
|
||||
class TwoFactorAuthRequiredException extends HttpException implements HttpExceptionInterface
|
||||
{
|
||||
/**
|
||||
* TwoFactorAuthRequiredException constructor.
|
||||
*/
|
||||
public function __construct(?\Throwable $previous = null)
|
||||
{
|
||||
parent::__construct(Response::HTTP_BAD_REQUEST, 'Two-factor authentication is required on this account in order to access this endpoint.', $previous);
|
||||
}
|
||||
}
|
||||
@@ -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 {}
|
||||
|
||||
@@ -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.');
|
||||
}
|
||||
}
|
||||
@@ -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.");
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
namespace App\Extensions\Captcha\Schemas;
|
||||
|
||||
use Filament\Forms\Components\Component;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Schemas\Components\Component;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
abstract class BaseSchema
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace App\Extensions\Captcha\Schemas;
|
||||
|
||||
use Filament\Forms\Components\Component;
|
||||
use Filament\Schemas\Components\Component;
|
||||
|
||||
interface CaptchaSchemaInterface
|
||||
{
|
||||
|
||||
@@ -2,12 +2,11 @@
|
||||
|
||||
namespace App\Extensions\Captcha\Schemas\Turnstile;
|
||||
|
||||
use App\Extensions\Captcha\Schemas\CaptchaSchemaInterface;
|
||||
use App\Extensions\Captcha\Schemas\BaseSchema;
|
||||
use App\Extensions\Captcha\Schemas\CaptchaSchemaInterface;
|
||||
use Exception;
|
||||
use Filament\Forms\Components\Component as BaseComponent;
|
||||
use Filament\Forms\Components\Placeholder;
|
||||
use Filament\Forms\Components\Toggle;
|
||||
use Filament\Infolists\Components\TextEntry;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\HtmlString;
|
||||
|
||||
@@ -23,7 +22,7 @@ class TurnstileSchema extends BaseSchema implements CaptchaSchemaInterface
|
||||
return env('CAPTCHA_TURNSTILE_ENABLED', false);
|
||||
}
|
||||
|
||||
public function getFormComponent(): BaseComponent
|
||||
public function getFormComponent(): Component
|
||||
{
|
||||
return Component::make('turnstile');
|
||||
}
|
||||
@@ -39,7 +38,9 @@ class TurnstileSchema extends BaseSchema implements CaptchaSchemaInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* @return BaseComponent[]
|
||||
* @return \Filament\Support\Components\Component[]
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function getSettingsForm(): array
|
||||
{
|
||||
@@ -53,10 +54,10 @@ class TurnstileSchema extends BaseSchema implements CaptchaSchemaInterface
|
||||
->onColor('success')
|
||||
->offColor('danger')
|
||||
->default(env('CAPTCHA_TURNSTILE_VERIFY_DOMAIN', true)),
|
||||
Placeholder::make('info')
|
||||
TextEntry::make('info')
|
||||
->label(trans('admin/setting.captcha.info_label'))
|
||||
->columnSpan(2)
|
||||
->content(new HtmlString(trans('admin/setting.captcha.info'))),
|
||||
->state(new HtmlString(trans('admin/setting.captcha.info'))),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,13 +7,13 @@ 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\Support\Facades\Blade;
|
||||
@@ -36,6 +36,9 @@ class GSLTokenSchema implements FeatureSchemaInterface
|
||||
return 'gsl_token';
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function getAction(): Action
|
||||
{
|
||||
/** @var Server $server */
|
||||
@@ -51,9 +54,9 @@ class GSLTokenSchema implements FeatureSchemaInterface
|
||||
->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(Permission::ACTION_STARTUP_UPDATE, $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')
|
||||
@@ -70,13 +73,12 @@ class GSLTokenSchema implements FeatureSchemaInterface
|
||||
}
|
||||
},
|
||||
])
|
||||
->hintIcon('tabler-code')
|
||||
->hintIcon('tabler-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 {
|
||||
@@ -98,7 +100,7 @@ class GSLTokenSchema implements FeatureSchemaInterface
|
||||
->log();
|
||||
}
|
||||
|
||||
$powerRepository->setServer($server)->send('restart');
|
||||
$serverRepository->setServer($server)->power('restart');
|
||||
|
||||
Notification::make()
|
||||
->title('GSL Token updated')
|
||||
|
||||
@@ -6,12 +6,12 @@ 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;
|
||||
|
||||
class JavaVersionSchema implements FeatureSchemaInterface
|
||||
@@ -44,9 +44,9 @@ class JavaVersionSchema implements FeatureSchemaInterface
|
||||
->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(Permission::ACTION_STARTUP_DOCKER_IMAGE, $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')
|
||||
@@ -56,10 +56,9 @@ class JavaVersionSchema implements FeatureSchemaInterface
|
||||
->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;
|
||||
@@ -71,7 +70,7 @@ class JavaVersionSchema implements FeatureSchemaInterface
|
||||
->log();
|
||||
}
|
||||
|
||||
$powerRepository->setServer($server)->send('restart');
|
||||
$serverRepository->setServer($server)->power('restart');
|
||||
|
||||
Notification::make()
|
||||
->title('Docker image updated')
|
||||
|
||||
@@ -5,7 +5,7 @@ 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;
|
||||
@@ -35,14 +35,14 @@ class MinecraftEulaSchema implements FeatureSchemaInterface
|
||||
->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')
|
||||
|
||||
@@ -32,9 +32,9 @@ class PIDLimitSchema implements FeatureSchemaInterface
|
||||
return Action::make($this->getId())
|
||||
->requiresConfirmation()
|
||||
->icon('tabler-alert-triangle')
|
||||
->modalHeading(fn () => auth()->user()->isAdmin() ? 'Memory or process limit reached...' : 'Possible resource limit reached...')
|
||||
->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>
|
||||
|
||||
@@ -29,7 +29,7 @@ class SteamDiskSpaceSchema implements FeatureSchemaInterface
|
||||
->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.
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
namespace App\Extensions\OAuth;
|
||||
|
||||
use Filament\Forms\Components\Component;
|
||||
use Filament\Forms\Components\Wizard\Step;
|
||||
use Filament\Schemas\Components\Component;
|
||||
use Filament\Schemas\Components\Wizard\Step;
|
||||
|
||||
interface OAuthSchemaInterface
|
||||
{
|
||||
@@ -32,4 +32,8 @@ interface OAuthSchemaInterface
|
||||
public function getHexColor(): ?string;
|
||||
|
||||
public function isEnabled(): bool;
|
||||
|
||||
public function shouldCreateMissingUsers(): bool;
|
||||
|
||||
public function shouldLinkMissingUsers(): bool;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
|
||||
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
|
||||
@@ -43,4 +45,27 @@ class OAuthService
|
||||
|
||||
$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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,10 @@ namespace App\Extensions\OAuth\Schemas;
|
||||
|
||||
use Filament\Forms\Components\ColorPicker;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Infolists\Components\TextEntry;
|
||||
use Filament\Schemas\Components\Wizard\Step;
|
||||
use Illuminate\Support\Facades\Blade;
|
||||
use Illuminate\Support\HtmlString;
|
||||
use SocialiteProviders\Authentik\Provider;
|
||||
|
||||
final class AuthentikSchema extends OAuthSchema
|
||||
@@ -20,11 +24,27 @@ final class AuthentikSchema extends OAuthSchema
|
||||
|
||||
public function getServiceConfig(): array
|
||||
{
|
||||
return [
|
||||
return array_merge(parent::getServiceConfig(), [
|
||||
'base_url' => env('OAUTH_AUTHENTIK_BASE_URL'),
|
||||
'client_id' => env('OAUTH_AUTHENTIK_CLIENT_ID'),
|
||||
'client_secret' => env('OAUTH_AUTHENTIK_CLIENT_SECRET'),
|
||||
];
|
||||
]);
|
||||
}
|
||||
|
||||
public function getSetupSteps(): array
|
||||
{
|
||||
return array_merge([
|
||||
Step::make('Create Authentik Application')
|
||||
->schema([
|
||||
TextEntry::make('create_application')
|
||||
->hiddenLabel()
|
||||
->state(new HtmlString(Blade::render('<p>On your Authentik dashboard select <b>Applications</b>, then select <b>Create with Provider</b>.</p><p>On the creation step select <b>OAuth2/OpenID Provider</b> and on the configure step set <b>Redirect URIs/Origins</b> to the value below.</p>'))),
|
||||
TextInput::make('_noenv_callback')
|
||||
->label('Callback URL')
|
||||
->dehydrated()
|
||||
->disabled()
|
||||
->hintCopy()
|
||||
->default(fn () => url('/auth/oauth/callback/authentik')),
|
||||
]),
|
||||
], parent::getSetupSteps());
|
||||
}
|
||||
|
||||
public function getSettingsForm(): array
|
||||
|
||||
45
app/Extensions/OAuth/Schemas/BitbucketSchema.php
Normal file
45
app/Extensions/OAuth/Schemas/BitbucketSchema.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace App\Extensions\OAuth\Schemas;
|
||||
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Infolists\Components\TextEntry;
|
||||
use Filament\Schemas\Components\Wizard\Step;
|
||||
use Illuminate\Support\Facades\Blade;
|
||||
use Illuminate\Support\HtmlString;
|
||||
|
||||
final class BitbucketSchema extends OAuthSchema
|
||||
{
|
||||
public function getId(): string
|
||||
{
|
||||
return 'bitbucket';
|
||||
}
|
||||
|
||||
public function getSetupSteps(): array
|
||||
{
|
||||
return array_merge([
|
||||
Step::make('Register new Bitbucket Consumer')
|
||||
->schema([
|
||||
TextEntry::make('create_application')
|
||||
->hiddenLabel()
|
||||
->state(new HtmlString(Blade::render('<p>Visit the <x-filament::link href="https://support.atlassian.com/bitbucket-cloud/docs/use-oauth-on-bitbucket-cloud" target="_blank">Bitbucket OAuth Documentation</x-filament::link> and follow the steps in <b>Create a consumer</b>.</p><p>For the <b>Callback URL</b> use the value below.</p>'))),
|
||||
TextInput::make('_noenv_callback')
|
||||
->label('Callback URL')
|
||||
->dehydrated()
|
||||
->disabled()
|
||||
->hintCopy()
|
||||
->default(fn () => url('/auth/oauth/callback/bitbucket')),
|
||||
]),
|
||||
], parent::getSetupSteps());
|
||||
}
|
||||
|
||||
public function getIcon(): string
|
||||
{
|
||||
return 'tabler-brand-bitbucket-f';
|
||||
}
|
||||
|
||||
public function getHexColor(): string
|
||||
{
|
||||
return '#205081';
|
||||
}
|
||||
}
|
||||
@@ -2,13 +2,12 @@
|
||||
|
||||
namespace App\Extensions\OAuth\Schemas;
|
||||
|
||||
use Filament\Forms\Components\Placeholder;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Wizard\Step;
|
||||
use Filament\Infolists\Components\TextEntry;
|
||||
use Filament\Schemas\Components\Wizard\Step;
|
||||
use Illuminate\Support\Facades\Blade;
|
||||
use Illuminate\Support\HtmlString;
|
||||
use SocialiteProviders\Discord\Provider;
|
||||
use Webbingbrasil\FilamentCopyActions\Forms\Actions\CopyAction;
|
||||
|
||||
final class DiscordSchema extends OAuthSchema
|
||||
{
|
||||
@@ -27,15 +26,17 @@ final class DiscordSchema extends OAuthSchema
|
||||
return array_merge([
|
||||
Step::make('Register new Discord OAuth App')
|
||||
->schema([
|
||||
Placeholder::make('')
|
||||
->content(new HtmlString(Blade::render('<p>Visit the <x-filament::link href="https://discord.com/developers/applications" target="_blank">Discord Developer Portal</x-filament::link> and click on <b>New Application</b>. Enter a <b>Name</b> (e.g. your panel name) and click on <b>Create</b>.</p><p>Copy the <b>Client ID</b> and the <b>Client Secret</b> from the OAuth2 tab, you will need them in the final step.</p>'))),
|
||||
Placeholder::make('')
|
||||
->content(new HtmlString('<p>Under <b>Redirects</b> add the below URL.</p>')),
|
||||
TextEntry::make('create_application')
|
||||
->hiddenLabel()
|
||||
->state(new HtmlString(Blade::render('<p>Visit the <x-filament::link href="https://discord.com/developers/applications" target="_blank">Discord Developer Portal</x-filament::link> and click on <b>New Application</b>. Enter a <b>Name</b> (e.g. your panel name) and click on <b>Create</b>.</p><p>Copy the <b>Client ID</b> and the <b>Client Secret</b> from the OAuth2 tab, you will need them in the final step.</p>'))),
|
||||
TextEntry::make('set_redirect')
|
||||
->hiddenLabel()
|
||||
->state(new HtmlString('<p>Under <b>Redirects</b> add the below URL.</p>')),
|
||||
TextInput::make('_noenv_callback')
|
||||
->label('Redirect URL')
|
||||
->dehydrated()
|
||||
->disabled()
|
||||
->hintAction(fn (string $state) => request()->isSecure() ? CopyAction::make()->copyable($state) : null)
|
||||
->hintCopy()
|
||||
->formatStateUsing(fn () => url('/auth/oauth/callback/discord')),
|
||||
]),
|
||||
], parent::getSetupSteps());
|
||||
|
||||
48
app/Extensions/OAuth/Schemas/FacebookSchema.php
Normal file
48
app/Extensions/OAuth/Schemas/FacebookSchema.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace App\Extensions\OAuth\Schemas;
|
||||
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Infolists\Components\TextEntry;
|
||||
use Filament\Schemas\Components\Wizard\Step;
|
||||
use Illuminate\Support\Facades\Blade;
|
||||
use Illuminate\Support\HtmlString;
|
||||
|
||||
final class FacebookSchema extends OAuthSchema
|
||||
{
|
||||
public function getId(): string
|
||||
{
|
||||
return 'facebook';
|
||||
}
|
||||
|
||||
public function getSetupSteps(): array
|
||||
{
|
||||
return array_merge([
|
||||
Step::make('Register new Facebook Application')
|
||||
->schema([
|
||||
TextEntry::make('create_application')
|
||||
->hiddenLabel()
|
||||
->state(new HtmlString(Blade::render('<p>Visit the <x-filament::link href="https://developers.facebook.com/apps" target="_blank">Facebook Developer Dashboard</x-filament::link> and select or create a new app you will use for authentication. Make sure to have "Authenticate and request data from users with Facebook Login" as one of the Use Cases.</p><p>Once selected go to <b>Use Cases</b> and customize "Authenticate and request data from users with Facebook Login", from there go to <b>Settings</b> and add <b>Valid OAuth Redirect URIs</b> using the value below.</p>'))),
|
||||
TextInput::make('_noenv_callback')
|
||||
->label('Valid OAuth Redirect URIs')
|
||||
->dehydrated()
|
||||
->disabled()
|
||||
->hintCopy()
|
||||
->default(fn () => url('/auth/oauth/callback/facebook')),
|
||||
TextEntry::make('get_app_info')
|
||||
->hiddenLabel()
|
||||
->state(new HtmlString(Blade::render('<p>To obtain the OAuth values go to <b>App Settings > Basic</b>.</p>'))),
|
||||
]),
|
||||
], parent::getSetupSteps());
|
||||
}
|
||||
|
||||
public function getIcon(): string
|
||||
{
|
||||
return 'tabler-brand-facebook-f';
|
||||
}
|
||||
|
||||
public function getHexColor(): string
|
||||
{
|
||||
return '#1877f2';
|
||||
}
|
||||
}
|
||||
@@ -2,12 +2,11 @@
|
||||
|
||||
namespace App\Extensions\OAuth\Schemas;
|
||||
|
||||
use Filament\Forms\Components\Placeholder;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Wizard\Step;
|
||||
use Filament\Infolists\Components\TextEntry;
|
||||
use Filament\Schemas\Components\Wizard\Step;
|
||||
use Illuminate\Support\Facades\Blade;
|
||||
use Illuminate\Support\HtmlString;
|
||||
use Webbingbrasil\FilamentCopyActions\Forms\Actions\CopyAction;
|
||||
|
||||
final class GithubSchema extends OAuthSchema
|
||||
{
|
||||
@@ -21,21 +20,24 @@ final class GithubSchema extends OAuthSchema
|
||||
return array_merge([
|
||||
Step::make('Register new Github OAuth App')
|
||||
->schema([
|
||||
Placeholder::make('')
|
||||
->content(new HtmlString(Blade::render('<p>Visit the <x-filament::link href="https://github.com/settings/developers" target="_blank">Github Developer Dashboard</x-filament::link>, go to <b>OAuth Apps</b> and click on <b>New OAuth App</b>.</p><p>Enter an <b>Application name</b> (e.g. your panel name), set <b>Homepage URL</b> to your panel url and enter the below url as <b>Authorization callback URL</b>.</p>'))),
|
||||
TextEntry::make('create_application')
|
||||
->hiddenLabel()
|
||||
->state(new HtmlString(Blade::render('<p>Visit the <x-filament::link href="https://github.com/settings/developers" target="_blank">Github Developer Dashboard</x-filament::link>, go to <b>OAuth Apps</b> and click on <b>New OAuth App</b>.</p><p>Enter an <b>Application name</b> (e.g. your panel name), set <b>Homepage URL</b> to your panel url and enter the below url as <b>Authorization callback URL</b>.</p>'))),
|
||||
TextInput::make('_noenv_callback')
|
||||
->label('Authorization callback URL')
|
||||
->dehydrated()
|
||||
->disabled()
|
||||
->hintAction(fn (string $state) => request()->isSecure() ? CopyAction::make()->copyable($state) : null)
|
||||
->hintCopy()
|
||||
->default(fn () => url('/auth/oauth/callback/github')),
|
||||
Placeholder::make('')
|
||||
->content(new HtmlString('<p>When you filled all fields click on <b>Register application</b>.</p>')),
|
||||
TextEntry::make('register_application')
|
||||
->hiddenLabel()
|
||||
->state(new HtmlString('<p>When you filled all fields click on <b>Register application</b>.</p>')),
|
||||
]),
|
||||
Step::make('Create Client Secret')
|
||||
->schema([
|
||||
Placeholder::make('')
|
||||
->content(new HtmlString('<p>Once you registered your app, generate a new <b>Client Secret</b>.</p><p>You will also need the <b>Client ID</b>.</p>')),
|
||||
TextEntry::make('create_client_secret')
|
||||
->hiddenLabel()
|
||||
->state(new HtmlString('<p>Once you registered your app, generate a new <b>Client Secret</b>.</p><p>You will also need the <b>Client ID</b>.</p>')),
|
||||
]),
|
||||
], parent::getSetupSteps());
|
||||
}
|
||||
|
||||
@@ -2,12 +2,11 @@
|
||||
|
||||
namespace App\Extensions\OAuth\Schemas;
|
||||
|
||||
use Filament\Forms\Components\Placeholder;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Wizard\Step;
|
||||
use Filament\Infolists\Components\TextEntry;
|
||||
use Filament\Schemas\Components\Wizard\Step;
|
||||
use Illuminate\Support\Facades\Blade;
|
||||
use Illuminate\Support\HtmlString;
|
||||
use Webbingbrasil\FilamentCopyActions\Forms\Actions\CopyAction;
|
||||
|
||||
final class GitlabSchema extends OAuthSchema
|
||||
{
|
||||
@@ -41,13 +40,14 @@ final class GitlabSchema extends OAuthSchema
|
||||
return array_merge([
|
||||
Step::make('Register new Gitlab OAuth App')
|
||||
->schema([
|
||||
Placeholder::make('')
|
||||
->content(new HtmlString(Blade::render('Check out the <x-filament::link href="https://docs.gitlab.com/integration/oauth_provider/" target="_blank">Gitlab docs</x-filament::link> on how to create the oauth app.'))),
|
||||
TextEntry::make('register_application')
|
||||
->hiddenLabel()
|
||||
->state(new HtmlString(Blade::render('Check out the <x-filament::link href="https://docs.gitlab.com/integration/oauth_provider/" target="_blank">Gitlab docs</x-filament::link> on how to create the oauth app.'))),
|
||||
TextInput::make('_noenv_callback')
|
||||
->label('Redirect URI')
|
||||
->dehydrated()
|
||||
->disabled()
|
||||
->hintAction(fn (string $state) => request()->isSecure() ? CopyAction::make()->copyable($state) : null)
|
||||
->hintCopy()
|
||||
->default(fn () => url('/auth/oauth/callback/gitlab')),
|
||||
]),
|
||||
], parent::getSetupSteps());
|
||||
|
||||
54
app/Extensions/OAuth/Schemas/GoogleSchema.php
Normal file
54
app/Extensions/OAuth/Schemas/GoogleSchema.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace App\Extensions\OAuth\Schemas;
|
||||
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Infolists\Components\TextEntry;
|
||||
use Filament\Schemas\Components\Wizard\Step;
|
||||
use Illuminate\Support\Facades\Blade;
|
||||
use Illuminate\Support\HtmlString;
|
||||
|
||||
final class GoogleSchema extends OAuthSchema
|
||||
{
|
||||
public function getId(): string
|
||||
{
|
||||
return 'google';
|
||||
}
|
||||
|
||||
public function getSetupSteps(): array
|
||||
{
|
||||
return array_merge([
|
||||
Step::make('Register new OAuth client')
|
||||
->schema([
|
||||
TextEntry::make('create_application')
|
||||
->hiddenLabel()
|
||||
->state(new HtmlString(Blade::render('<p>Visit the <x-filament::link href="https://console.developers.google.com/" target="_blank">Google API Console</x-filament::link> and create or select the project you want to use.</p><p>Navigate or search <b>Credentials</b>, click on the <b>Create Credentials</b> button and select <b>OAuth client ID</b>. On the Application type select <b>Web Application</b>.</p><p>On <b>Authorized JavaScript origins</b> and <b>Authorized redirect URIs</b> add and use the values below.</p>'))),
|
||||
TextInput::make('_noenv_origin')
|
||||
->label('Authorized JavaScript origins')
|
||||
->dehydrated()
|
||||
->disabled()
|
||||
->hintCopy()
|
||||
->default(fn () => url('')),
|
||||
TextInput::make('_noenv_callback')
|
||||
->label('Authorized redirect URIs')
|
||||
->dehydrated()
|
||||
->disabled()
|
||||
->hintCopy()
|
||||
->default(fn () => url('/auth/oauth/callback/google')),
|
||||
TextEntry::make('register_application')
|
||||
->hiddenLabel()
|
||||
->state(new HtmlString('<p>When you filled all fields click on <b>Create</b>.</p>')),
|
||||
]),
|
||||
], parent::getSetupSteps());
|
||||
}
|
||||
|
||||
public function getIcon(): string
|
||||
{
|
||||
return 'tabler-brand-google-f';
|
||||
}
|
||||
|
||||
public function getHexColor(): string
|
||||
{
|
||||
return '#4285f4';
|
||||
}
|
||||
}
|
||||
45
app/Extensions/OAuth/Schemas/LinkedinSchema.php
Normal file
45
app/Extensions/OAuth/Schemas/LinkedinSchema.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace App\Extensions\OAuth\Schemas;
|
||||
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Infolists\Components\TextEntry;
|
||||
use Filament\Schemas\Components\Wizard\Step;
|
||||
use Illuminate\Support\Facades\Blade;
|
||||
use Illuminate\Support\HtmlString;
|
||||
|
||||
final class LinkedinSchema extends OAuthSchema
|
||||
{
|
||||
public function getId(): string
|
||||
{
|
||||
return 'linkedin';
|
||||
}
|
||||
|
||||
public function getSetupSteps(): array
|
||||
{
|
||||
return array_merge([
|
||||
Step::make('Obtain Linkedin App OAuth Config')
|
||||
->schema([
|
||||
TextEntry::make('create_application')
|
||||
->hiddenLabel()
|
||||
->state(new HtmlString(Blade::render('<p><x-filament::link href="https://www.linkedin.com/developers/apps/new" target="_blank">Create</x-filament::link> or <x-filament::link href="https://www.linkedin.com/developers/apps" target="_blank">select</x-filament::link> the one you will be using for authentication.</p><p>Select the <b>Auth</b> tab and set <b>Authorized redirect URLs for your app</b> to the value below.</p>'))),
|
||||
TextInput::make('_noenv_callback')
|
||||
->label('Authorized redirect URL')
|
||||
->dehydrated()
|
||||
->disabled()
|
||||
->hintCopy()
|
||||
->default(fn () => url('/auth/oauth/callback/linkedin')),
|
||||
]),
|
||||
], parent::getSetupSteps());
|
||||
}
|
||||
|
||||
public function getIcon(): string
|
||||
{
|
||||
return 'tabler-brand-linkedin-f';
|
||||
}
|
||||
|
||||
public function getHexColor(): string
|
||||
{
|
||||
return '#0a66c2';
|
||||
}
|
||||
}
|
||||
@@ -3,9 +3,11 @@
|
||||
namespace App\Extensions\OAuth\Schemas;
|
||||
|
||||
use App\Extensions\OAuth\OAuthSchemaInterface;
|
||||
use Filament\Forms\Components\Component;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Wizard\Step;
|
||||
use Filament\Forms\Components\Toggle;
|
||||
use Filament\Schemas\Components\Component;
|
||||
use Filament\Schemas\Components\Utilities\Set;
|
||||
use Filament\Schemas\Components\Wizard\Step;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
abstract class OAuthSchema implements OAuthSchemaInterface
|
||||
@@ -53,6 +55,28 @@ abstract class OAuthSchema implements OAuthSchemaInterface
|
||||
->revealable()
|
||||
->autocomplete(false)
|
||||
->default(env("OAUTH_{$id}_CLIENT_SECRET")),
|
||||
Toggle::make("OAUTH_{$id}_SHOULD_CREATE_MISSING_USERS")
|
||||
->label(trans('admin/setting.oauth.create_missing_users'))
|
||||
->columnSpan(2)
|
||||
->inline(false)
|
||||
->onIcon('tabler-check')
|
||||
->offIcon('tabler-x')
|
||||
->onColor('success')
|
||||
->offColor('danger')
|
||||
->formatStateUsing(fn ($state) => (bool) $state)
|
||||
->afterStateUpdated(fn ($state, Set $set) => $set("OAUTH_{$id}_SHOULD_CREATE_MISSING_USERS", (bool) $state))
|
||||
->default(env("OAUTH_{$id}_SHOULD_CREATE_MISSING_USERS")),
|
||||
Toggle::make("OAUTH_{$id}_SHOULD_LINK_MISSING_USERS")
|
||||
->label(trans('admin/setting.oauth.link_missing_users'))
|
||||
->columnSpan(2)
|
||||
->inline(false)
|
||||
->onIcon('tabler-check')
|
||||
->offIcon('tabler-x')
|
||||
->onColor('success')
|
||||
->offColor('danger')
|
||||
->formatStateUsing(fn ($state) => (bool) $state)
|
||||
->afterStateUpdated(fn ($state, Set $set) => $set("OAUTH_{$id}_SHOULD_LINK_MISSING_USERS", (bool) $state))
|
||||
->default(env("OAUTH_{$id}_SHOULD_LINK_MISSING_USERS")),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -96,4 +120,18 @@ abstract class OAuthSchema implements OAuthSchemaInterface
|
||||
|
||||
return env("OAUTH_{$id}_ENABLED", false);
|
||||
}
|
||||
|
||||
public function shouldCreateMissingUsers(): bool
|
||||
{
|
||||
$id = Str::upper($this->getId());
|
||||
|
||||
return env("OAUTH_{$id}_SHOULD_CREATE_MISSING_USERS", false);
|
||||
}
|
||||
|
||||
public function shouldLinkMissingUsers(): bool
|
||||
{
|
||||
$id = Str::upper($this->getId());
|
||||
|
||||
return env("OAUTH_{$id}_SHOULD_LINK_MISSING_USERS", false);
|
||||
}
|
||||
}
|
||||
|
||||
45
app/Extensions/OAuth/Schemas/SlackSchema.php
Normal file
45
app/Extensions/OAuth/Schemas/SlackSchema.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace App\Extensions\OAuth\Schemas;
|
||||
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Infolists\Components\TextEntry;
|
||||
use Filament\Schemas\Components\Wizard\Step;
|
||||
use Illuminate\Support\Facades\Blade;
|
||||
use Illuminate\Support\HtmlString;
|
||||
|
||||
final class SlackSchema extends OAuthSchema
|
||||
{
|
||||
public function getId(): string
|
||||
{
|
||||
return 'slack';
|
||||
}
|
||||
|
||||
public function getSetupSteps(): array
|
||||
{
|
||||
return array_merge([
|
||||
Step::make('Register new Slack OAuth')
|
||||
->schema([
|
||||
TextEntry::make('create_application')
|
||||
->hiddenLabel()
|
||||
->state(new HtmlString(Blade::render('<p><x-filament::link href="https://api.slack.com/apps?new_app=1" target="_blank">Create</x-filament::link> a slack app or <x-filament::link href="https://api.slack.com/apps" target="_blank">select</x-filament::link> the one you will be using for authentication.</p><p>Navigate to the <b>OAuth & Permissions</b> section and configure the <b>Redirect URL</b> using the value below.</p>'))),
|
||||
TextInput::make('_noenv_callback')
|
||||
->label('Redirect URL')
|
||||
->dehydrated()
|
||||
->disabled()
|
||||
->hintCopy()
|
||||
->default(fn () => url('/auth/oauth/callback/slack')),
|
||||
]),
|
||||
], parent::getSetupSteps());
|
||||
}
|
||||
|
||||
public function getIcon(): string
|
||||
{
|
||||
return 'tabler-brand-slack';
|
||||
}
|
||||
|
||||
public function getHexColor(): string
|
||||
{
|
||||
return '#6ecadc';
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
namespace App\Extensions\OAuth\Schemas;
|
||||
|
||||
use Filament\Forms\Components\Placeholder;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Wizard\Step;
|
||||
use Filament\Infolists\Components\TextEntry;
|
||||
use Filament\Schemas\Components\Wizard\Step;
|
||||
use Illuminate\Support\Facades\Blade;
|
||||
use Illuminate\Support\HtmlString;
|
||||
use SocialiteProviders\Steam\Provider;
|
||||
@@ -52,8 +52,9 @@ final class SteamSchema extends OAuthSchema
|
||||
return array_merge([
|
||||
Step::make('Create API Key')
|
||||
->schema([
|
||||
Placeholder::make('')
|
||||
->content(new HtmlString(Blade::render('Visit <x-filament::link href="https://steamcommunity.com/dev/apikey" target="_blank">https://steamcommunity.com/dev/apikey</x-filament::link> to generate an API key.'))),
|
||||
TextEntry::make('create_api_key')
|
||||
->hiddenLabel()
|
||||
->state(new HtmlString(Blade::render('Visit <x-filament::link href="https://steamcommunity.com/dev/apikey" target="_blank">https://steamcommunity.com/dev/apikey</x-filament::link> to generate an API key.'))),
|
||||
]),
|
||||
], parent::getSetupSteps());
|
||||
}
|
||||
|
||||
54
app/Extensions/OAuth/Schemas/XSchema.php
Normal file
54
app/Extensions/OAuth/Schemas/XSchema.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace App\Extensions\OAuth\Schemas;
|
||||
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Infolists\Components\TextEntry;
|
||||
use Filament\Schemas\Components\Wizard\Step;
|
||||
use Illuminate\Support\Facades\Blade;
|
||||
use Illuminate\Support\HtmlString;
|
||||
|
||||
final class XSchema extends OAuthSchema
|
||||
{
|
||||
public function getId(): string
|
||||
{
|
||||
return 'x';
|
||||
}
|
||||
|
||||
public function getSetupSteps(): array
|
||||
{
|
||||
return array_merge([
|
||||
Step::make('Register new X App')
|
||||
->schema([
|
||||
TextEntry::make('create_application')
|
||||
->hiddenLabel()
|
||||
->state(new HtmlString(Blade::render('<p>Visit the <x-filament::link href="https://developer.x.com/en/portal/dashboard" target="_blank">X Developer Dashboard</x-filament::link> and create or select the project app you want to use.</p><p>Go to the app\'s settings and set up <b>User authentication</b> if not yet. Make sure to select <b>Web App</b> as the type of app.</p><p>For the <b>Callback URI / Redirect URL</b> and <b>Website URL</b> set it using the value below.</p>'))),
|
||||
TextInput::make('_noenv_origin')
|
||||
->label('Website URL')
|
||||
->dehydrated()
|
||||
->disabled()
|
||||
->hintCopy()
|
||||
->default(fn () => url('')),
|
||||
TextInput::make('_noenv_callback')
|
||||
->label('Callback URI / Redirect URL')
|
||||
->dehydrated()
|
||||
->disabled()
|
||||
->hintCopy()
|
||||
->default(fn () => url('/auth/oauth/callback/x')),
|
||||
TextEntry::make('register_application')
|
||||
->hiddenLabel()
|
||||
->state(new HtmlString('<p>If you have already set this up go to your app\'s <b>Keys and tokens</b> and obtain the Client ID and Secret there.</p>')),
|
||||
]),
|
||||
], parent::getSetupSteps());
|
||||
}
|
||||
|
||||
public function getIcon(): string
|
||||
{
|
||||
return 'tabler-brand-x';
|
||||
}
|
||||
|
||||
public function getHexColor(): string
|
||||
{
|
||||
return '#1da1f2';
|
||||
}
|
||||
}
|
||||
@@ -2,20 +2,22 @@
|
||||
|
||||
namespace App\Extensions\Spatie\Fractalistic;
|
||||
|
||||
use App\Extensions\League\Fractal\Serializers\PanelSerializer;
|
||||
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
||||
use League\Fractal\Pagination\IlluminatePaginatorAdapter;
|
||||
use League\Fractal\Scope;
|
||||
use League\Fractal\TransformerAbstract;
|
||||
use Spatie\Fractal\Fractal as SpatieFractal;
|
||||
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
||||
use League\Fractal\Pagination\IlluminatePaginatorAdapter;
|
||||
use App\Extensions\League\Fractal\Serializers\PanelSerializer;
|
||||
use Spatie\Fractalistic\Exceptions\InvalidTransformation;
|
||||
use Spatie\Fractalistic\Exceptions\NoTransformerSpecified;
|
||||
|
||||
class Fractal extends SpatieFractal
|
||||
{
|
||||
/**
|
||||
* Create fractal data.
|
||||
*
|
||||
* @throws \Spatie\Fractalistic\Exceptions\InvalidTransformation
|
||||
* @throws \Spatie\Fractalistic\Exceptions\NoTransformerSpecified
|
||||
* @throws InvalidTransformation
|
||||
* @throws NoTransformerSpecified
|
||||
*/
|
||||
public function createData(): Scope
|
||||
{
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
namespace App\Facades;
|
||||
|
||||
use Illuminate\Support\Facades\Facade;
|
||||
use App\Services\Activity\ActivityLogService;
|
||||
use Illuminate\Support\Facades\Facade;
|
||||
|
||||
class Activity extends Facade
|
||||
{
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
namespace App\Facades;
|
||||
|
||||
use Illuminate\Support\Facades\Facade;
|
||||
use App\Services\Activity\ActivityLogTargetableService;
|
||||
use Illuminate\Support\Facades\Facade;
|
||||
|
||||
class LogTarget extends Facade
|
||||
{
|
||||
|
||||
@@ -7,7 +7,7 @@ use Filament\Pages\Dashboard as BaseDashboard;
|
||||
|
||||
class Dashboard extends BaseDashboard
|
||||
{
|
||||
protected static ?string $navigationIcon = 'tabler-layout-dashboard';
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'tabler-layout-dashboard';
|
||||
|
||||
private SoftwareVersionService $softwareVersionService;
|
||||
|
||||
@@ -16,7 +16,7 @@ class Dashboard extends BaseDashboard
|
||||
$this->softwareVersionService = $softwareVersionService;
|
||||
}
|
||||
|
||||
public function getColumns(): int
|
||||
public function getColumns(): int|array
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -13,9 +13,9 @@ use Spatie\Health\ResultStores\ResultStore;
|
||||
|
||||
class Health extends Page
|
||||
{
|
||||
protected static ?string $navigationIcon = 'tabler-heart';
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'tabler-heart';
|
||||
|
||||
protected static string $view = 'filament.pages.health';
|
||||
protected string $view = 'filament.pages.health';
|
||||
|
||||
/** @var array<string, string> */
|
||||
protected $listeners = [
|
||||
@@ -39,7 +39,7 @@ class Health extends Page
|
||||
|
||||
public static function canAccess(): bool
|
||||
{
|
||||
return auth()->user()->can('view health');
|
||||
return user()?->can('view health');
|
||||
}
|
||||
|
||||
protected function getActions(): array
|
||||
@@ -123,7 +123,7 @@ class Health extends Page
|
||||
return $carry;
|
||||
}, []);
|
||||
|
||||
return trans('admin/health.checks.failed') . implode(', ', $failedNames);
|
||||
return trans('admin/health.checks.failed', ['checks' => implode(', ', $failedNames)]);
|
||||
}
|
||||
|
||||
public static function getNavigationIcon(): string
|
||||
|
||||
130
app/Filament/Admin/Pages/ListLogs.php
Normal file
130
app/Filament/Admin/Pages/ListLogs.php
Normal file
@@ -0,0 +1,130 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Pages;
|
||||
|
||||
use Boquizo\FilamentLogViewer\Actions\DeleteAction;
|
||||
use Boquizo\FilamentLogViewer\Actions\DownloadAction;
|
||||
use Boquizo\FilamentLogViewer\Actions\ViewLogAction;
|
||||
use Boquizo\FilamentLogViewer\Pages\ListLogs as BaseListLogs;
|
||||
use Boquizo\FilamentLogViewer\Tables\Columns\LevelColumn;
|
||||
use Boquizo\FilamentLogViewer\Tables\Columns\NameColumn;
|
||||
use Boquizo\FilamentLogViewer\Utils\Level;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Support\Enums\IconSize;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
|
||||
class ListLogs extends BaseListLogs
|
||||
{
|
||||
protected string $view = 'filament.components.list-logs';
|
||||
|
||||
public function getHeading(): string|null|\Illuminate\Contracts\Support\Htmlable
|
||||
{
|
||||
return trans('admin/log.navigation.panel_logs');
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return parent::table($table)
|
||||
->emptyStateHeading(trans('admin/log.empty_table'))
|
||||
->emptyStateIcon('tabler-check')
|
||||
->columns([
|
||||
NameColumn::make('date'),
|
||||
LevelColumn::make(Level::ALL)
|
||||
->tooltip(trans('admin/log.total_logs')),
|
||||
LevelColumn::make(Level::Error)
|
||||
->tooltip(trans('admin/log.error')),
|
||||
LevelColumn::make(Level::Warning)
|
||||
->tooltip(trans('admin/log.warning')),
|
||||
LevelColumn::make(Level::Notice)
|
||||
->tooltip(trans('admin/log.notice')),
|
||||
LevelColumn::make(Level::Info)
|
||||
->tooltip(trans('admin/log.info')),
|
||||
LevelColumn::make(Level::Debug)
|
||||
->tooltip(trans('admin/log.debug')),
|
||||
])
|
||||
->recordActions([
|
||||
ViewLogAction::make()
|
||||
->icon('tabler-file-description')->iconSize(IconSize::Medium),
|
||||
DownloadAction::make()
|
||||
->icon('tabler-file-download')->iconSize(IconSize::Medium),
|
||||
Action::make('uploadLogs')
|
||||
->button()
|
||||
->hiddenLabel()
|
||||
->icon('tabler-world-upload')->iconSize(IconSize::Medium)
|
||||
->requiresConfirmation()
|
||||
->modalHeading(trans('admin/log.actions.upload_logs'))
|
||||
->modalDescription(fn ($record) => trans('admin/log.actions.upload_logs_description', ['file' => $record['date'], 'url' => 'https://logs.pelican.dev']))
|
||||
->action(function ($record) {
|
||||
$logPath = storage_path('logs/' . $record['date']);
|
||||
|
||||
if (!file_exists($logPath)) {
|
||||
Notification::make()
|
||||
->title(trans('admin/log.actions.log_not_found'))
|
||||
->body(trans('admin/log.actions.log_not_found_description', ['filename' => $record['date']]))
|
||||
->danger()
|
||||
->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$lines = file($logPath, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
||||
$totalLines = count($lines);
|
||||
$uploadLines = $totalLines <= 1000 ? $lines : array_slice($lines, -1000);
|
||||
$content = implode("\n", $uploadLines);
|
||||
|
||||
$logUrl = 'https://logs.pelican.dev';
|
||||
try {
|
||||
$response = Http::timeout(10)->asMultipart()->post($logUrl, [
|
||||
[
|
||||
'name' => 'c',
|
||||
'contents' => $content,
|
||||
],
|
||||
[
|
||||
'name' => 'e',
|
||||
'contents' => '14d',
|
||||
],
|
||||
]);
|
||||
|
||||
if ($response->failed()) {
|
||||
Notification::make()
|
||||
->title(trans('admin/log.actions.failed_to_upload'))
|
||||
->body(trans('admin/log.actions.failed_to_upload_description', ['status' => $response->status()]))
|
||||
->danger()
|
||||
->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$data = $response->json();
|
||||
$url = $data['url'];
|
||||
|
||||
Notification::make()
|
||||
->title(trans('admin/log.actions.log_upload'))
|
||||
->body("{$url}")
|
||||
->success()
|
||||
->actions([
|
||||
Action::make('viewLogs')
|
||||
->label(trans('admin/log.actions.view_logs'))
|
||||
->url($url)
|
||||
->openUrlInNewTab(true),
|
||||
])
|
||||
->persistent()
|
||||
->send();
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Notification::make()
|
||||
->title(trans('admin/log.actions.failed_to_upload'))
|
||||
->body($e->getMessage())
|
||||
->danger()
|
||||
->send();
|
||||
|
||||
return;
|
||||
}
|
||||
}),
|
||||
DeleteAction::make()
|
||||
->icon('tabler-trash')->iconSize(IconSize::Medium),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -10,32 +10,33 @@ use App\Notifications\MailTested;
|
||||
use App\Traits\EnvironmentWriterTrait;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use BackedEnum;
|
||||
use Exception;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\ActionGroup;
|
||||
use Filament\Forms\Components\Actions;
|
||||
use Filament\Forms\Components\Actions\Action as FormAction;
|
||||
use Filament\Forms\Components\Component;
|
||||
use Filament\Forms\Components\FileUpload;
|
||||
use Filament\Forms\Components\Group;
|
||||
use Filament\Forms\Components\Hidden;
|
||||
use Filament\Forms\Components\Section;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\Tabs;
|
||||
use Filament\Forms\Components\Tabs\Tab;
|
||||
use Filament\Forms\Components\TagsInput;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Toggle;
|
||||
use Filament\Forms\Components\ToggleButtons;
|
||||
use Filament\Forms\Concerns\InteractsWithForms;
|
||||
use Filament\Forms\Contracts\HasForms;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Forms\Get;
|
||||
use Filament\Forms\Set;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Pages\Concerns\InteractsWithHeaderActions;
|
||||
use Filament\Pages\Page;
|
||||
use Filament\Support\Enums\MaxWidth;
|
||||
use Filament\Schemas\Components\Actions;
|
||||
use Filament\Schemas\Components\Component;
|
||||
use Filament\Schemas\Components\Group;
|
||||
use Filament\Schemas\Components\Section;
|
||||
use Filament\Schemas\Components\StateCasts\BooleanStateCast;
|
||||
use Filament\Schemas\Components\Tabs;
|
||||
use Filament\Schemas\Components\Tabs\Tab;
|
||||
use Filament\Schemas\Components\Utilities\Get;
|
||||
use Filament\Schemas\Components\Utilities\Set;
|
||||
use Filament\Schemas\Contracts\HasSchemas;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Support\Enums\Width;
|
||||
use Illuminate\Http\Client\Factory;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
@@ -43,9 +44,9 @@ use Illuminate\Support\Facades\Notification as MailNotification;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
/**
|
||||
* @property Form $form
|
||||
* @property Schema $form
|
||||
*/
|
||||
class Settings extends Page implements HasForms
|
||||
class Settings extends Page implements HasSchemas
|
||||
{
|
||||
use CanCustomizeHeaderActions, InteractsWithHeaderActions {
|
||||
CanCustomizeHeaderActions::getHeaderActions insteadof InteractsWithHeaderActions;
|
||||
@@ -54,9 +55,9 @@ class Settings extends Page implements HasForms
|
||||
use EnvironmentWriterTrait;
|
||||
use InteractsWithForms;
|
||||
|
||||
protected static ?string $navigationIcon = 'tabler-settings';
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'tabler-settings';
|
||||
|
||||
protected static string $view = 'filament.pages.settings';
|
||||
protected string $view = 'filament.pages.settings';
|
||||
|
||||
protected OAuthService $oauthService;
|
||||
|
||||
@@ -81,7 +82,7 @@ class Settings extends Page implements HasForms
|
||||
|
||||
public static function canAccess(): bool
|
||||
{
|
||||
return auth()->user()->can('view settings');
|
||||
return user()?->can('view settings');
|
||||
}
|
||||
|
||||
public function getTitle(): string
|
||||
@@ -94,13 +95,18 @@ class Settings extends Page implements HasForms
|
||||
return trans('admin/setting.title');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<Component>
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function getFormSchema(): array
|
||||
{
|
||||
return [
|
||||
Tabs::make('Tabs')
|
||||
->columns()
|
||||
->persistTabInQueryString()
|
||||
->disabled(fn () => !auth()->user()->can('update settings'))
|
||||
->disabled(fn () => !user()?->can('update settings'))
|
||||
->tabs([
|
||||
Tab::make('general')
|
||||
->label(trans('admin/setting.navigation.general'))
|
||||
@@ -110,7 +116,7 @@ class Settings extends Page implements HasForms
|
||||
->label(trans('admin/setting.navigation.captcha'))
|
||||
->icon('tabler-shield')
|
||||
->schema($this->captchaSettings())
|
||||
->columns(3),
|
||||
->columns(1),
|
||||
Tab::make('mail')
|
||||
->label(trans('admin/setting.navigation.mail'))
|
||||
->icon('tabler-mail')
|
||||
@@ -119,10 +125,11 @@ class Settings extends Page implements HasForms
|
||||
->label(trans('admin/setting.navigation.backup'))
|
||||
->icon('tabler-box')
|
||||
->schema($this->backupSettings()),
|
||||
Tab::make('OAuth')
|
||||
Tab::make('oauth')
|
||||
->label(trans('admin/setting.navigation.oauth'))
|
||||
->icon('tabler-brand-oauth')
|
||||
->schema($this->oauthSettings()),
|
||||
->schema($this->oauthSettings())
|
||||
->columns(1),
|
||||
Tab::make('misc')
|
||||
->label(trans('admin/setting.navigation.misc'))
|
||||
->icon('tabler-tool')
|
||||
@@ -131,7 +138,9 @@ class Settings extends Page implements HasForms
|
||||
];
|
||||
}
|
||||
|
||||
/** @return Component[] */
|
||||
/** @return Component[]
|
||||
* @throws Exception
|
||||
*/
|
||||
private function generalSettings(): array
|
||||
{
|
||||
return [
|
||||
@@ -144,14 +153,12 @@ class Settings extends Page implements HasForms
|
||||
->schema([
|
||||
TextInput::make('APP_LOGO')
|
||||
->label(trans('admin/setting.general.app_logo'))
|
||||
->hintIcon('tabler-question-mark')
|
||||
->hintIconTooltip(trans('admin/setting.general.app_logo_help'))
|
||||
->hintIcon('tabler-question-mark', trans('admin/setting.general.app_logo_help'))
|
||||
->default(env('APP_LOGO'))
|
||||
->placeholder('/pelican.svg'),
|
||||
TextInput::make('APP_FAVICON')
|
||||
->label(trans('admin/setting.general.app_favicon'))
|
||||
->hintIcon('tabler-question-mark')
|
||||
->hintIconTooltip(trans('admin/setting.general.app_favicon_help'))
|
||||
->hintIcon('tabler-question-mark', trans('admin/setting.general.app_favicon_help'))
|
||||
->required()
|
||||
->default(env('APP_FAVICON', '/pelican.ico'))
|
||||
->placeholder('/pelican.ico'),
|
||||
@@ -166,26 +173,14 @@ class Settings extends Page implements HasForms
|
||||
->offIcon('tabler-x')
|
||||
->onColor('success')
|
||||
->offColor('danger')
|
||||
->formatStateUsing(fn ($state): bool => (bool) $state)
|
||||
->afterStateUpdated(fn ($state, Set $set) => $set('APP_DEBUG', (bool) $state))
|
||||
->stateCast(new BooleanStateCast(false))
|
||||
->default(env('APP_DEBUG', config('app.debug'))),
|
||||
ToggleButtons::make('FILAMENT_TOP_NAVIGATION')
|
||||
->label(trans('admin/setting.general.navigation'))
|
||||
->inline()
|
||||
->options([
|
||||
false => trans('admin/setting.general.sidebar'),
|
||||
true => trans('admin/setting.general.topbar'),
|
||||
])
|
||||
->formatStateUsing(fn ($state): bool => (bool) $state)
|
||||
->afterStateUpdated(fn ($state, Set $set) => $set('FILAMENT_TOP_NAVIGATION', (bool) $state))
|
||||
->default(env('FILAMENT_TOP_NAVIGATION', config('panel.filament.top-navigation'))),
|
||||
]),
|
||||
Group::make()
|
||||
->columns(2)
|
||||
->schema([
|
||||
Select::make('FILAMENT_AVATAR_PROVIDER')
|
||||
->label(trans('admin/setting.general.avatar_provider'))
|
||||
->native(false)
|
||||
->options($this->avatarService->getMappings())
|
||||
->selectablePlaceholder(false)
|
||||
->default(env('FILAMENT_AVATAR_PROVIDER', config('panel.filament.avatar-provider'))),
|
||||
@@ -196,20 +191,27 @@ class Settings extends Page implements HasForms
|
||||
->offIcon('tabler-x')
|
||||
->onColor('success')
|
||||
->offColor('danger')
|
||||
->formatStateUsing(fn ($state) => (bool) $state)
|
||||
->afterStateUpdated(fn ($state, Set $set) => $set('FILAMENT_UPLOADABLE_AVATARS', (bool) $state))
|
||||
->stateCast(new BooleanStateCast(false))
|
||||
->default(env('FILAMENT_UPLOADABLE_AVATARS', config('panel.filament.uploadable-avatars'))),
|
||||
]),
|
||||
ToggleButtons::make('PANEL_USE_BINARY_PREFIX')
|
||||
->label(trans('admin/setting.general.unit_prefix'))
|
||||
->inline()
|
||||
->options([
|
||||
false => trans('admin/setting.general.decimal_prefix'),
|
||||
true => trans('admin/setting.general.binary_prefix'),
|
||||
0 => trans('admin/setting.general.decimal_prefix'),
|
||||
1 => trans('admin/setting.general.binary_prefix'),
|
||||
])
|
||||
->formatStateUsing(fn ($state): bool => (bool) $state)
|
||||
->afterStateUpdated(fn ($state, Set $set) => $set('PANEL_USE_BINARY_PREFIX', (bool) $state))
|
||||
->stateCast(new BooleanStateCast(false, true))
|
||||
->default(env('PANEL_USE_BINARY_PREFIX', config('panel.use_binary_prefix'))),
|
||||
ToggleButtons::make('FILAMENT_DEFAULT_NAVIGATION')
|
||||
->label(trans('admin/setting.general.default_navigation'))
|
||||
->inline()
|
||||
->options([
|
||||
'sidebar' => trans('admin/setting.general.sidebar'),
|
||||
'topbar' => trans('admin/setting.general.topbar'),
|
||||
'mixed' => trans('admin/setting.general.mixed'),
|
||||
])
|
||||
->default(env('FILAMENT_DEFAULT_NAVIGATION', config('panel.filament.default-navigation'))),
|
||||
ToggleButtons::make('APP_2FA_REQUIRED')
|
||||
->label(trans('admin/setting.general.2fa_requirement'))
|
||||
->inline()
|
||||
@@ -223,8 +225,7 @@ class Settings extends Page implements HasForms
|
||||
->default(env('APP_2FA_REQUIRED', config('panel.auth.2fa_required'))),
|
||||
Select::make('FILAMENT_WIDTH')
|
||||
->label(trans('admin/setting.general.display_width'))
|
||||
->native(false)
|
||||
->options(MaxWidth::class)
|
||||
->options(Width::class)
|
||||
->selectablePlaceholder(false)
|
||||
->default(env('FILAMENT_WIDTH', config('panel.filament.display-width'))),
|
||||
TagsInput::make('TRUSTED_PROXIES')
|
||||
@@ -234,17 +235,17 @@ class Settings extends Page implements HasForms
|
||||
->placeholder(trans('admin/setting.general.trusted_proxies_help'))
|
||||
->default(env('TRUSTED_PROXIES', implode(',', Arr::wrap(config('trustedproxy.proxies')))))
|
||||
->hintActions([
|
||||
FormAction::make('clear')
|
||||
Action::make('clear')
|
||||
->label(trans('admin/setting.general.clear'))
|
||||
->color('danger')
|
||||
->icon('tabler-trash')
|
||||
->requiresConfirmation()
|
||||
->authorize(fn () => auth()->user()->can('update settings'))
|
||||
->authorize(fn () => user()?->can('update settings'))
|
||||
->action(fn (Set $set) => $set('TRUSTED_PROXIES', [])),
|
||||
FormAction::make('cloudflare')
|
||||
Action::make('cloudflare')
|
||||
->label(trans('admin/setting.general.set_to_cf'))
|
||||
->icon('tabler-brand-cloudflare')
|
||||
->authorize(fn () => auth()->user()->can('update settings'))
|
||||
->authorize(fn () => user()?->can('update settings'))
|
||||
->action(function (Factory $client, Set $set) {
|
||||
$ips = collect();
|
||||
|
||||
@@ -272,6 +273,8 @@ class Settings extends Page implements HasForms
|
||||
|
||||
/**
|
||||
* @return Component[]
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
private function captchaSettings(): array
|
||||
{
|
||||
@@ -291,12 +294,12 @@ class Settings extends Page implements HasForms
|
||||
->live()
|
||||
->default(env("CAPTCHA_{$id}_ENABLED")),
|
||||
Actions::make([
|
||||
FormAction::make("disable_captcha_$id")
|
||||
Action::make("disable_captcha_$id")
|
||||
->visible(fn (Get $get) => $get("CAPTCHA_{$id}_ENABLED"))
|
||||
->label(trans('admin/setting.captcha.disable'))
|
||||
->color('danger')
|
||||
->action(fn (Set $set) => $set("CAPTCHA_{$id}_ENABLED", false)),
|
||||
FormAction::make("enable_captcha_$id")
|
||||
Action::make("enable_captcha_$id")
|
||||
->visible(fn (Get $get) => !$get("CAPTCHA_{$id}_ENABLED"))
|
||||
->label(trans('admin/setting.captcha.enable'))
|
||||
->color('success')
|
||||
@@ -314,6 +317,8 @@ class Settings extends Page implements HasForms
|
||||
|
||||
/**
|
||||
* @return Component[]
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
private function mailSettings(): array
|
||||
{
|
||||
@@ -333,11 +338,11 @@ class Settings extends Page implements HasForms
|
||||
->live()
|
||||
->default(env('MAIL_MAILER', config('mail.default')))
|
||||
->hintAction(
|
||||
FormAction::make('test')
|
||||
Action::make('test')
|
||||
->label(trans('admin/setting.mail.test_mail'))
|
||||
->icon('tabler-send')
|
||||
->hidden(fn (Get $get) => $get('MAIL_MAILER') === 'log')
|
||||
->authorize(fn () => auth()->user()->can('update settings'))
|
||||
->authorize(fn () => user()?->can('update settings'))
|
||||
->action(function (Get $get) {
|
||||
// Store original mail configuration
|
||||
$originalConfig = [
|
||||
@@ -370,8 +375,8 @@ class Settings extends Page implements HasForms
|
||||
'services.mailgun.endpoint' => $get('MAILGUN_ENDPOINT'),
|
||||
]);
|
||||
|
||||
MailNotification::route('mail', auth()->user()->email)
|
||||
->notify(new MailTested(auth()->user()));
|
||||
MailNotification::route('mail', user()?->email)
|
||||
->notify(new MailTested(user()));
|
||||
|
||||
Notification::make()
|
||||
->title(trans('admin/setting.mail.test_mail_sent'))
|
||||
@@ -391,6 +396,7 @@ class Settings extends Page implements HasForms
|
||||
Section::make(trans('admin/setting.mail.from_settings'))
|
||||
->description(trans('admin/setting.mail.from_settings_help'))
|
||||
->columns()
|
||||
->columnSpanFull()
|
||||
->schema([
|
||||
TextInput::make('MAIL_FROM_ADDRESS')
|
||||
->label(trans('admin/setting.mail.from_address'))
|
||||
@@ -404,6 +410,7 @@ class Settings extends Page implements HasForms
|
||||
]),
|
||||
Section::make(trans('admin/setting.mail.smtp.smtp_title'))
|
||||
->columns()
|
||||
->columnSpanFull()
|
||||
->visible(fn (Get $get) => $get('MAIL_MAILER') === 'smtp')
|
||||
->schema([
|
||||
TextInput::make('MAIL_HOST')
|
||||
@@ -440,6 +447,7 @@ class Settings extends Page implements HasForms
|
||||
]),
|
||||
Section::make(trans('admin/setting.mail.mailgun.mailgun_title'))
|
||||
->columns()
|
||||
->columnSpanFull()
|
||||
->visible(fn (Get $get) => $get('MAIL_MAILER') === 'mailgun')
|
||||
->schema([
|
||||
TextInput::make('MAILGUN_DOMAIN')
|
||||
@@ -460,6 +468,8 @@ class Settings extends Page implements HasForms
|
||||
|
||||
/**
|
||||
* @return Component[]
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
private function backupSettings(): array
|
||||
{
|
||||
@@ -477,6 +487,7 @@ class Settings extends Page implements HasForms
|
||||
Section::make(trans('admin/setting.backup.throttle'))
|
||||
->description(trans('admin/setting.backup.throttle_help'))
|
||||
->columns()
|
||||
->columnSpanFull()
|
||||
->schema([
|
||||
TextInput::make('BACKUP_THROTTLE_LIMIT')
|
||||
->label(trans('admin/setting.backup.limit'))
|
||||
@@ -524,8 +535,7 @@ class Settings extends Page implements HasForms
|
||||
->onColor('success')
|
||||
->offColor('danger')
|
||||
->live()
|
||||
->formatStateUsing(fn ($state): bool => (bool) $state)
|
||||
->afterStateUpdated(fn ($state, Set $set) => $set('AWS_USE_PATH_STYLE_ENDPOINT', (bool) $state))
|
||||
->stateCast(new BooleanStateCast(false))
|
||||
->default(env('AWS_USE_PATH_STYLE_ENDPOINT', config('backups.disks.s3.use_path_style_endpoint'))),
|
||||
]),
|
||||
];
|
||||
@@ -533,6 +543,8 @@ class Settings extends Page implements HasForms
|
||||
|
||||
/**
|
||||
* @return Component[]
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
private function oauthSettings(): array
|
||||
{
|
||||
@@ -553,17 +565,17 @@ class Settings extends Page implements HasForms
|
||||
->live()
|
||||
->default(env($key)),
|
||||
Actions::make([
|
||||
FormAction::make("disable_oauth_$id")
|
||||
Action::make("disable_oauth_$id")
|
||||
->visible(fn (Get $get) => $get($key))
|
||||
->label(trans('admin/setting.oauth.disable'))
|
||||
->color('danger')
|
||||
->action(fn (Set $set) => $set($key, false)),
|
||||
FormAction::make("enable_oauth_$id")
|
||||
Action::make("enable_oauth_$id")
|
||||
->visible(fn (Get $get) => !$get($key))
|
||||
->label(trans('admin/setting.oauth.enable'))
|
||||
->color('success')
|
||||
->steps($schema->getSetupSteps())
|
||||
->modalHeading(trans('admin/setting.oauth.enable') . ' ' . $schema->getName())
|
||||
->modalHeading(trans('admin/setting.oauth.enable_schema', ['schema' => $schema->getName()]))
|
||||
->modalSubmitActionLabel(trans('admin/setting.oauth.enable'))
|
||||
->modalCancelAction(false)
|
||||
->action(function ($data, Set $set) use ($key) {
|
||||
@@ -588,6 +600,8 @@ class Settings extends Page implements HasForms
|
||||
|
||||
/**
|
||||
* @return Component[]
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
private function miscSettings(): array
|
||||
{
|
||||
@@ -606,8 +620,7 @@ class Settings extends Page implements HasForms
|
||||
->offColor('danger')
|
||||
->live()
|
||||
->columnSpanFull()
|
||||
->formatStateUsing(fn ($state): bool => (bool) $state)
|
||||
->afterStateUpdated(fn ($state, Set $set) => $set('PANEL_CLIENT_ALLOCATIONS_ENABLED', (bool) $state))
|
||||
->stateCast(new BooleanStateCast(false))
|
||||
->default(env('PANEL_CLIENT_ALLOCATIONS_ENABLED', config('panel.client_features.allocations.enabled'))),
|
||||
TextInput::make('PANEL_CLIENT_ALLOCATIONS_RANGE_START')
|
||||
->label(trans('admin/setting.misc.auto_allocation.start'))
|
||||
@@ -698,8 +711,7 @@ class Settings extends Page implements HasForms
|
||||
->onColor('success')
|
||||
->offColor('danger')
|
||||
->live()
|
||||
->formatStateUsing(fn ($state): bool => (bool) $state)
|
||||
->afterStateUpdated(fn ($state, Set $set) => $set('APP_ACTIVITY_HIDE_ADMIN', (bool) $state))
|
||||
->stateCast(new BooleanStateCast(false))
|
||||
->default(env('APP_ACTIVITY_HIDE_ADMIN', config('activity.hide_admin_activity'))),
|
||||
]),
|
||||
Section::make(trans('admin/setting.misc.api.title'))
|
||||
@@ -777,8 +789,19 @@ class Settings extends Page implements HasForms
|
||||
$data = $this->form->getState();
|
||||
unset($data['ConsoleFonts']);
|
||||
|
||||
// Convert bools to a string, so they are correctly written to the .env file
|
||||
$data = array_map(fn ($value) => is_bool($value) ? ($value ? 'true' : 'false') : $value, $data);
|
||||
$data = array_map(function ($value) {
|
||||
// Convert bools to a string, so they are correctly written to the .env file
|
||||
if (is_bool($value)) {
|
||||
return $value ? 'true' : 'false';
|
||||
}
|
||||
|
||||
// Convert enum to its value
|
||||
if ($value instanceof BackedEnum) {
|
||||
return $value->value;
|
||||
}
|
||||
|
||||
return $value;
|
||||
}, $data);
|
||||
|
||||
$this->writeToEnvironment($data);
|
||||
|
||||
@@ -806,7 +829,7 @@ class Settings extends Page implements HasForms
|
||||
return [
|
||||
Action::make('save')
|
||||
->action('save')
|
||||
->authorize(fn () => auth()->user()->can('update settings'))
|
||||
->authorize(fn () => user()?->can('update settings'))
|
||||
->keyBindings(['mod+s']),
|
||||
];
|
||||
|
||||
|
||||
105
app/Filament/Admin/Pages/ViewLogs.php
Normal file
105
app/Filament/Admin/Pages/ViewLogs.php
Normal file
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Pages;
|
||||
|
||||
use App\Traits\ResolvesRecordDate;
|
||||
use Boquizo\FilamentLogViewer\Actions\BackAction;
|
||||
use Boquizo\FilamentLogViewer\Actions\DeleteAction;
|
||||
use Boquizo\FilamentLogViewer\Actions\DownloadAction;
|
||||
use Boquizo\FilamentLogViewer\Pages\ViewLog as BaseViewLog;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Support\Enums\IconSize;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
|
||||
class ViewLogs extends BaseViewLog
|
||||
{
|
||||
use ResolvesRecordDate;
|
||||
|
||||
public function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
DeleteAction::make(withTooltip: true)
|
||||
->icon('tabler-trash')->iconSize(IconSize::Medium),
|
||||
DownloadAction::make(withTooltip: true)
|
||||
->icon('tabler-file-download')->iconSize(IconSize::Medium),
|
||||
Action::make('uploadLogs')
|
||||
->button()
|
||||
->hiddenLabel()
|
||||
->icon('tabler-world-upload')->iconSize(IconSize::Medium)
|
||||
->requiresConfirmation()
|
||||
->tooltip(trans('admin/log.actions.upload_tooltip', ['url' => 'logs.pelican.dev']))
|
||||
->modalHeading(trans('admin/log.actions.upload_logs'))
|
||||
->modalDescription(fn () => trans('admin/log.actions.upload_logs_description', ['file' => $this->resolveRecordDate(), 'url' => 'https://logs.pelican.dev']))
|
||||
->action(function () {
|
||||
$logPath = storage_path('logs/' . $this->resolveRecordDate());
|
||||
|
||||
if (!file_exists($logPath)) {
|
||||
Notification::make()
|
||||
->title(trans('admin/log.actions.log_not_found'))
|
||||
->body(trans('admin/log.actions.log_not_found_description', ['filename' => $this->resolveRecordDate()]))
|
||||
->danger()
|
||||
->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$lines = file($logPath, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
||||
$totalLines = count($lines);
|
||||
$uploadLines = $totalLines <= 1000 ? $lines : array_slice($lines, -1000);
|
||||
$content = implode("\n", $uploadLines);
|
||||
|
||||
$logUrl = 'https://logs.pelican.dev';
|
||||
try {
|
||||
$response = Http::timeout(10)->asMultipart()->post($logUrl, [
|
||||
[
|
||||
'name' => 'c',
|
||||
'contents' => $content,
|
||||
],
|
||||
[
|
||||
'name' => 'e',
|
||||
'contents' => '14d',
|
||||
],
|
||||
]);
|
||||
|
||||
if ($response->failed()) {
|
||||
Notification::make()
|
||||
->title(trans('admin/log.actions.failed_to_upload'))
|
||||
->body(trans('admin/log.actions.failed_to_upload_description', ['status' => $response->status()]))
|
||||
->danger()
|
||||
->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$data = $response->json();
|
||||
$url = $data['url'];
|
||||
|
||||
Notification::make()
|
||||
->title(trans('admin/log.actions.log_upload'))
|
||||
->body("{$url}")
|
||||
->success()
|
||||
->actions([
|
||||
Action::make('viewLogs')
|
||||
->label(trans('admin/log.actions.view_logs'))
|
||||
->url($url)
|
||||
->openUrlInNewTab(true),
|
||||
])
|
||||
->persistent()
|
||||
->send();
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Notification::make()
|
||||
->title(trans('admin/log.actions.failed_to_upload'))
|
||||
->body($e->getMessage())
|
||||
->danger()
|
||||
->send();
|
||||
|
||||
return;
|
||||
}
|
||||
}),
|
||||
BackAction::make()
|
||||
->icon('tabler-arrow-left')->iconSize(IconSize::Medium),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,24 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources;
|
||||
namespace App\Filament\Admin\Resources\ApiKeys;
|
||||
|
||||
use App\Filament\Admin\Resources\ApiKeyResource\Pages;
|
||||
use App\Filament\Admin\Resources\UserResource\Pages\EditUser;
|
||||
use App\Filament\Admin\Resources\ApiKeys\Pages\CreateApiKey;
|
||||
use App\Filament\Admin\Resources\ApiKeys\Pages\ListApiKeys;
|
||||
use App\Filament\Admin\Resources\Users\Pages\EditUser;
|
||||
use App\Filament\Components\Tables\Columns\DateTimeColumn;
|
||||
use App\Models\ApiKey;
|
||||
use App\Traits\Filament\CanCustomizePages;
|
||||
use App\Traits\Filament\CanCustomizeRelations;
|
||||
use App\Traits\Filament\CanModifyForm;
|
||||
use App\Traits\Filament\CanModifyTable;
|
||||
use Filament\Forms\Components\Fieldset;
|
||||
use Exception;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Forms\Components\TagsInput;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Forms\Components\ToggleButtons;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Resources\Pages\PageRegistration;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Tables\Actions\CreateAction;
|
||||
use Filament\Tables\Actions\DeleteAction;
|
||||
use Filament\Schemas\Components\Fieldset;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
@@ -32,7 +34,7 @@ class ApiKeyResource extends Resource
|
||||
|
||||
protected static ?string $model = ApiKey::class;
|
||||
|
||||
protected static ?string $navigationIcon = 'tabler-key';
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'tabler-key';
|
||||
|
||||
public static function getNavigationLabel(): string
|
||||
{
|
||||
@@ -66,6 +68,9 @@ class ApiKeyResource extends Resource
|
||||
return trans('admin/dashboard.advanced');
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function defaultTable(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
@@ -88,30 +93,28 @@ class ApiKeyResource extends Resource
|
||||
->sortable(),
|
||||
TextColumn::make('user.username')
|
||||
->label(trans('admin/apikey.table.created_by'))
|
||||
->icon('tabler-user')
|
||||
->url(fn (ApiKey $apiKey) => auth()->user()->can('update', $apiKey->user) ? EditUser::getUrl(['record' => $apiKey->user]) : null),
|
||||
->url(fn (ApiKey $apiKey) => user()?->can('update', $apiKey->user) ? EditUser::getUrl(['record' => $apiKey->user]) : null),
|
||||
])
|
||||
->actions([
|
||||
->recordActions([
|
||||
DeleteAction::make(),
|
||||
])
|
||||
->emptyStateIcon('tabler-key')
|
||||
->emptyStateDescription('')
|
||||
->emptyStateHeading(trans('admin/apikey.empty_table'))
|
||||
->emptyStateHeading(trans('admin/apikey.empty'))
|
||||
->emptyStateActions([
|
||||
CreateAction::make(),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function defaultForm(Form $form): Form
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function defaultForm(Schema $schema): Schema
|
||||
{
|
||||
return $form
|
||||
->schema([
|
||||
return $schema
|
||||
->components([
|
||||
Fieldset::make('Permissions')
|
||||
->columns([
|
||||
'default' => 1,
|
||||
'sm' => 1,
|
||||
'md' => 2,
|
||||
])
|
||||
->columnSpanFull()
|
||||
->schema(
|
||||
collect(ApiKey::getPermissionList())->map(fn ($resource) => ToggleButtons::make('permissions_' . $resource)
|
||||
->label(str($resource)->replace('_', ' ')->title())->inline()
|
||||
@@ -156,8 +159,8 @@ class ApiKeyResource extends Resource
|
||||
public static function getDefaultPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListApiKeys::route('/'),
|
||||
'create' => Pages\CreateApiKey::route('/create'),
|
||||
'index' => ListApiKeys::route('/'),
|
||||
'create' => CreateApiKey::route('/create'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources\ApiKeyResource\Pages;
|
||||
namespace App\Filament\Admin\Resources\ApiKeys\Pages;
|
||||
|
||||
use App\Filament\Admin\Resources\ApiKeyResource;
|
||||
use App\Filament\Admin\Resources\ApiKeys\ApiKeyResource;
|
||||
use App\Models\ApiKey;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
@@ -10,6 +10,7 @@ use Filament\Actions\Action;
|
||||
use Filament\Actions\ActionGroup;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class CreateApiKey extends CreateRecord
|
||||
{
|
||||
@@ -36,8 +37,8 @@ class CreateApiKey extends CreateRecord
|
||||
protected function handleRecordCreation(array $data): Model
|
||||
{
|
||||
$data['identifier'] = ApiKey::generateTokenIdentifier(ApiKey::TYPE_APPLICATION);
|
||||
$data['token'] = str_random(ApiKey::KEY_LENGTH);
|
||||
$data['user_id'] = auth()->user()->id;
|
||||
$data['token'] = Str::random(ApiKey::KEY_LENGTH);
|
||||
$data['user_id'] = user()?->id;
|
||||
$data['key_type'] = ApiKey::TYPE_APPLICATION;
|
||||
|
||||
$permissions = [];
|
||||
@@ -1,8 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources\ApiKeyResource\Pages;
|
||||
namespace App\Filament\Admin\Resources\ApiKeys\Pages;
|
||||
|
||||
use App\Filament\Admin\Resources\ApiKeyResource;
|
||||
use App\Filament\Admin\Resources\ApiKeys\ApiKeyResource;
|
||||
use App\Models\ApiKey;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
@@ -1,26 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources;
|
||||
namespace App\Filament\Admin\Resources\DatabaseHosts;
|
||||
|
||||
use App\Filament\Admin\Resources\DatabaseHostResource\Pages;
|
||||
use App\Filament\Admin\Resources\DatabaseHostResource\RelationManagers;
|
||||
use App\Filament\Admin\Resources\DatabaseHosts\Pages\CreateDatabaseHost;
|
||||
use App\Filament\Admin\Resources\DatabaseHosts\Pages\EditDatabaseHost;
|
||||
use App\Filament\Admin\Resources\DatabaseHosts\Pages\ListDatabaseHosts;
|
||||
use App\Filament\Admin\Resources\DatabaseHosts\Pages\ViewDatabaseHost;
|
||||
use App\Filament\Admin\Resources\DatabaseHosts\RelationManagers\DatabasesRelationManager;
|
||||
use App\Models\DatabaseHost;
|
||||
use App\Traits\Filament\CanCustomizePages;
|
||||
use App\Traits\Filament\CanCustomizeRelations;
|
||||
use App\Traits\Filament\CanModifyForm;
|
||||
use App\Traits\Filament\CanModifyTable;
|
||||
use Filament\Forms\Components\Section;
|
||||
use Exception;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Actions\ViewAction;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Forms\Set;
|
||||
use Filament\Resources\Pages\PageRegistration;
|
||||
use Filament\Resources\RelationManagers\RelationManager;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Tables\Actions\CreateAction;
|
||||
use Filament\Tables\Actions\DeleteBulkAction;
|
||||
use Filament\Tables\Actions\EditAction;
|
||||
use Filament\Tables\Actions\ViewAction;
|
||||
use Filament\Schemas\Components\Section;
|
||||
use Filament\Schemas\Components\Utilities\Set;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
@@ -34,7 +38,7 @@ class DatabaseHostResource extends Resource
|
||||
|
||||
protected static ?string $model = DatabaseHost::class;
|
||||
|
||||
protected static ?string $navigationIcon = 'tabler-database';
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'tabler-database';
|
||||
|
||||
protected static ?string $recordTitleAttribute = 'name';
|
||||
|
||||
@@ -63,6 +67,9 @@ class DatabaseHostResource extends Resource
|
||||
return trans('admin/dashboard.advanced');
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function defaultTable(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
@@ -77,15 +84,13 @@ class DatabaseHostResource extends Resource
|
||||
->label(trans('admin/databasehost.table.username')),
|
||||
TextColumn::make('databases_count')
|
||||
->counts('databases')
|
||||
->icon('tabler-database')
|
||||
->label(trans('admin/databasehost.databases')),
|
||||
TextColumn::make('nodes.name')
|
||||
->icon('tabler-server-2')
|
||||
->badge()
|
||||
->placeholder(trans('admin/databasehost.no_nodes')),
|
||||
])
|
||||
->checkIfRecordIsSelectableUsing(fn (DatabaseHost $databaseHost) => !$databaseHost->databases_count)
|
||||
->actions([
|
||||
->recordActions([
|
||||
ViewAction::make()
|
||||
->hidden(fn ($record) => static::canEdit($record)),
|
||||
EditAction::make(),
|
||||
@@ -101,11 +106,15 @@ class DatabaseHostResource extends Resource
|
||||
]);
|
||||
}
|
||||
|
||||
public static function defaultForm(Form $form): Form
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function defaultForm(Schema $schema): Schema
|
||||
{
|
||||
return $form
|
||||
->schema([
|
||||
return $schema
|
||||
->components([
|
||||
Section::make()
|
||||
->columnSpanFull()
|
||||
->columns([
|
||||
'default' => 2,
|
||||
'sm' => 3,
|
||||
@@ -132,7 +141,7 @@ class DatabaseHostResource extends Resource
|
||||
->maxValue(65535),
|
||||
TextInput::make('max_databases')
|
||||
->label(trans('admin/databasehost.max_database'))
|
||||
->helpertext(trans('admin/databasehost.max_databases_help'))
|
||||
->helperText(trans('admin/databasehost.max_databases_help'))
|
||||
->numeric(),
|
||||
TextInput::make('name')
|
||||
->label(trans('admin/databasehost.display_name'))
|
||||
@@ -157,7 +166,7 @@ class DatabaseHostResource extends Resource
|
||||
->preload()
|
||||
->helperText(trans('admin/databasehost.linked_nodes_help'))
|
||||
->label(trans('admin/databasehost.linked_nodes'))
|
||||
->relationship('nodes', 'name', fn (Builder $query) => $query->whereIn('nodes.id', auth()->user()->accessibleNodes()->pluck('id'))),
|
||||
->relationship('nodes', 'name', fn (Builder $query) => $query->whereIn('nodes.id', user()?->accessibleNodes()->pluck('id'))),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
@@ -166,7 +175,7 @@ class DatabaseHostResource extends Resource
|
||||
public static function getDefaultRelations(): array
|
||||
{
|
||||
return [
|
||||
RelationManagers\DatabasesRelationManager::class,
|
||||
DatabasesRelationManager::class,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -174,10 +183,10 @@ class DatabaseHostResource extends Resource
|
||||
public static function getDefaultPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListDatabaseHosts::route('/'),
|
||||
'create' => Pages\CreateDatabaseHost::route('/create'),
|
||||
'view' => Pages\ViewDatabaseHost::route('/{record}'),
|
||||
'edit' => Pages\EditDatabaseHost::route('/{record}/edit'),
|
||||
'index' => ListDatabaseHosts::route('/'),
|
||||
'create' => CreateDatabaseHost::route('/create'),
|
||||
'view' => ViewDatabaseHost::route('/{record}'),
|
||||
'edit' => EditDatabaseHost::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -187,7 +196,7 @@ class DatabaseHostResource extends Resource
|
||||
|
||||
return $query->where(function (Builder $query) {
|
||||
return $query->whereHas('nodes', function (Builder $query) {
|
||||
$query->whereIn('nodes.id', auth()->user()->accessibleNodes()->pluck('id'));
|
||||
$query->whereIn('nodes.id', user()?->accessibleNodes()->pluck('id'));
|
||||
})->orDoesntHave('nodes');
|
||||
});
|
||||
}
|
||||
@@ -1,30 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources\DatabaseHostResource\Pages;
|
||||
namespace App\Filament\Admin\Resources\DatabaseHosts\Pages;
|
||||
|
||||
use App\Filament\Admin\Resources\DatabaseHostResource;
|
||||
use App\Filament\Admin\Resources\DatabaseHosts\DatabaseHostResource;
|
||||
use App\Services\Databases\Hosts\HostCreationService;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use Filament\Forms\Components\Fieldset;
|
||||
use Exception;
|
||||
use Filament\Forms\Components\Hidden;
|
||||
use Filament\Forms\Components\Placeholder;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Toggle;
|
||||
use Filament\Forms\Components\Wizard\Step;
|
||||
use Filament\Forms\Get;
|
||||
use Filament\Forms\Set;
|
||||
use Filament\Infolists\Components\TextEntry;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
use Filament\Resources\Pages\CreateRecord\Concerns\HasWizard;
|
||||
use Filament\Schemas\Components\Fieldset;
|
||||
use Filament\Schemas\Components\Utilities\Get;
|
||||
use Filament\Schemas\Components\Utilities\Set;
|
||||
use Filament\Schemas\Components\Wizard\Step;
|
||||
use Filament\Support\Exceptions\Halt;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\HtmlString;
|
||||
use Illuminate\Support\Str;
|
||||
use PDOException;
|
||||
use Webbingbrasil\FilamentCopyActions\Forms\Actions\CopyAction;
|
||||
use Throwable;
|
||||
|
||||
class CreateDatabaseHost extends CreateRecord
|
||||
{
|
||||
@@ -43,15 +44,18 @@ class CreateDatabaseHost extends CreateRecord
|
||||
$this->service = $service;
|
||||
}
|
||||
|
||||
/** @return Step[] */
|
||||
/** @return Step[]
|
||||
* @throws Exception
|
||||
*/
|
||||
public function getSteps(): array
|
||||
{
|
||||
return [
|
||||
Step::make(trans('admin/databasehost.setup.preparations'))
|
||||
->columns()
|
||||
->schema([
|
||||
Placeholder::make('')
|
||||
->content(trans('admin/databasehost.setup.note')),
|
||||
TextEntry::make('setup')
|
||||
->hiddenLabel()
|
||||
->state(trans('admin/databasehost.setup.note')),
|
||||
Toggle::make('different_server')
|
||||
->label(new HtmlString(trans('admin/databasehost.setup.different_server')))
|
||||
->dehydrated(false)
|
||||
@@ -84,31 +88,34 @@ class CreateDatabaseHost extends CreateRecord
|
||||
->schema([
|
||||
Fieldset::make(trans('admin/databasehost.setup.database_user'))
|
||||
->schema([
|
||||
Placeholder::make('')
|
||||
->content(new HtmlString(trans('admin/databasehost.setup.cli_login')))
|
||||
TextEntry::make('cli_login')
|
||||
->hiddenLabel()
|
||||
->state(new HtmlString(trans('admin/databasehost.setup.cli_login')))
|
||||
->columnSpanFull(),
|
||||
TextInput::make('create_user')
|
||||
->label(trans('admin/databasehost.setup.command_create_user'))
|
||||
->default(fn (Get $get) => "CREATE USER '{$get('username')}'@'{$get('panel_ip')}' IDENTIFIED BY '{$get('password')}';")
|
||||
->disabled()
|
||||
->dehydrated(false)
|
||||
->suffixAction(fn (string $state) => request()->isSecure() ? CopyAction::make()->copyable($state) : null)
|
||||
->copyable()
|
||||
->columnSpanFull(),
|
||||
TextInput::make('assign_permissions')
|
||||
->label(trans('admin/databasehost.setup.command_assign_permissions'))
|
||||
->default(fn (Get $get) => "GRANT ALL PRIVILEGES ON *.* TO '{$get('username')}'@'{$get('panel_ip')}' WITH GRANT OPTION;")
|
||||
->disabled()
|
||||
->dehydrated(false)
|
||||
->suffixAction(fn (string $state) => request()->isSecure() ? CopyAction::make()->copyable($state) : null)
|
||||
->copyable()
|
||||
->columnSpanFull(),
|
||||
Placeholder::make('')
|
||||
->content(new HtmlString(trans('admin/databasehost.setup.cli_exit')))
|
||||
TextEntry::make('cli_exit')
|
||||
->hiddenLabel()
|
||||
->state(new HtmlString(trans('admin/databasehost.setup.cli_exit')))
|
||||
->columnSpanFull(),
|
||||
]),
|
||||
Fieldset::make(trans('admin/databasehost.setup.external_access'))
|
||||
->schema([
|
||||
Placeholder::make('')
|
||||
->content(new HtmlString(trans('admin/databasehost.setup.allow_external_access')))
|
||||
TextEntry::make('allow_external_access')
|
||||
->hiddenLabel()
|
||||
->state(new HtmlString(trans('admin/databasehost.setup.allow_external_access')))
|
||||
->columnSpanFull(),
|
||||
]),
|
||||
]),
|
||||
@@ -136,7 +143,7 @@ class CreateDatabaseHost extends CreateRecord
|
||||
->maxValue(65535),
|
||||
TextInput::make('max_databases')
|
||||
->label(trans('admin/databasehost.max_database'))
|
||||
->helpertext(trans('admin/databasehost.max_databases_help'))
|
||||
->helperText(trans('admin/databasehost.max_databases_help'))
|
||||
->placeholder(trans('admin/databasehost.unlimited'))
|
||||
->numeric(),
|
||||
TextInput::make('name')
|
||||
@@ -150,11 +157,15 @@ class CreateDatabaseHost extends CreateRecord
|
||||
->preload()
|
||||
->helperText(trans('admin/databasehost.linked_nodes_help'))
|
||||
->label(trans('admin/databasehost.linked_nodes'))
|
||||
->relationship('nodes', 'name', fn (Builder $query) => $query->whereIn('nodes.id', auth()->user()->accessibleNodes()->pluck('id'))),
|
||||
->relationship('nodes', 'name', fn (Builder $query) => $query->whereIn('nodes.id', user()?->accessibleNodes()->pluck('id'))),
|
||||
]),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Halt
|
||||
* @throws Throwable
|
||||
*/
|
||||
protected function handleRecordCreation(array $data): Model
|
||||
{
|
||||
try {
|
||||
@@ -1,8 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources\DatabaseHostResource\Pages;
|
||||
namespace App\Filament\Admin\Resources\DatabaseHosts\Pages;
|
||||
|
||||
use App\Filament\Admin\Resources\DatabaseHostResource;
|
||||
use App\Filament\Admin\Resources\DatabaseHosts\DatabaseHostResource;
|
||||
use App\Models\DatabaseHost;
|
||||
use App\Services\Databases\Hosts\HostUpdateService;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
@@ -1,8 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources\DatabaseHostResource\Pages;
|
||||
namespace App\Filament\Admin\Resources\DatabaseHosts\Pages;
|
||||
|
||||
use App\Filament\Admin\Resources\DatabaseHostResource;
|
||||
use App\Filament\Admin\Resources\DatabaseHosts\DatabaseHostResource;
|
||||
use App\Models\DatabaseHost;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
@@ -1,8 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources\DatabaseHostResource\Pages;
|
||||
namespace App\Filament\Admin\Resources\DatabaseHosts\Pages;
|
||||
|
||||
use App\Filament\Admin\Resources\DatabaseHostResource;
|
||||
use App\Filament\Admin\Resources\DatabaseHosts\DatabaseHostResource;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use Filament\Actions\Action;
|
||||
@@ -1,15 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources\DatabaseHostResource\RelationManagers;
|
||||
namespace App\Filament\Admin\Resources\DatabaseHosts\RelationManagers;
|
||||
|
||||
use App\Filament\Components\Forms\Actions\RotateDatabasePasswordAction;
|
||||
use App\Filament\Components\Actions\RotateDatabasePasswordAction;
|
||||
use App\Filament\Components\Tables\Columns\DateTimeColumn;
|
||||
use App\Models\Database;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Actions\ViewAction;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Resources\RelationManagers\RelationManager;
|
||||
use Filament\Tables\Actions\DeleteAction;
|
||||
use Filament\Tables\Actions\ViewAction;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
@@ -17,10 +17,10 @@ class DatabasesRelationManager extends RelationManager
|
||||
{
|
||||
protected static string $relationship = 'databases';
|
||||
|
||||
public function form(Form $form): Form
|
||||
public function form(Schema $schema): Schema
|
||||
{
|
||||
return $form
|
||||
->schema([
|
||||
return $schema
|
||||
->components([
|
||||
TextInput::make('database')
|
||||
->columnSpanFull(),
|
||||
TextInput::make('username')
|
||||
@@ -49,19 +49,16 @@ class DatabasesRelationManager extends RelationManager
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->recordTitleAttribute('servers')
|
||||
->recordTitleAttribute('database')
|
||||
->heading('')
|
||||
->columns([
|
||||
TextColumn::make('database')
|
||||
->icon('tabler-database'),
|
||||
TextColumn::make('database'),
|
||||
TextColumn::make('username')
|
||||
->label(trans('admin/databasehost.table.username'))
|
||||
->icon('tabler-user'),
|
||||
->label(trans('admin/databasehost.table.username')),
|
||||
TextColumn::make('remote')
|
||||
->label(trans('admin/databasehost.table.remote'))
|
||||
->formatStateUsing(fn (Database $record) => $record->remote === '%' ? trans('admin/databasehost.anywhere'). ' ( % )' : $record->remote),
|
||||
TextColumn::make('server.name')
|
||||
->icon('tabler-brand-docker')
|
||||
->url(fn (Database $database) => route('filament.admin.resources.servers.edit', ['record' => $database->server_id])),
|
||||
TextColumn::make('max_connections')
|
||||
->label(trans('admin/databasehost.table.max_connections'))
|
||||
@@ -69,12 +66,10 @@ class DatabasesRelationManager extends RelationManager
|
||||
DateTimeColumn::make('created_at')
|
||||
->label(trans('admin/databasehost.table.created_at')),
|
||||
])
|
||||
->actions([
|
||||
DeleteAction::make()
|
||||
->authorize(fn (Database $database) => auth()->user()->can('delete', $database)),
|
||||
->recordActions([
|
||||
ViewAction::make()
|
||||
->color('primary')
|
||||
->hidden(fn () => !auth()->user()->can('viewAny', Database::class)),
|
||||
->color('primary'),
|
||||
DeleteAction::make(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -1,287 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources\EggResource\Pages;
|
||||
|
||||
use AbdelhamidErrahmouni\FilamentMonacoEditor\MonacoEditor;
|
||||
use App\Filament\Admin\Resources\EggResource;
|
||||
use App\Filament\Components\Actions\ExportEggAction;
|
||||
use App\Filament\Components\Actions\ImportEggAction;
|
||||
use App\Filament\Components\Forms\Fields\CopyFrom;
|
||||
use App\Models\Egg;
|
||||
use App\Models\EggVariable;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\ActionGroup;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Forms\Components\Checkbox;
|
||||
use Filament\Forms\Components\Fieldset;
|
||||
use Filament\Forms\Components\Hidden;
|
||||
use Filament\Forms\Components\KeyValue;
|
||||
use Filament\Forms\Components\Repeater;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\Tabs;
|
||||
use Filament\Forms\Components\Tabs\Tab;
|
||||
use Filament\Forms\Components\TagsInput;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Toggle;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Forms\Get;
|
||||
use Filament\Forms\Set;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
use Illuminate\Validation\Rules\Unique;
|
||||
|
||||
class EditEgg extends EditRecord
|
||||
{
|
||||
use CanCustomizeHeaderActions;
|
||||
use CanCustomizeHeaderWidgets;
|
||||
|
||||
protected static string $resource = EggResource::class;
|
||||
|
||||
public function form(Form $form): Form
|
||||
{
|
||||
return $form
|
||||
->schema([
|
||||
Tabs::make()->tabs([
|
||||
Tab::make(trans('admin/egg.tabs.configuration'))
|
||||
->columns(['default' => 1, 'sm' => 1, 'md' => 2, 'lg' => 4])
|
||||
->icon('tabler-egg')
|
||||
->schema([
|
||||
TextInput::make('name')
|
||||
->label(trans('admin/egg.name'))
|
||||
->required()
|
||||
->maxLength(255)
|
||||
->columnSpan(['default' => 1, 'sm' => 1, 'md' => 2, 'lg' => 1])
|
||||
->helperText(trans('admin/egg.name_help')),
|
||||
TextInput::make('uuid')
|
||||
->label(trans('admin/egg.egg_uuid'))
|
||||
->disabled()
|
||||
->columnSpan(['default' => 1, 'sm' => 1, 'md' => 1, 'lg' => 2])
|
||||
->helperText(trans('admin/egg.uuid_help')),
|
||||
TextInput::make('id')
|
||||
->label(trans('admin/egg.egg_id'))
|
||||
->disabled(),
|
||||
Textarea::make('description')
|
||||
->label(trans('admin/egg.description'))
|
||||
->rows(3)
|
||||
->columnSpan(['default' => 1, 'sm' => 1, 'md' => 2, 'lg' => 2])
|
||||
->helperText(trans('admin/egg.description_help')),
|
||||
TextInput::make('author')
|
||||
->label(trans('admin/egg.author'))
|
||||
->required()
|
||||
->maxLength(255)
|
||||
->email()
|
||||
->disabled()
|
||||
->columnSpan(['default' => 1, 'sm' => 1, 'md' => 2, 'lg' => 2])
|
||||
->helperText(trans('admin/egg.author_help_edit')),
|
||||
Textarea::make('startup')
|
||||
->label(trans('admin/egg.startup'))
|
||||
->rows(3)
|
||||
->columnSpanFull()
|
||||
->required()
|
||||
->helperText(trans('admin/egg.startup_help')),
|
||||
TagsInput::make('file_denylist')
|
||||
->label(trans('admin/egg.file_denylist'))
|
||||
->placeholder('denied-file.txt')
|
||||
->helperText(trans('admin/egg.file_denylist_help'))
|
||||
->columnSpan(['default' => 1, 'sm' => 1, 'md' => 2, 'lg' => 2]),
|
||||
TagsInput::make('features')
|
||||
->label(trans('admin/egg.features'))
|
||||
->columnSpan(['default' => 1, 'sm' => 1, 'md' => 1, 'lg' => 1]),
|
||||
Toggle::make('force_outgoing_ip')
|
||||
->inline(false)
|
||||
->label(trans('admin/egg.force_ip'))
|
||||
->hintIcon('tabler-question-mark')
|
||||
->hintIconTooltip(trans('admin/egg.force_ip_help')),
|
||||
Hidden::make('script_is_privileged')
|
||||
->helperText('The docker images available to servers using this egg.'),
|
||||
TagsInput::make('tags')
|
||||
->label(trans('admin/egg.tags'))
|
||||
->columnSpan(['default' => 1, 'sm' => 1, 'md' => 2, 'lg' => 2]),
|
||||
TextInput::make('update_url')
|
||||
->label(trans('admin/egg.update_url'))
|
||||
->url()
|
||||
->hintIcon('tabler-question-mark')
|
||||
->hintIconTooltip(trans('admin/egg.update_url_help'))
|
||||
->columnSpan(['default' => 1, 'sm' => 1, 'md' => 2, 'lg' => 2]),
|
||||
KeyValue::make('docker_images')
|
||||
->label(trans('admin/egg.docker_images'))
|
||||
->live()
|
||||
->columnSpanFull()
|
||||
->required()
|
||||
->addActionLabel(trans('admin/egg.add_image'))
|
||||
->keyLabel(trans('admin/egg.docker_name'))
|
||||
->valueLabel(trans('admin/egg.docker_uri'))
|
||||
->helperText(trans('admin/egg.docker_help')),
|
||||
]),
|
||||
Tab::make(trans('admin/egg.tabs.process_management'))
|
||||
->columns()
|
||||
->icon('tabler-server-cog')
|
||||
->schema([
|
||||
CopyFrom::make('copy_process_from')
|
||||
->process(),
|
||||
TextInput::make('config_stop')
|
||||
->label(trans('admin/egg.stop_command'))
|
||||
->maxLength(255)
|
||||
->helperText(trans('admin/egg.stop_command_help')),
|
||||
Textarea::make('config_startup')->rows(10)->json()
|
||||
->label(trans('admin/egg.start_config'))
|
||||
->helperText(trans('admin/egg.start_config_help')),
|
||||
Textarea::make('config_files')->rows(10)->json()
|
||||
->label(trans('admin/egg.config_files'))
|
||||
->helperText(trans('admin/egg.config_files_help')),
|
||||
Textarea::make('config_logs')->rows(10)->json()
|
||||
->label(trans('admin/egg.log_config'))
|
||||
->helperText(trans('admin/egg.log_config_help')),
|
||||
]),
|
||||
Tab::make(trans('admin/egg.tabs.egg_variables'))
|
||||
->columnSpanFull()
|
||||
->icon('tabler-variable')
|
||||
->schema([
|
||||
Repeater::make('variables')
|
||||
->label('')
|
||||
->grid()
|
||||
->relationship('variables')
|
||||
->name('name')
|
||||
->reorderable()
|
||||
->collapsible()->collapsed()
|
||||
->orderColumn()
|
||||
->addActionLabel(trans('admin/egg.add_new_variable'))
|
||||
->itemLabel(fn (array $state) => $state['name'])
|
||||
->mutateRelationshipDataBeforeCreateUsing(function (array $data): array {
|
||||
$data['default_value'] ??= '';
|
||||
$data['description'] ??= '';
|
||||
$data['rules'] ??= [];
|
||||
$data['user_viewable'] ??= '';
|
||||
$data['user_editable'] ??= '';
|
||||
|
||||
return $data;
|
||||
})
|
||||
->mutateRelationshipDataBeforeSaveUsing(function (array $data): array {
|
||||
$data['default_value'] ??= '';
|
||||
$data['description'] ??= '';
|
||||
$data['rules'] ??= [];
|
||||
$data['user_viewable'] ??= '';
|
||||
$data['user_editable'] ??= '';
|
||||
|
||||
return $data;
|
||||
})
|
||||
->schema([
|
||||
TextInput::make('name')
|
||||
->label(trans('admin/egg.name'))
|
||||
->live()
|
||||
->debounce(750)
|
||||
->maxLength(255)
|
||||
->columnSpanFull()
|
||||
->afterStateUpdated(fn (Set $set, $state) => $set('env_variable', str($state)->trim()->snake()->upper()->toString()))
|
||||
->unique(modifyRuleUsing: fn (Unique $rule, Get $get) => $rule->where('egg_id', $get('../../id')), ignoreRecord: true)
|
||||
->validationMessages([
|
||||
'unique' => trans('admin/egg.error_unique'),
|
||||
])
|
||||
->required(),
|
||||
Textarea::make('description')->label(trans('admin/egg.description'))->columnSpanFull(),
|
||||
TextInput::make('env_variable')
|
||||
->label(trans('admin/egg.environment_variable'))
|
||||
->maxLength(255)
|
||||
->prefix('{{')
|
||||
->suffix('}}')
|
||||
->hintIcon('tabler-code')
|
||||
->hintIconTooltip(fn ($state) => "{{{$state}}}")
|
||||
->unique(modifyRuleUsing: fn (Unique $rule, Get $get) => $rule->where('egg_id', $get('../../id')), ignoreRecord: true)
|
||||
->rules(EggVariable::getRulesForField('env_variable'))
|
||||
->validationMessages([
|
||||
'unique' => trans('admin/egg.error_unique'),
|
||||
'required' => trans('admin/egg.error_required'),
|
||||
'*' => trans('admin/egg.error_reserved'),
|
||||
])
|
||||
->required(),
|
||||
TextInput::make('default_value')->label(trans('admin/egg.default_value'))->maxLength(255),
|
||||
Fieldset::make(trans('admin/egg.user_permissions'))
|
||||
->schema([
|
||||
Checkbox::make('user_viewable')->label(trans('admin/egg.viewable')),
|
||||
Checkbox::make('user_editable')->label(trans('admin/egg.editable')),
|
||||
]),
|
||||
TagsInput::make('rules')
|
||||
->label(trans('admin/egg.rules'))
|
||||
->columnSpanFull()
|
||||
->reorderable()
|
||||
->suggestions([
|
||||
'required',
|
||||
'nullable',
|
||||
'string',
|
||||
'integer',
|
||||
'numeric',
|
||||
'boolean',
|
||||
'alpha',
|
||||
'alpha_dash',
|
||||
'alpha_num',
|
||||
'url',
|
||||
'email',
|
||||
'regex:',
|
||||
'min:',
|
||||
'max:',
|
||||
'between:',
|
||||
'between:1024,65535',
|
||||
'in:',
|
||||
'in:true,false',
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
Tab::make(trans('admin/egg.tabs.install_script'))
|
||||
->columns(3)
|
||||
->icon('tabler-file-download')
|
||||
->schema([
|
||||
CopyFrom::make('copy_script_from')
|
||||
->script(),
|
||||
TextInput::make('script_container')
|
||||
->label(trans('admin/egg.script_container'))
|
||||
->required()
|
||||
->maxLength(255)
|
||||
->placeholder('ghcr.io/pelican-eggs/installers:debian'),
|
||||
Select::make('script_entry')
|
||||
->label(trans('admin/egg.script_entry'))
|
||||
->native(false)
|
||||
->selectablePlaceholder(false)
|
||||
->options([
|
||||
'bash' => 'bash',
|
||||
'ash' => 'ash',
|
||||
'/bin/bash' => '/bin/bash',
|
||||
])
|
||||
->required(),
|
||||
MonacoEditor::make('script_install')
|
||||
->label(trans('admin/egg.script_install'))
|
||||
->placeholderText('')
|
||||
->columnSpanFull()
|
||||
->fontSize('16px')
|
||||
->language('shell')
|
||||
->view('filament.plugins.monaco-editor'),
|
||||
]),
|
||||
])->columnSpanFull()->persistTabInQueryString(),
|
||||
]);
|
||||
}
|
||||
|
||||
/** @return array<Action|ActionGroup> */
|
||||
protected function getDefaultHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
DeleteAction::make()
|
||||
->disabled(fn (Egg $egg): bool => $egg->servers()->count() > 0)
|
||||
->label(fn (Egg $egg): string => $egg->servers()->count() <= 0 ? trans('filament-actions::delete.single.label') : trans('admin/egg.in_use')),
|
||||
ExportEggAction::make(),
|
||||
ImportEggAction::make()
|
||||
->multiple(false),
|
||||
$this->getSaveFormAction()->formId('form'),
|
||||
];
|
||||
}
|
||||
|
||||
public function refreshForm(): void
|
||||
{
|
||||
$this->fillForm();
|
||||
}
|
||||
|
||||
protected function getFormActions(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources;
|
||||
namespace App\Filament\Admin\Resources\Eggs;
|
||||
|
||||
use App\Filament\Admin\Resources\EggResource\Pages;
|
||||
use App\Filament\Admin\Resources\EggResource\RelationManagers;
|
||||
use App\Enums\CustomizationKey;
|
||||
use App\Filament\Admin\Resources\Eggs\Pages\CreateEgg;
|
||||
use App\Filament\Admin\Resources\Eggs\Pages\EditEgg;
|
||||
use App\Filament\Admin\Resources\Eggs\Pages\ListEggs;
|
||||
use App\Filament\Admin\Resources\Eggs\RelationManagers\ServersRelationManager;
|
||||
use App\Models\Egg;
|
||||
use App\Traits\Filament\CanCustomizePages;
|
||||
use App\Traits\Filament\CanCustomizeRelations;
|
||||
@@ -18,18 +21,18 @@ class EggResource extends Resource
|
||||
|
||||
protected static ?string $model = Egg::class;
|
||||
|
||||
protected static ?string $navigationIcon = 'tabler-eggs';
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'tabler-eggs';
|
||||
|
||||
protected static ?string $recordTitleAttribute = 'name';
|
||||
|
||||
public static function getNavigationBadge(): ?string
|
||||
{
|
||||
return static::getModel()::count() ?: null;
|
||||
return ($count = static::getModel()::count()) > 0 ? (string) $count : null;
|
||||
}
|
||||
|
||||
public static function getNavigationGroup(): ?string
|
||||
{
|
||||
return config('panel.filament.top-navigation', false) ? null : trans('admin/dashboard.server');
|
||||
return user()?->getCustomization(CustomizationKey::TopNavigation) ? false : trans('admin/dashboard.server');
|
||||
}
|
||||
|
||||
public static function getNavigationLabel(): string
|
||||
@@ -56,7 +59,7 @@ class EggResource extends Resource
|
||||
public static function getDefaultRelations(): array
|
||||
{
|
||||
return [
|
||||
RelationManagers\ServersRelationManager::class,
|
||||
ServersRelationManager::class,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -64,9 +67,9 @@ class EggResource extends Resource
|
||||
public static function getDefaultPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListEggs::route('/'),
|
||||
'create' => Pages\CreateEgg::route('/create'),
|
||||
'edit' => Pages\EditEgg::route('/{record}/edit'),
|
||||
'index' => ListEggs::route('/'),
|
||||
'create' => CreateEgg::route('/create'),
|
||||
'edit' => EditEgg::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,31 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources\EggResource\Pages;
|
||||
namespace App\Filament\Admin\Resources\Eggs\Pages;
|
||||
|
||||
use AbdelhamidErrahmouni\FilamentMonacoEditor\MonacoEditor;
|
||||
use App\Filament\Admin\Resources\EggResource;
|
||||
use App\Filament\Admin\Resources\Eggs\EggResource;
|
||||
use App\Filament\Components\Forms\Fields\CopyFrom;
|
||||
use App\Models\EggVariable;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use Exception;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\ActionGroup;
|
||||
use Filament\Forms\Components\Checkbox;
|
||||
use Filament\Forms\Components\Fieldset;
|
||||
use Filament\Forms\Components\CodeEditor;
|
||||
use Filament\Forms\Components\Hidden;
|
||||
use Filament\Forms\Components\KeyValue;
|
||||
use Filament\Forms\Components\Repeater;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\Tabs;
|
||||
use Filament\Forms\Components\Tabs\Tab;
|
||||
use Filament\Forms\Components\TagsInput;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Toggle;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Forms\Get;
|
||||
use Filament\Forms\Set;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
use Filament\Schemas\Components\Fieldset;
|
||||
use Filament\Schemas\Components\Tabs;
|
||||
use Filament\Schemas\Components\Tabs\Tab;
|
||||
use Filament\Schemas\Components\Utilities\Get;
|
||||
use Filament\Schemas\Components\Utilities\Set;
|
||||
use Filament\Schemas\Schema;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\Rules\Unique;
|
||||
@@ -52,12 +53,16 @@ class CreateEgg extends CreateRecord
|
||||
return [];
|
||||
}
|
||||
|
||||
public function form(Form $form): Form
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function form(Schema $schema): Schema
|
||||
{
|
||||
return $form
|
||||
->schema([
|
||||
return $schema
|
||||
->components([
|
||||
Tabs::make()->tabs([
|
||||
Tab::make(trans('admin/egg.tabs.configuration'))
|
||||
Tab::make('configuration')
|
||||
->label(trans('admin/egg.tabs.configuration'))
|
||||
->columns(['default' => 1, 'sm' => 1, 'md' => 2, 'lg' => 4])
|
||||
->schema([
|
||||
TextInput::make('name')
|
||||
@@ -78,14 +83,16 @@ class CreateEgg extends CreateRecord
|
||||
->rows(2)
|
||||
->columnSpanFull()
|
||||
->helperText(trans('admin/egg.description_help')),
|
||||
Textarea::make('startup')
|
||||
->label(trans('admin/egg.startup'))
|
||||
->rows(3)
|
||||
KeyValue::make('startup_commands')
|
||||
->label(trans('admin/egg.startup_commands'))
|
||||
->live()
|
||||
->columnSpanFull()
|
||||
->required()
|
||||
->placeholder(implode("\n", [
|
||||
'java -Xms128M -XX:MaxRAMPercentage=95.0 -jar {{SERVER_JARFILE}}',
|
||||
]))
|
||||
->addActionLabel(trans('admin/egg.add_startup'))
|
||||
->keyLabel(trans('admin/egg.startup_name'))
|
||||
->keyPlaceholder('Default')
|
||||
->valueLabel(trans('admin/egg.startup_command'))
|
||||
->valuePlaceholder('java -Xms128M -XX:MaxRAMPercentage=95.0 -jar {{SERVER_JARFILE}}')
|
||||
->helperText(trans('admin/egg.startup_help')),
|
||||
TagsInput::make('file_denylist')
|
||||
->label(trans('admin/egg.file_denylist'))
|
||||
@@ -97,8 +104,7 @@ class CreateEgg extends CreateRecord
|
||||
->columnSpan(['default' => 1, 'sm' => 1, 'md' => 1, 'lg' => 1]),
|
||||
Toggle::make('force_outgoing_ip')
|
||||
->label(trans('admin/egg.force_ip'))
|
||||
->hintIcon('tabler-question-mark')
|
||||
->hintIconTooltip(trans('admin/egg.force_ip_help')),
|
||||
->hintIcon('tabler-question-mark', trans('admin/egg.force_ip_help')),
|
||||
Hidden::make('script_is_privileged')
|
||||
->default(1),
|
||||
TagsInput::make('tags')
|
||||
@@ -106,8 +112,7 @@ class CreateEgg extends CreateRecord
|
||||
->columnSpan(['default' => 1, 'sm' => 1, 'md' => 2, 'lg' => 2]),
|
||||
TextInput::make('update_url')
|
||||
->label(trans('admin/egg.update_url'))
|
||||
->hintIcon('tabler-question-mark')
|
||||
->hintIconTooltip(trans('admin/egg.update_url_help'))
|
||||
->hintIcon('tabler-question-mark', trans('admin/egg.update_url_help'))
|
||||
->columnSpan(['default' => 1, 'sm' => 1, 'md' => 2, 'lg' => 2])
|
||||
->url(),
|
||||
KeyValue::make('docker_images')
|
||||
@@ -123,7 +128,8 @@ class CreateEgg extends CreateRecord
|
||||
->helperText(trans('admin/egg.docker_help')),
|
||||
]),
|
||||
|
||||
Tab::make(trans('admin/egg.tabs.process_management'))
|
||||
Tab::make('process_management')
|
||||
->label(trans('admin/egg.tabs.process_management'))
|
||||
->columns()
|
||||
->schema([
|
||||
CopyFrom::make('copy_process_from')
|
||||
@@ -146,15 +152,15 @@ class CreateEgg extends CreateRecord
|
||||
->default('{}')
|
||||
->helperText(trans('admin/egg.log_config_help')),
|
||||
]),
|
||||
Tab::make(trans('admin/egg.tabs.egg_variables'))
|
||||
Tab::make('egg_variables')
|
||||
->label(trans('admin/egg.tabs.egg_variables'))
|
||||
->columnSpanFull()
|
||||
->schema([
|
||||
Repeater::make('variables')
|
||||
->label('')
|
||||
->hiddenLabel()
|
||||
->addActionLabel(trans('admin/egg.add_new_variable'))
|
||||
->grid()
|
||||
->relationship('variables')
|
||||
->name('name')
|
||||
->reorderable()->orderColumn()
|
||||
->collapsible()->collapsed()
|
||||
->columnSpan(2)
|
||||
@@ -186,7 +192,7 @@ class CreateEgg extends CreateRecord
|
||||
->maxLength(255)
|
||||
->columnSpanFull()
|
||||
->afterStateUpdated(fn (Set $set, $state) => $set('env_variable', str($state)->trim()->snake()->upper()->toString()))
|
||||
->unique(modifyRuleUsing: fn (Unique $rule, Get $get) => $rule->where('egg_id', $get('../../id')), ignoreRecord: true)
|
||||
->unique(modifyRuleUsing: fn (Unique $rule, Get $get) => $rule->where('egg_id', $get('../../id')))
|
||||
->validationMessages([
|
||||
'unique' => trans('admin/egg.error_unique'),
|
||||
])
|
||||
@@ -197,9 +203,8 @@ class CreateEgg extends CreateRecord
|
||||
->maxLength(255)
|
||||
->prefix('{{')
|
||||
->suffix('}}')
|
||||
->hintIcon('tabler-code')
|
||||
->hintIconTooltip(fn ($state) => "{{{$state}}}")
|
||||
->unique(modifyRuleUsing: fn (Unique $rule, Get $get) => $rule->where('egg_id', $get('../../id')), ignoreRecord: true)
|
||||
->hintIcon('tabler-code', fn ($state) => "{{{$state}}}")
|
||||
->unique(modifyRuleUsing: fn (Unique $rule, Get $get) => $rule->where('egg_id', $get('../../id')))
|
||||
->rules(EggVariable::getRulesForField('env_variable'))
|
||||
->validationMessages([
|
||||
'unique' => trans('admin/egg.error_unique'),
|
||||
@@ -207,7 +212,7 @@ class CreateEgg extends CreateRecord
|
||||
'*' => trans('admin/egg.error_reserved'),
|
||||
])
|
||||
->required(),
|
||||
TextInput::make('default_value')->label(trans('admin/egg.default_value'))->maxLength(255),
|
||||
TextInput::make('default_value')->label(trans('admin/egg.default_value')),
|
||||
Fieldset::make(trans('admin/egg.user_permissions'))
|
||||
->schema([
|
||||
Checkbox::make('user_viewable')->label(trans('admin/egg.viewable')),
|
||||
@@ -239,7 +244,8 @@ class CreateEgg extends CreateRecord
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
Tab::make(trans('admin/egg.tabs.install_script'))
|
||||
Tab::make('install_script')
|
||||
->label(trans('admin/egg.tabs.install_script'))
|
||||
->columns(3)
|
||||
->schema([
|
||||
CopyFrom::make('copy_script_from')
|
||||
@@ -251,7 +257,6 @@ class CreateEgg extends CreateRecord
|
||||
->default('ghcr.io/pelican-eggs/installers:debian'),
|
||||
Select::make('script_entry')
|
||||
->label(trans('admin/egg.script_entry'))
|
||||
->native(false)
|
||||
->selectablePlaceholder(false)
|
||||
->default('bash')
|
||||
->options([
|
||||
@@ -260,13 +265,10 @@ class CreateEgg extends CreateRecord
|
||||
'/bin/bash' => '/bin/bash',
|
||||
])
|
||||
->required(),
|
||||
MonacoEditor::make('script_install')
|
||||
CodeEditor::make('script_install')
|
||||
->label(trans('admin/egg.script_install'))
|
||||
->columnSpanFull()
|
||||
->fontSize('16px')
|
||||
->language('shell')
|
||||
->lazy()
|
||||
->view('filament.plugins.monaco-editor'),
|
||||
->lazy(),
|
||||
]),
|
||||
|
||||
])->columnSpanFull()->persistTabInQueryString(),
|
||||
471
app/Filament/Admin/Resources/Eggs/Pages/EditEgg.php
Normal file
471
app/Filament/Admin/Resources/Eggs/Pages/EditEgg.php
Normal file
@@ -0,0 +1,471 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources\Eggs\Pages;
|
||||
|
||||
use App\Filament\Admin\Resources\Eggs\EggResource;
|
||||
use App\Filament\Components\Actions\ExportEggAction;
|
||||
use App\Filament\Components\Actions\ImportEggAction;
|
||||
use App\Filament\Components\Forms\Fields\CopyFrom;
|
||||
use App\Models\Egg;
|
||||
use App\Models\EggVariable;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use Exception;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\ActionGroup;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Forms\Components\Checkbox;
|
||||
use Filament\Forms\Components\CodeEditor;
|
||||
use Filament\Forms\Components\FileUpload;
|
||||
use Filament\Forms\Components\Hidden;
|
||||
use Filament\Forms\Components\KeyValue;
|
||||
use Filament\Forms\Components\Repeater;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\TagsInput;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Toggle;
|
||||
use Filament\Infolists\Components\TextEntry;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
use Filament\Schemas\Components\Fieldset;
|
||||
use Filament\Schemas\Components\Flex;
|
||||
use Filament\Schemas\Components\Grid;
|
||||
use Filament\Schemas\Components\Image;
|
||||
use Filament\Schemas\Components\Tabs;
|
||||
use Filament\Schemas\Components\Tabs\Tab;
|
||||
use Filament\Schemas\Components\Utilities\Get;
|
||||
use Filament\Schemas\Components\Utilities\Set;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Support\Enums\IconSize;
|
||||
use Illuminate\Validation\Rules\Unique;
|
||||
|
||||
class EditEgg extends EditRecord
|
||||
{
|
||||
use CanCustomizeHeaderActions;
|
||||
use CanCustomizeHeaderWidgets;
|
||||
|
||||
protected static string $resource = EggResource::class;
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
Tabs::make()->tabs([
|
||||
Tab::make('configuration')
|
||||
->label(trans('admin/egg.tabs.configuration'))
|
||||
->columns(['default' => 2, 'sm' => 2, 'md' => 4, 'lg' => 6])
|
||||
->icon('tabler-egg')
|
||||
->schema([
|
||||
Grid::make(2)
|
||||
->columnSpan(1)
|
||||
->schema([
|
||||
Image::make('', '')
|
||||
->hidden(fn ($record) => !$record->image)
|
||||
->url(fn ($record) => $record->image)
|
||||
->alt('')
|
||||
->alignJustify()
|
||||
->imageSize(150)
|
||||
->columnSpanFull(),
|
||||
Flex::make([
|
||||
Action::make('uploadImage')
|
||||
->iconButton()
|
||||
->iconSize(IconSize::Large)
|
||||
->icon('tabler-photo-up')
|
||||
->modal()
|
||||
->modalHeading('')
|
||||
->modalSubmitActionLabel(trans('admin/egg.import.import_image'))
|
||||
->schema([
|
||||
Tabs::make()
|
||||
->contained(false)
|
||||
->tabs([
|
||||
Tab::make(trans('admin/egg.import.url'))
|
||||
->schema([
|
||||
Hidden::make('base64Image'),
|
||||
TextInput::make('image_url')
|
||||
->label(trans('admin/egg.import.image_url'))
|
||||
->reactive()
|
||||
->autocomplete(false)
|
||||
->debounce(500)
|
||||
->afterStateUpdated(function ($state, Set $set) {
|
||||
if (!$state) {
|
||||
$set('image_url_error', null);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (!filter_var($state, FILTER_VALIDATE_URL)) {
|
||||
throw new \Exception(trans('admin/egg.import.invalid_url'));
|
||||
}
|
||||
|
||||
$allowedExtensions = [
|
||||
'png' => 'image/png',
|
||||
'jpg' => 'image/jpeg',
|
||||
'jpeg' => 'image/jpeg',
|
||||
'gif' => 'image/gif',
|
||||
'webp' => 'image/webp',
|
||||
'svg' => 'image/svg+xml',
|
||||
];
|
||||
|
||||
$extension = strtolower(pathinfo(parse_url($state, PHP_URL_PATH), PATHINFO_EXTENSION));
|
||||
|
||||
if (!array_key_exists($extension, $allowedExtensions)) {
|
||||
throw new \Exception(trans('admin/egg.import.unsupported_format', ['format' => implode(', ', $allowedExtensions)]));
|
||||
}
|
||||
|
||||
$host = parse_url($state, PHP_URL_HOST);
|
||||
$ip = gethostbyname($host);
|
||||
|
||||
if (
|
||||
filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) === false
|
||||
) {
|
||||
throw new \Exception(trans('admin/egg.import.no_local_ip'));
|
||||
}
|
||||
|
||||
$context = stream_context_create([
|
||||
'http' => ['timeout' => 3],
|
||||
'https' => [
|
||||
'timeout' => 3,
|
||||
'verify_peer' => true,
|
||||
'verify_peer_name' => true,
|
||||
],
|
||||
]);
|
||||
|
||||
$imageContent = @file_get_contents($state, false, $context, 0, 1048576); // 1024KB
|
||||
|
||||
if (!$imageContent) {
|
||||
throw new \Exception(trans('admin/egg.import.image_error'));
|
||||
}
|
||||
|
||||
if (strlen($imageContent) >= 1048576) {
|
||||
throw new \Exception(trans('admin/egg.import.image_too_large'));
|
||||
}
|
||||
|
||||
$mimeType = $allowedExtensions[$extension];
|
||||
$base64 = 'data:' . $mimeType . ';base64,' . base64_encode($imageContent);
|
||||
|
||||
$set('base64Image', $base64);
|
||||
$set('image_url_error', null);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$set('image_url_error', $e->getMessage());
|
||||
$set('base64Image', null);
|
||||
}
|
||||
}),
|
||||
TextEntry::make('image_url_error')
|
||||
->hiddenLabel()
|
||||
->visible(fn ($get) => $get('image_url_error') !== null)
|
||||
->afterStateHydrated(fn ($set, $get) => $get('image_url_error')),
|
||||
Image::make(fn (Get $get) => $get('image_url'), '')
|
||||
->imageSize(150)
|
||||
->visible(fn ($get) => $get('image_url') && !$get('image_url_error'))
|
||||
->alignCenter(),
|
||||
]),
|
||||
Tab::make(trans('admin/egg.import.file'))
|
||||
->schema([
|
||||
FileUpload::make('image')
|
||||
->hiddenLabel()
|
||||
->previewable()
|
||||
->openable(false)
|
||||
->downloadable(false)
|
||||
->maxSize(1024)
|
||||
->maxFiles(1)
|
||||
->columnSpanFull()
|
||||
->alignCenter()
|
||||
->imageEditor()
|
||||
->saveUploadedFileUsing(function ($file, Set $set) {
|
||||
$base64 = "data:{$file->getMimeType()};base64,". base64_encode(file_get_contents($file->getRealPath()));
|
||||
$set('base64Image', $base64);
|
||||
|
||||
return $base64;
|
||||
}),
|
||||
]),
|
||||
]),
|
||||
])
|
||||
->action(function (array $data, $record): void {
|
||||
$base64 = $data['base64Image'] ?? null;
|
||||
|
||||
if (empty($base64) && !empty($data['image'])) {
|
||||
$base64 = $data['image'];
|
||||
}
|
||||
|
||||
if (!empty($base64)) {
|
||||
$record->update([
|
||||
'image' => $base64,
|
||||
]);
|
||||
|
||||
Notification::make()
|
||||
->title(trans('admin/egg.import.image_updated'))
|
||||
->success()
|
||||
->send();
|
||||
|
||||
$record->refresh();
|
||||
} else {
|
||||
Notification::make()
|
||||
->title(trans('admin/egg.import.no_image'))
|
||||
->warning()
|
||||
->send();
|
||||
}
|
||||
}),
|
||||
Action::make('deleteImage')
|
||||
->visible(fn ($record) => $record->image)
|
||||
->label('')
|
||||
->icon('tabler-trash')
|
||||
->iconButton()
|
||||
->iconSize(IconSize::Large)
|
||||
->color('danger')
|
||||
->action(function ($record) {
|
||||
|
||||
$record->update([
|
||||
'image' => null,
|
||||
]);
|
||||
|
||||
Notification::make()
|
||||
->title(trans('admin/egg.import.image_deleted'))
|
||||
->success()
|
||||
->send();
|
||||
|
||||
$record->refresh();
|
||||
}),
|
||||
]),
|
||||
]),
|
||||
TextInput::make('name')
|
||||
->label(trans('admin/egg.name'))
|
||||
->required()
|
||||
->maxLength(255)
|
||||
->columnSpan(['default' => 2, 'sm' => 2, 'md' => 3, 'lg' => 2])
|
||||
->helperText(trans('admin/egg.name_help')),
|
||||
Textarea::make('description')
|
||||
->label(trans('admin/egg.description'))
|
||||
->rows(3)
|
||||
->columnSpan(['default' => 2, 'sm' => 2, 'md' => 4, 'lg' => 3])
|
||||
->helperText(trans('admin/egg.description_help')),
|
||||
TextInput::make('id')
|
||||
->label(trans('admin/egg.egg_id'))
|
||||
->columnSpan(1)
|
||||
->disabled(),
|
||||
TextInput::make('uuid')
|
||||
->label(trans('admin/egg.egg_uuid'))
|
||||
->disabled()
|
||||
->columnSpan(['default' => 1, 'sm' => 1, 'md' => 1, 'lg' => 2])
|
||||
->helperText(trans('admin/egg.uuid_help')),
|
||||
TextInput::make('author')
|
||||
->label(trans('admin/egg.author'))
|
||||
->required()
|
||||
->maxLength(255)
|
||||
->email()
|
||||
->disabled()
|
||||
->columnSpan(['default' => 1, 'sm' => 1, 'md' => 1, 'lg' => 2])
|
||||
->helperText(trans('admin/egg.author_help_edit')),
|
||||
Toggle::make('force_outgoing_ip')
|
||||
->inline(false)
|
||||
->label(trans('admin/egg.force_ip'))
|
||||
->columnSpan(1)
|
||||
->hintIcon('tabler-question-mark', trans('admin/egg.force_ip_help')),
|
||||
KeyValue::make('startup_commands')
|
||||
->label(trans('admin/egg.startup_commands'))
|
||||
->live()
|
||||
->columnSpanFull()
|
||||
->required()
|
||||
->addActionLabel(trans('admin/egg.add_startup'))
|
||||
->keyLabel(trans('admin/egg.startup_name'))
|
||||
->valueLabel(trans('admin/egg.startup_command'))
|
||||
->helperText(trans('admin/egg.startup_help')),
|
||||
TagsInput::make('file_denylist')
|
||||
->label(trans('admin/egg.file_denylist'))
|
||||
->placeholder('denied-file.txt')
|
||||
->helperText(trans('admin/egg.file_denylist_help'))
|
||||
->columnSpan(['default' => 2, 'sm' => 2, 'md' => 2, 'lg' => 3]),
|
||||
TextInput::make('update_url')
|
||||
->label(trans('admin/egg.update_url'))
|
||||
->url()
|
||||
->hintIcon('tabler-question-mark', trans('admin/egg.update_url_help'))
|
||||
->columnSpan(['default' => 2, 'sm' => 2, 'md' => 2, 'lg' => 3]),
|
||||
TagsInput::make('features')
|
||||
->label(trans('admin/egg.features'))
|
||||
->columnSpan(['default' => 2, 'sm' => 2, 'md' => 2, 'lg' => 3]),
|
||||
Hidden::make('script_is_privileged')
|
||||
->helperText('The docker images available to servers using this egg.'),
|
||||
TagsInput::make('tags')
|
||||
->label(trans('admin/egg.tags'))
|
||||
->columnSpan(['default' => 2, 'sm' => 2, 'md' => 2, 'lg' => 3]),
|
||||
KeyValue::make('docker_images')
|
||||
->label(trans('admin/egg.docker_images'))
|
||||
->live()
|
||||
->columnSpanFull()
|
||||
->required()
|
||||
->addActionLabel(trans('admin/egg.add_image'))
|
||||
->keyLabel(trans('admin/egg.docker_name'))
|
||||
->valueLabel(trans('admin/egg.docker_uri'))
|
||||
->helperText(trans('admin/egg.docker_help')),
|
||||
]),
|
||||
Tab::make('process_management')
|
||||
->label(trans('admin/egg.tabs.process_management'))
|
||||
->columns()
|
||||
->icon('tabler-server-cog')
|
||||
->schema([
|
||||
CopyFrom::make('copy_process_from')
|
||||
->process(),
|
||||
TextInput::make('config_stop')
|
||||
->label(trans('admin/egg.stop_command'))
|
||||
->maxLength(255)
|
||||
->helperText(trans('admin/egg.stop_command_help')),
|
||||
Textarea::make('config_startup')->rows(10)->json()
|
||||
->label(trans('admin/egg.start_config'))
|
||||
->helperText(trans('admin/egg.start_config_help')),
|
||||
Textarea::make('config_files')->rows(10)->json()
|
||||
->label(trans('admin/egg.config_files'))
|
||||
->helperText(trans('admin/egg.config_files_help')),
|
||||
Textarea::make('config_logs')->rows(10)->json()
|
||||
->label(trans('admin/egg.log_config'))
|
||||
->helperText(trans('admin/egg.log_config_help')),
|
||||
]),
|
||||
Tab::make('egg_variables')
|
||||
->label(trans('admin/egg.tabs.egg_variables'))
|
||||
->columnSpanFull()
|
||||
->icon('tabler-variable')
|
||||
->schema([
|
||||
Repeater::make('variables')
|
||||
->hiddenLabel()
|
||||
->grid()
|
||||
->relationship('variables')
|
||||
->reorderable()
|
||||
->collapsible()->collapsed()
|
||||
->orderColumn()
|
||||
->addActionLabel(trans('admin/egg.add_new_variable'))
|
||||
->itemLabel(fn (array $state) => $state['name'])
|
||||
->mutateRelationshipDataBeforeCreateUsing(function (array $data): array {
|
||||
$data['default_value'] ??= '';
|
||||
$data['description'] ??= '';
|
||||
$data['rules'] ??= [];
|
||||
$data['user_viewable'] ??= '';
|
||||
$data['user_editable'] ??= '';
|
||||
|
||||
return $data;
|
||||
})
|
||||
->mutateRelationshipDataBeforeSaveUsing(function (array $data): array {
|
||||
$data['default_value'] ??= '';
|
||||
$data['description'] ??= '';
|
||||
$data['rules'] ??= [];
|
||||
$data['user_viewable'] ??= '';
|
||||
$data['user_editable'] ??= '';
|
||||
|
||||
return $data;
|
||||
})
|
||||
->schema([
|
||||
TextInput::make('name')
|
||||
->label(trans('admin/egg.name'))
|
||||
->live()
|
||||
->debounce(750)
|
||||
->maxLength(255)
|
||||
->columnSpanFull()
|
||||
->afterStateUpdated(fn (Set $set, $state) => $set('env_variable', str($state)->trim()->snake()->upper()->toString()))
|
||||
->unique(modifyRuleUsing: fn (Unique $rule, Get $get) => $rule->where('egg_id', $get('../../id')))
|
||||
->validationMessages([
|
||||
'unique' => trans('admin/egg.error_unique'),
|
||||
])
|
||||
->required(),
|
||||
Textarea::make('description')->label(trans('admin/egg.description'))->columnSpanFull(),
|
||||
TextInput::make('env_variable')
|
||||
->label(trans('admin/egg.environment_variable'))
|
||||
->maxLength(255)
|
||||
->prefix('{{')
|
||||
->suffix('}}')
|
||||
->hintIcon('tabler-code', fn ($state) => "{{{$state}}}")
|
||||
->unique(modifyRuleUsing: fn (Unique $rule, Get $get) => $rule->where('egg_id', $get('../../id')))
|
||||
->rules(EggVariable::getRulesForField('env_variable'))
|
||||
->validationMessages([
|
||||
'unique' => trans('admin/egg.error_unique'),
|
||||
'required' => trans('admin/egg.error_required'),
|
||||
'*' => trans('admin/egg.error_reserved'),
|
||||
])
|
||||
->required(),
|
||||
TextInput::make('default_value')->label(trans('admin/egg.default_value')),
|
||||
Fieldset::make(trans('admin/egg.user_permissions'))
|
||||
->schema([
|
||||
Checkbox::make('user_viewable')->label(trans('admin/egg.viewable')),
|
||||
Checkbox::make('user_editable')->label(trans('admin/egg.editable')),
|
||||
]),
|
||||
TagsInput::make('rules')
|
||||
->label(trans('admin/egg.rules'))
|
||||
->columnSpanFull()
|
||||
->reorderable()
|
||||
->suggestions([
|
||||
'required',
|
||||
'nullable',
|
||||
'string',
|
||||
'integer',
|
||||
'numeric',
|
||||
'boolean',
|
||||
'alpha',
|
||||
'alpha_dash',
|
||||
'alpha_num',
|
||||
'url',
|
||||
'email',
|
||||
'regex:',
|
||||
'min:',
|
||||
'max:',
|
||||
'between:',
|
||||
'between:1024,65535',
|
||||
'in:',
|
||||
'in:true,false',
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
Tab::make('install_script')
|
||||
->label(trans('admin/egg.tabs.install_script'))
|
||||
->columns(3)
|
||||
->icon('tabler-file-download')
|
||||
->schema([
|
||||
CopyFrom::make('copy_script_from')
|
||||
->script(),
|
||||
TextInput::make('script_container')
|
||||
->label(trans('admin/egg.script_container'))
|
||||
->required()
|
||||
->maxLength(255)
|
||||
->placeholder('ghcr.io/pelican-eggs/installers:debian'),
|
||||
Select::make('script_entry')
|
||||
->label(trans('admin/egg.script_entry'))
|
||||
->selectablePlaceholder(false)
|
||||
->options([
|
||||
'bash' => 'bash',
|
||||
'ash' => 'ash',
|
||||
'/bin/bash' => '/bin/bash',
|
||||
])
|
||||
->required(),
|
||||
CodeEditor::make('script_install')
|
||||
->hiddenLabel()
|
||||
->columnSpanFull(),
|
||||
]),
|
||||
])->columnSpanFull()->persistTabInQueryString(),
|
||||
]);
|
||||
}
|
||||
|
||||
/** @return array<Action|ActionGroup> */
|
||||
protected function getDefaultHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
DeleteAction::make()
|
||||
->disabled(fn (Egg $egg): bool => $egg->servers()->count() > 0)
|
||||
->label(fn (Egg $egg): string => $egg->servers()->count() <= 0 ? trans('filament-actions::delete.single.label') : trans('admin/egg.in_use')),
|
||||
ExportEggAction::make(),
|
||||
ImportEggAction::make()
|
||||
->multiple(false),
|
||||
$this->getSaveFormAction()->formId('form'),
|
||||
];
|
||||
}
|
||||
|
||||
public function refreshForm(): void
|
||||
{
|
||||
$this->fillForm();
|
||||
}
|
||||
|
||||
protected function getFormActions(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -1,28 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources\EggResource\Pages;
|
||||
namespace App\Filament\Admin\Resources\Eggs\Pages;
|
||||
|
||||
use App\Filament\Admin\Resources\EggResource;
|
||||
use App\Filament\Components\Actions\ImportEggAction as ImportEggHeaderAction;
|
||||
use App\Filament\Components\Tables\Actions\ExportEggAction;
|
||||
use App\Filament\Components\Tables\Actions\ImportEggAction;
|
||||
use App\Filament\Components\Tables\Actions\UpdateEggAction;
|
||||
use App\Filament\Components\Tables\Actions\UpdateEggBulkAction;
|
||||
use App\Filament\Admin\Resources\Eggs\EggResource;
|
||||
use App\Filament\Components\Actions\ExportEggAction;
|
||||
use App\Filament\Components\Actions\ImportEggAction;
|
||||
use App\Filament\Components\Actions\UpdateEggAction;
|
||||
use App\Filament\Components\Actions\UpdateEggBulkAction;
|
||||
use App\Filament\Components\Tables\Filters\TagsFilter;
|
||||
use App\Models\Egg;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use Exception;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\ActionGroup;
|
||||
use Filament\Actions\CreateAction as CreateHeaderAction;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Actions\ReplicateAction;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
use Filament\Tables\Actions\CreateAction;
|
||||
use Filament\Tables\Actions\DeleteBulkAction;
|
||||
use Filament\Tables\Actions\EditAction;
|
||||
use Filament\Tables\Actions\ReplicateAction;
|
||||
use Filament\Tables\Columns\ImageColumn;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class ListEggs extends ListRecords
|
||||
@@ -32,6 +31,9 @@ class ListEggs extends ListRecords
|
||||
|
||||
protected static string $resource = EggResource::class;
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
@@ -41,19 +43,24 @@ class ListEggs extends ListRecords
|
||||
TextColumn::make('id')
|
||||
->label('Id')
|
||||
->hidden(),
|
||||
ImageColumn::make('image')
|
||||
->label('')
|
||||
->alignCenter()
|
||||
->circular()
|
||||
->getStateUsing(fn ($record) => $record->image
|
||||
? $record->image
|
||||
: 'data:image/svg+xml;base64,' . base64_encode(file_get_contents(public_path('pelican.svg')))),
|
||||
TextColumn::make('name')
|
||||
->label(trans('admin/egg.name'))
|
||||
->icon('tabler-egg')
|
||||
->description(fn ($record): ?string => (strlen($record->description) > 120) ? substr($record->description, 0, 120).'...' : $record->description)
|
||||
->wrap()
|
||||
->searchable()
|
||||
->sortable(),
|
||||
TextColumn::make('servers_count')
|
||||
->counts('servers')
|
||||
->icon('tabler-server')
|
||||
->label(trans('admin/egg.servers')),
|
||||
])
|
||||
->actions([
|
||||
->recordActions([
|
||||
EditAction::make()
|
||||
->iconButton()
|
||||
->tooltip(trans('filament-actions::edit.single.label')),
|
||||
@@ -62,14 +69,14 @@ class ListEggs extends ListRecords
|
||||
->tooltip(trans('filament-actions::export.modal.actions.export.label')),
|
||||
UpdateEggAction::make()
|
||||
->iconButton()
|
||||
->tooltip(trans('admin/egg.update')),
|
||||
->tooltip(trans_choice('admin/egg.update', 1)),
|
||||
ReplicateAction::make()
|
||||
->iconButton()
|
||||
->tooltip(trans('filament-actions::replicate.single.label'))
|
||||
->modal(false)
|
||||
->excludeAttributes(['author', 'uuid', 'update_url', 'servers_count', 'created_at', 'updated_at'])
|
||||
->beforeReplicaSaved(function (Egg $replica) {
|
||||
$replica->author = auth()->user()->email;
|
||||
$replica->author = user()?->email;
|
||||
$replica->name .= ' Copy';
|
||||
$replica->uuid = Str::uuid()->toString();
|
||||
})
|
||||
@@ -78,15 +85,15 @@ class ListEggs extends ListRecords
|
||||
])
|
||||
->groupedBulkActions([
|
||||
DeleteBulkAction::make()
|
||||
->before(fn (DeleteBulkAction $action, Collection $records) => $action->records($records->filter(function ($egg) {
|
||||
->before(fn (&$records) => $records = $records->filter(function ($egg) {
|
||||
/** @var Egg $egg */
|
||||
return $egg->servers_count <= 0;
|
||||
}))),
|
||||
})),
|
||||
UpdateEggBulkAction::make()
|
||||
->before(fn (UpdateEggBulkAction $action, Collection $records) => $action->records($records->filter(function ($egg) {
|
||||
->before(fn (&$records) => $records = $records->filter(function ($egg) {
|
||||
/** @var Egg $egg */
|
||||
return cache()->get("eggs.$egg->uuid.update", false);
|
||||
}))),
|
||||
})),
|
||||
])
|
||||
->emptyStateIcon('tabler-eggs')
|
||||
->emptyStateDescription('')
|
||||
@@ -102,13 +109,15 @@ class ListEggs extends ListRecords
|
||||
]);
|
||||
}
|
||||
|
||||
/** @return array<Action|ActionGroup> */
|
||||
/** @return array<Action|ActionGroup>
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function getDefaultHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
ImportEggHeaderAction::make()
|
||||
ImportEggAction::make()
|
||||
->multiple(),
|
||||
CreateHeaderAction::make(),
|
||||
CreateAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources\EggResource\RelationManagers;
|
||||
namespace App\Filament\Admin\Resources\Eggs\RelationManagers;
|
||||
|
||||
use App\Models\Server;
|
||||
use Filament\Resources\RelationManagers\RelationManager;
|
||||
@@ -22,17 +22,14 @@ class ServersRelationManager extends RelationManager
|
||||
->heading(trans('admin/egg.servers'))
|
||||
->columns([
|
||||
TextColumn::make('user.username')
|
||||
->label('Owner')
|
||||
->icon('tabler-user')
|
||||
->label(trans('admin/server.owner'))
|
||||
->url(fn (Server $server): string => route('filament.admin.resources.users.edit', ['record' => $server->user]))
|
||||
->sortable(),
|
||||
TextColumn::make('name')
|
||||
->label(trans('admin/server.name'))
|
||||
->icon('tabler-brand-docker')
|
||||
->url(fn (Server $server): string => route('filament.admin.resources.servers.edit', ['record' => $server]))
|
||||
->sortable(),
|
||||
TextColumn::make('node.name')
|
||||
->icon('tabler-server-2')
|
||||
->url(fn (Server $server): string => route('filament.admin.resources.nodes.edit', ['record' => $server->node])),
|
||||
TextColumn::make('image')
|
||||
->label(trans('admin/server.docker_image')),
|
||||
@@ -40,7 +37,7 @@ class ServersRelationManager extends RelationManager
|
||||
->label(trans('admin/server.primary_allocation'))
|
||||
->disabled()
|
||||
->options(fn (Server $server) => $server->allocations->take(1)->mapWithKeys(fn ($allocation) => [$allocation->id => $allocation->address]))
|
||||
->placeholder('None')
|
||||
->placeholder(trans('admin/server.none'))
|
||||
->sortable(),
|
||||
]);
|
||||
}
|
||||
@@ -1,26 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources;
|
||||
namespace App\Filament\Admin\Resources\Mounts;
|
||||
|
||||
use App\Filament\Admin\Resources\MountResource\Pages;
|
||||
use App\Filament\Admin\Resources\Mounts\Pages\CreateMount;
|
||||
use App\Filament\Admin\Resources\Mounts\Pages\EditMount;
|
||||
use App\Filament\Admin\Resources\Mounts\Pages\ListMounts;
|
||||
use App\Filament\Admin\Resources\Mounts\Pages\ViewMount;
|
||||
use App\Models\Mount;
|
||||
use App\Traits\Filament\CanCustomizePages;
|
||||
use App\Traits\Filament\CanCustomizeRelations;
|
||||
use App\Traits\Filament\CanModifyForm;
|
||||
use App\Traits\Filament\CanModifyTable;
|
||||
use Filament\Forms\Components\Group;
|
||||
use Filament\Forms\Components\Section;
|
||||
use Exception;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Actions\ViewAction;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\ToggleButtons;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Resources\Pages\PageRegistration;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Tables\Actions\CreateAction;
|
||||
use Filament\Tables\Actions\DeleteBulkAction;
|
||||
use Filament\Tables\Actions\EditAction;
|
||||
use Filament\Tables\Actions\ViewAction;
|
||||
use Filament\Schemas\Components\Group;
|
||||
use Filament\Schemas\Components\Section;
|
||||
use Filament\Schemas\Components\StateCasts\BooleanStateCast;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
@@ -34,7 +39,7 @@ class MountResource extends Resource
|
||||
|
||||
protected static ?string $model = Mount::class;
|
||||
|
||||
protected static ?string $navigationIcon = 'tabler-layers-linked';
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'tabler-layers-linked';
|
||||
|
||||
protected static ?string $recordTitleAttribute = 'name';
|
||||
|
||||
@@ -63,6 +68,9 @@ class MountResource extends Resource
|
||||
return trans('admin/dashboard.advanced');
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function defaultTable(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
@@ -72,12 +80,10 @@ class MountResource extends Resource
|
||||
->description(fn (Mount $mount) => "$mount->source -> $mount->target")
|
||||
->sortable(),
|
||||
TextColumn::make('eggs.name')
|
||||
->icon('tabler-eggs')
|
||||
->label(trans('admin/mount.eggs'))
|
||||
->badge()
|
||||
->placeholder(trans('admin/mount.table.all_eggs')),
|
||||
TextColumn::make('nodes.name')
|
||||
->icon('tabler-server-2')
|
||||
->label(trans('admin/mount.nodes'))
|
||||
->badge()
|
||||
->placeholder(trans('admin/mount.table.all_nodes')),
|
||||
@@ -88,7 +94,7 @@ class MountResource extends Resource
|
||||
->color(fn ($state) => $state ? 'success' : 'warning')
|
||||
->formatStateUsing(fn ($state) => $state ? trans('admin/mount.toggles.read_only') : trans('admin/mount.toggles.writable')),
|
||||
])
|
||||
->actions([
|
||||
->recordActions([
|
||||
ViewAction::make()
|
||||
->hidden(fn ($record) => static::canEdit($record)),
|
||||
EditAction::make(),
|
||||
@@ -104,10 +110,13 @@ class MountResource extends Resource
|
||||
]);
|
||||
}
|
||||
|
||||
public static function defaultForm(Form $form): Form
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function defaultForm(Schema $schema): Schema
|
||||
{
|
||||
return $form
|
||||
->schema([
|
||||
return $schema
|
||||
->components([
|
||||
Section::make()->schema([
|
||||
TextInput::make('name')
|
||||
->label(trans('admin/mount.name'))
|
||||
@@ -117,6 +126,7 @@ class MountResource extends Resource
|
||||
ToggleButtons::make('read_only')
|
||||
->label(trans('admin/mount.read_only'))
|
||||
->helperText(trans('admin/mount.read_only_help'))
|
||||
->stateCast(new BooleanStateCast(false, true))
|
||||
->options([
|
||||
false => trans('admin/mount.toggles.writable'),
|
||||
true => trans('admin/mount.toggles.read_only'),
|
||||
@@ -130,8 +140,7 @@ class MountResource extends Resource
|
||||
true => 'success',
|
||||
])
|
||||
->inline()
|
||||
->default(false)
|
||||
->required(),
|
||||
->default(false),
|
||||
TextInput::make('source')
|
||||
->label(trans('admin/mount.source'))
|
||||
->required()
|
||||
@@ -154,11 +163,12 @@ class MountResource extends Resource
|
||||
Section::make()->schema([
|
||||
Select::make('eggs')->multiple()
|
||||
->label(trans('admin/mount.eggs'))
|
||||
->relationship('eggs', 'name')
|
||||
// Selecting only non-json fields to prevent Postgres from choking on DISTINCT JSON columns
|
||||
->relationship('eggs', 'name', fn (Builder $query) => $query->select(['eggs.id', 'eggs.name']))
|
||||
->preload(),
|
||||
Select::make('nodes')->multiple()
|
||||
->label(trans('admin/mount.nodes'))
|
||||
->relationship('nodes', 'name', fn (Builder $query) => $query->whereIn('nodes.id', auth()->user()->accessibleNodes()->pluck('id')))
|
||||
->relationship('nodes', 'name', fn (Builder $query) => $query->whereIn('nodes.id', user()?->accessibleNodes()->pluck('id')))
|
||||
->searchable(['name', 'fqdn'])
|
||||
->preload(),
|
||||
]),
|
||||
@@ -176,10 +186,10 @@ class MountResource extends Resource
|
||||
public static function getDefaultPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListMounts::route('/'),
|
||||
'create' => Pages\CreateMount::route('/create'),
|
||||
'view' => Pages\ViewMount::route('/{record}'),
|
||||
'edit' => Pages\EditMount::route('/{record}/edit'),
|
||||
'index' => ListMounts::route('/'),
|
||||
'create' => CreateMount::route('/create'),
|
||||
'view' => ViewMount::route('/{record}'),
|
||||
'edit' => EditMount::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -189,7 +199,7 @@ class MountResource extends Resource
|
||||
|
||||
return $query->where(function (Builder $query) {
|
||||
return $query->whereHas('nodes', function (Builder $query) {
|
||||
$query->whereIn('nodes.id', auth()->user()->accessibleNodes()->pluck('id'));
|
||||
$query->whereIn('nodes.id', user()?->accessibleNodes()->pluck('id'));
|
||||
})->orDoesntHave('nodes');
|
||||
});
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources\MountResource\Pages;
|
||||
namespace App\Filament\Admin\Resources\Mounts\Pages;
|
||||
|
||||
use App\Filament\Admin\Resources\MountResource;
|
||||
use App\Filament\Admin\Resources\Mounts\MountResource;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use Filament\Actions\Action;
|
||||
@@ -1,8 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources\MountResource\Pages;
|
||||
namespace App\Filament\Admin\Resources\Mounts\Pages;
|
||||
|
||||
use App\Filament\Admin\Resources\MountResource;
|
||||
use App\Filament\Admin\Resources\Mounts\MountResource;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use Filament\Actions\Action;
|
||||
@@ -1,8 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources\MountResource\Pages;
|
||||
namespace App\Filament\Admin\Resources\Mounts\Pages;
|
||||
|
||||
use App\Filament\Admin\Resources\MountResource;
|
||||
use App\Filament\Admin\Resources\Mounts\MountResource;
|
||||
use App\Models\Mount;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
@@ -1,8 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources\MountResource\Pages;
|
||||
namespace App\Filament\Admin\Resources\Mounts\Pages;
|
||||
|
||||
use App\Filament\Admin\Resources\MountResource;
|
||||
use App\Filament\Admin\Resources\Mounts\MountResource;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use Filament\Actions\Action;
|
||||
@@ -1,9 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources;
|
||||
namespace App\Filament\Admin\Resources\Nodes;
|
||||
|
||||
use App\Filament\Admin\Resources\NodeResource\Pages;
|
||||
use App\Filament\Admin\Resources\NodeResource\RelationManagers;
|
||||
use App\Enums\CustomizationKey;
|
||||
use App\Filament\Admin\Resources\Nodes\Pages\CreateNode;
|
||||
use App\Filament\Admin\Resources\Nodes\Pages\EditNode;
|
||||
use App\Filament\Admin\Resources\Nodes\Pages\ListNodes;
|
||||
use App\Filament\Admin\Resources\Nodes\RelationManagers\AllocationsRelationManager;
|
||||
use App\Filament\Admin\Resources\Nodes\RelationManagers\NodesRelationManager;
|
||||
use App\Models\Node;
|
||||
use App\Traits\Filament\CanCustomizePages;
|
||||
use App\Traits\Filament\CanCustomizeRelations;
|
||||
@@ -19,7 +23,7 @@ class NodeResource extends Resource
|
||||
|
||||
protected static ?string $model = Node::class;
|
||||
|
||||
protected static ?string $navigationIcon = 'tabler-server-2';
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'tabler-server-2';
|
||||
|
||||
protected static ?string $recordTitleAttribute = 'name';
|
||||
|
||||
@@ -40,7 +44,7 @@ class NodeResource extends Resource
|
||||
|
||||
public static function getNavigationGroup(): ?string
|
||||
{
|
||||
return config('panel.filament.top-navigation', false) ? null : trans('admin/dashboard.server');
|
||||
return user()?->getCustomization(CustomizationKey::TopNavigation) ? false : trans('admin/dashboard.server');
|
||||
}
|
||||
|
||||
public static function getNavigationBadge(): ?string
|
||||
@@ -52,8 +56,8 @@ class NodeResource extends Resource
|
||||
public static function getDefaultRelations(): array
|
||||
{
|
||||
return [
|
||||
RelationManagers\AllocationsRelationManager::class,
|
||||
RelationManagers\NodesRelationManager::class,
|
||||
AllocationsRelationManager::class,
|
||||
NodesRelationManager::class,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -61,9 +65,9 @@ class NodeResource extends Resource
|
||||
public static function getDefaultPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListNodes::route('/'),
|
||||
'create' => Pages\CreateNode::route('/create'),
|
||||
'edit' => Pages\EditNode::route('/{record}/edit'),
|
||||
'index' => ListNodes::route('/'),
|
||||
'create' => CreateNode::route('/create'),
|
||||
'edit' => EditNode::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -71,6 +75,6 @@ class NodeResource extends Resource
|
||||
{
|
||||
$query = parent::getEloquentQuery();
|
||||
|
||||
return $query->whereIn('id', auth()->user()->accessibleNodes()->pluck('id'));
|
||||
return $query->whereIn('id', user()?->accessibleNodes()->pluck('id'));
|
||||
}
|
||||
}
|
||||
@@ -1,23 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources\NodeResource\Pages;
|
||||
namespace App\Filament\Admin\Resources\Nodes\Pages;
|
||||
|
||||
use App\Filament\Admin\Resources\NodeResource;
|
||||
use App\Filament\Admin\Resources\Nodes\NodeResource;
|
||||
use App\Models\Node;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use Filament\Forms;
|
||||
use Filament\Forms\Components\Actions\Action;
|
||||
use Filament\Forms\Components\Grid;
|
||||
use Exception;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Forms\Components\Hidden;
|
||||
use Filament\Forms\Components\TagsInput;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\ToggleButtons;
|
||||
use Filament\Forms\Components\Wizard;
|
||||
use Filament\Forms\Components\Wizard\Step;
|
||||
use Filament\Forms\Get;
|
||||
use Filament\Forms\Set;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
use Filament\Schemas\Components\Grid;
|
||||
use Filament\Schemas\Components\Utilities\Get;
|
||||
use Filament\Schemas\Components\Utilities\Set;
|
||||
use Filament\Schemas\Components\Wizard;
|
||||
use Filament\Schemas\Components\Wizard\Step;
|
||||
use Filament\Schemas\Schema;
|
||||
use Illuminate\Support\Facades\Blade;
|
||||
use Illuminate\Support\HtmlString;
|
||||
|
||||
@@ -30,10 +31,13 @@ class CreateNode extends CreateRecord
|
||||
|
||||
protected static bool $canCreateAnother = false;
|
||||
|
||||
public function form(Forms\Form $form): Forms\Form
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function form(Schema $schema): Schema
|
||||
{
|
||||
return $form
|
||||
->schema([
|
||||
return $schema
|
||||
->components([
|
||||
Wizard::make([
|
||||
Step::make('basic')
|
||||
->label(trans('admin/node.tabs.basic_settings'))
|
||||
@@ -222,8 +226,7 @@ class CreateNode extends CreateRecord
|
||||
->label(trans('admin/node.maintenance_mode'))->inline()
|
||||
->columnSpan(1)
|
||||
->default(false)
|
||||
->hinticon('tabler-question-mark')
|
||||
->hintIconTooltip(trans('admin/node.maintenance_mode_help'))
|
||||
->hintIcon('tabler-question-mark', trans('admin/node.maintenance_mode_help'))
|
||||
->options([
|
||||
true => trans('admin/node.enabled'),
|
||||
false => trans('admin/node.disabled'),
|
||||
@@ -250,8 +253,7 @@ class CreateNode extends CreateRecord
|
||||
TextInput::make('upload_size')
|
||||
->label(trans('admin/node.upload_limit'))
|
||||
->helperText(trans('admin/node.upload_limit_help.0'))
|
||||
->hintIcon('tabler-question-mark')
|
||||
->hintIconTooltip(trans('admin/node.upload_limit_help.1'))
|
||||
->hintIcon('tabler-question-mark', trans('admin/node.upload_limit_help.1'))
|
||||
->columnSpan(1)
|
||||
->numeric()->required()
|
||||
->default(256)
|
||||
@@ -404,7 +406,7 @@ class CreateNode extends CreateRecord
|
||||
type="submit"
|
||||
size="sm"
|
||||
>
|
||||
Create Node
|
||||
{{ trans('admin/node.create') }}
|
||||
</x-filament::button>
|
||||
BLADE))),
|
||||
]);
|
||||
@@ -413,7 +415,7 @@ class CreateNode extends CreateRecord
|
||||
protected function getRedirectUrlParameters(): array
|
||||
{
|
||||
return [
|
||||
'tab' => '-configuration-file-tab',
|
||||
'tab' => 'configuration-file::data::tab',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -1,38 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources\NodeResource\Pages;
|
||||
namespace App\Filament\Admin\Resources\Nodes\Pages;
|
||||
|
||||
use App\Filament\Admin\Resources\NodeResource;
|
||||
use App\Filament\Admin\Resources\Nodes\NodeResource;
|
||||
use App\Models\Node;
|
||||
use App\Repositories\Daemon\DaemonConfigurationRepository;
|
||||
use App\Repositories\Daemon\DaemonSystemRepository;
|
||||
use App\Services\Helpers\SoftwareVersionService;
|
||||
use App\Services\Nodes\NodeAutoDeployService;
|
||||
use App\Services\Nodes\NodeUpdateService;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use Exception;
|
||||
use Filament\Actions;
|
||||
use Filament\Forms;
|
||||
use Filament\Forms\Components\Actions as FormActions;
|
||||
use Filament\Forms\Components\Fieldset;
|
||||
use Filament\Forms\Components\Grid;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Forms\Components\Hidden;
|
||||
use Filament\Forms\Components\Placeholder;
|
||||
use Filament\Forms\Components\Tabs;
|
||||
use Filament\Forms\Components\Tabs\Tab;
|
||||
use Filament\Forms\Components\Slider;
|
||||
use Filament\Forms\Components\Slider\Enums\PipsMode;
|
||||
use Filament\Forms\Components\TagsInput;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\ToggleButtons;
|
||||
use Filament\Forms\Components\View;
|
||||
use Filament\Forms\Get;
|
||||
use Filament\Forms\Set;
|
||||
use Filament\Infolists\Components\CodeEntry;
|
||||
use Filament\Infolists\Components\TextEntry;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
use Filament\Schemas\Components\Actions;
|
||||
use Filament\Schemas\Components\Fieldset;
|
||||
use Filament\Schemas\Components\Grid;
|
||||
use Filament\Schemas\Components\Section;
|
||||
use Filament\Schemas\Components\StateCasts\BooleanStateCast;
|
||||
use Filament\Schemas\Components\Tabs;
|
||||
use Filament\Schemas\Components\Tabs\Tab;
|
||||
use Filament\Schemas\Components\Utilities\Get;
|
||||
use Filament\Schemas\Components\Utilities\Set;
|
||||
use Filament\Schemas\Components\View;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Support\Enums\Alignment;
|
||||
use Filament\Support\Enums\IconSize;
|
||||
use Filament\Support\RawJs;
|
||||
use Illuminate\Http\Client\ConnectionException;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\HtmlString;
|
||||
use Webbingbrasil\FilamentCopyActions\Forms\Actions\CopyAction;
|
||||
use Phiki\Grammar\Grammar;
|
||||
use Throwable;
|
||||
|
||||
class EditNode extends EditRecord
|
||||
{
|
||||
@@ -41,19 +51,22 @@ class EditNode extends EditRecord
|
||||
|
||||
protected static string $resource = NodeResource::class;
|
||||
|
||||
private DaemonConfigurationRepository $daemonConfigurationRepository;
|
||||
private DaemonSystemRepository $daemonSystemRepository;
|
||||
|
||||
private NodeUpdateService $nodeUpdateService;
|
||||
|
||||
public function boot(DaemonConfigurationRepository $daemonConfigurationRepository, NodeUpdateService $nodeUpdateService): void
|
||||
public function boot(DaemonSystemRepository $daemonSystemRepository, NodeUpdateService $nodeUpdateService): void
|
||||
{
|
||||
$this->daemonConfigurationRepository = $daemonConfigurationRepository;
|
||||
$this->daemonSystemRepository = $daemonSystemRepository;
|
||||
$this->nodeUpdateService = $nodeUpdateService;
|
||||
}
|
||||
|
||||
public function form(Forms\Form $form): Forms\Form
|
||||
/**
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function form(Schema $schema): Schema
|
||||
{
|
||||
return $form->schema([
|
||||
return $schema->components([
|
||||
Tabs::make('Tabs')
|
||||
->columns([
|
||||
'default' => 2,
|
||||
@@ -64,7 +77,7 @@ class EditNode extends EditRecord
|
||||
->persistTabInQueryString()
|
||||
->columnSpanFull()
|
||||
->tabs([
|
||||
Tab::make('')
|
||||
Tab::make('overview')
|
||||
->label(trans('admin/node.tabs.overview'))
|
||||
->icon('tabler-chart-area-line-filled')
|
||||
->columns([
|
||||
@@ -77,19 +90,20 @@ class EditNode extends EditRecord
|
||||
Fieldset::make()
|
||||
->label(trans('admin/node.node_info'))
|
||||
->columns(4)
|
||||
->columnSpanFull()
|
||||
->schema([
|
||||
Placeholder::make('')
|
||||
TextEntry::make('wings_version')
|
||||
->label(trans('admin/node.wings_version'))
|
||||
->content(fn (Node $node, SoftwareVersionService $versionService) => ($node->systemInformation()['version'] ?? trans('admin/node.unknown')) . ' (' . trans('admin/node.latest') . ': ' . $versionService->latestWingsVersion() . ')'),
|
||||
Placeholder::make('')
|
||||
->state(fn (Node $node, SoftwareVersionService $versionService) => ($node->systemInformation()['version'] ?? trans('admin/node.unknown')) . ' ' . trans('admin/node.latest', ['version' => $versionService->latestWingsVersion()])),
|
||||
TextEntry::make('cpu_threads')
|
||||
->label(trans('admin/node.cpu_threads'))
|
||||
->content(fn (Node $node) => $node->systemInformation()['cpu_count'] ?? 0),
|
||||
Placeholder::make('')
|
||||
->state(fn (Node $node) => $node->systemInformation()['cpu_count'] ?? 0),
|
||||
TextEntry::make('architecture')
|
||||
->label(trans('admin/node.architecture'))
|
||||
->content(fn (Node $node) => $node->systemInformation()['architecture'] ?? trans('admin/node.unknown')),
|
||||
Placeholder::make('')
|
||||
->state(fn (Node $node) => $node->systemInformation()['architecture'] ?? trans('admin/node.unknown')),
|
||||
TextEntry::make('kernel')
|
||||
->label(trans('admin/node.kernel'))
|
||||
->content(fn (Node $node) => $node->systemInformation()['kernel_version'] ?? trans('admin/node.unknown')),
|
||||
->state(fn (Node $node) => $node->systemInformation()['kernel_version'] ?? trans('admin/node.unknown')),
|
||||
]),
|
||||
View::make('filament.components.node-cpu-chart')
|
||||
->columnSpan([
|
||||
@@ -108,7 +122,8 @@ class EditNode extends EditRecord
|
||||
View::make('filament.components.node-storage-chart')
|
||||
->columnSpanFull(),
|
||||
]),
|
||||
Tab::make(trans('admin/node.tabs.basic_settings'))
|
||||
Tab::make('basic_settings')
|
||||
->label(trans('admin/node.tabs.basic_settings'))
|
||||
->icon('tabler-server')
|
||||
->schema([
|
||||
TextInput::make('fqdn')
|
||||
@@ -175,13 +190,14 @@ class EditNode extends EditRecord
|
||||
->default(null)
|
||||
->hint(fn (Get $get) => $get('ip'))
|
||||
->hintColor('success')
|
||||
->stateCast(new BooleanStateCast(false, true))
|
||||
->options([
|
||||
true => trans('admin/node.valid'),
|
||||
false => trans('admin/node.invalid'),
|
||||
1 => trans('admin/node.valid'),
|
||||
0 => trans('admin/node.invalid'),
|
||||
])
|
||||
->colors([
|
||||
true => 'success',
|
||||
false => 'danger',
|
||||
1 => 'success',
|
||||
0 => 'danger',
|
||||
])
|
||||
->columnSpan(1),
|
||||
TextInput::make('daemon_connect')
|
||||
@@ -257,7 +273,7 @@ class EditNode extends EditRecord
|
||||
->integer()
|
||||
->visible(fn (Get $get) => $get('connection') === 'https_proxy'),
|
||||
]),
|
||||
Tab::make('adv')
|
||||
Tab::make('advanced_settings')
|
||||
->label(trans('admin/node.tabs.advanced_settings'))
|
||||
->columns([
|
||||
'default' => 1,
|
||||
@@ -284,7 +300,7 @@ class EditNode extends EditRecord
|
||||
'lg' => 2,
|
||||
])
|
||||
->label(trans('admin/node.node_uuid'))
|
||||
->hintAction(fn () => request()->isSecure() ? CopyAction::make() : null)
|
||||
->hintCopy()
|
||||
->disabled(),
|
||||
TagsInput::make('tags')
|
||||
->label(trans('admin/node.tags'))
|
||||
@@ -303,8 +319,7 @@ class EditNode extends EditRecord
|
||||
'lg' => 1,
|
||||
])
|
||||
->label(trans('admin/node.upload_limit'))
|
||||
->hintIcon('tabler-question-mark')
|
||||
->hintIconTooltip(trans('admin/node.upload_limit_help.0') . trans('admin/node.upload_limit_help.1'))
|
||||
->hintIcon('tabler-question-mark', trans('admin/node.upload_limit_help.0') . trans('admin/node.upload_limit_help.1'))
|
||||
->numeric()->required()
|
||||
->minValue(1)
|
||||
->maxValue(1024)
|
||||
@@ -338,14 +353,16 @@ class EditNode extends EditRecord
|
||||
'md' => 1,
|
||||
'lg' => 3,
|
||||
])
|
||||
->label(trans('admin/node.use_for_deploy'))->inline()
|
||||
->label(trans('admin/node.use_for_deploy'))
|
||||
->inline()
|
||||
->stateCast(new BooleanStateCast(false, true))
|
||||
->options([
|
||||
true => trans('admin/node.yes'),
|
||||
false => trans('admin/node.no'),
|
||||
1 => trans('admin/node.yes'),
|
||||
0 => trans('admin/node.no'),
|
||||
])
|
||||
->colors([
|
||||
true => 'success',
|
||||
false => 'danger',
|
||||
1 => 'success',
|
||||
0 => 'danger',
|
||||
]),
|
||||
ToggleButtons::make('maintenance_mode')
|
||||
->columnSpan([
|
||||
@@ -354,16 +371,17 @@ class EditNode extends EditRecord
|
||||
'md' => 1,
|
||||
'lg' => 3,
|
||||
])
|
||||
->label(trans('admin/node.maintenance_mode'))->inline()
|
||||
->hinticon('tabler-question-mark')
|
||||
->hintIconTooltip(trans('admin/node.maintenance_mode_help'))
|
||||
->label(trans('admin/node.maintenance_mode'))
|
||||
->inline()
|
||||
->hintIcon('tabler-question-mark', trans('admin/node.maintenance_mode_help'))
|
||||
->stateCast(new BooleanStateCast(false, true))
|
||||
->options([
|
||||
true => trans('admin/node.enabled'),
|
||||
false => trans('admin/node.disabled'),
|
||||
1 => trans('admin/node.enabled'),
|
||||
0 => trans('admin/node.disabled'),
|
||||
])
|
||||
->colors([
|
||||
false => 'success',
|
||||
true => 'danger',
|
||||
1 => 'danger',
|
||||
0 => 'success',
|
||||
]),
|
||||
Grid::make()
|
||||
->columns([
|
||||
@@ -381,13 +399,14 @@ class EditNode extends EditRecord
|
||||
->afterStateUpdated(fn (Set $set) => $set('memory_overallocate', 0))
|
||||
->formatStateUsing(fn (Get $get) => $get('memory') == 0)
|
||||
->live()
|
||||
->stateCast(new BooleanStateCast(false, true))
|
||||
->options([
|
||||
true => trans('admin/node.unlimited'),
|
||||
false => trans('admin/node.limited'),
|
||||
1 => trans('admin/node.unlimited'),
|
||||
0 => trans('admin/node.limited'),
|
||||
])
|
||||
->colors([
|
||||
true => 'primary',
|
||||
false => 'warning',
|
||||
1 => 'primary',
|
||||
0 => 'warning',
|
||||
])
|
||||
->columnSpan([
|
||||
'default' => 1,
|
||||
@@ -426,6 +445,7 @@ class EditNode extends EditRecord
|
||||
->suffix('%'),
|
||||
]),
|
||||
Grid::make()
|
||||
->columnSpanFull()
|
||||
->columns([
|
||||
'default' => 1,
|
||||
'sm' => 1,
|
||||
@@ -440,13 +460,14 @@ class EditNode extends EditRecord
|
||||
->afterStateUpdated(fn (Set $set) => $set('disk', 0))
|
||||
->afterStateUpdated(fn (Set $set) => $set('disk_overallocate', 0))
|
||||
->formatStateUsing(fn (Get $get) => $get('disk') == 0)
|
||||
->stateCast(new BooleanStateCast(false, true))
|
||||
->options([
|
||||
true => trans('admin/node.unlimited'),
|
||||
false => trans('admin/node.limited'),
|
||||
1 => trans('admin/node.unlimited'),
|
||||
0 => trans('admin/node.limited'),
|
||||
])
|
||||
->colors([
|
||||
true => 'primary',
|
||||
false => 'warning',
|
||||
1 => 'primary',
|
||||
0 => 'warning',
|
||||
])
|
||||
->columnSpan([
|
||||
'default' => 1,
|
||||
@@ -495,13 +516,14 @@ class EditNode extends EditRecord
|
||||
->afterStateUpdated(fn (Set $set) => $set('cpu', 0))
|
||||
->afterStateUpdated(fn (Set $set) => $set('cpu_overallocate', 0))
|
||||
->formatStateUsing(fn (Get $get) => $get('cpu') == 0)
|
||||
->stateCast(new BooleanStateCast(false, true))
|
||||
->options([
|
||||
true => trans('admin/node.unlimited'),
|
||||
false => trans('admin/node.limited'),
|
||||
1 => trans('admin/node.unlimited'),
|
||||
0 => trans('admin/node.limited'),
|
||||
])
|
||||
->colors([
|
||||
true => 'primary',
|
||||
false => 'warning',
|
||||
1 => 'primary',
|
||||
0 => 'warning',
|
||||
])
|
||||
->columnSpan(2),
|
||||
TextInput::make('cpu')
|
||||
@@ -525,25 +547,27 @@ class EditNode extends EditRecord
|
||||
->suffix('%'),
|
||||
]),
|
||||
]),
|
||||
Tab::make('Config')
|
||||
Tab::make('config_file')
|
||||
->label(trans('admin/node.tabs.config_file'))
|
||||
->icon('tabler-code')
|
||||
->schema([
|
||||
Placeholder::make('instructions')
|
||||
TextEntry::make('instructions')
|
||||
->label(trans('admin/node.instructions'))
|
||||
->columnSpanFull()
|
||||
->content(new HtmlString(trans('admin/node.instructions_help'))),
|
||||
Textarea::make('config')
|
||||
->state(new HtmlString(trans('admin/node.instructions_help'))),
|
||||
CodeEntry::make('config')
|
||||
->label('/etc/pelican/config.yml')
|
||||
->grammar(Grammar::Yaml)
|
||||
->state(fn (Node $node) => $node->getYamlConfiguration())
|
||||
->copyable()
|
||||
->disabled()
|
||||
->rows(19)
|
||||
->hintAction(fn () => request()->isSecure() ? CopyAction::make() : null)
|
||||
->columnSpanFull(),
|
||||
Grid::make()
|
||||
->columns()
|
||||
->columnSpanFull()
|
||||
->schema([
|
||||
FormActions::make([
|
||||
FormActions\Action::make('autoDeploy')
|
||||
Actions::make([
|
||||
Action::make('autoDeploy')
|
||||
->label(trans('admin/node.auto_deploy'))
|
||||
->color('primary')
|
||||
->modalHeading(trans('admin/node.auto_deploy'))
|
||||
@@ -551,36 +575,37 @@ class EditNode extends EditRecord
|
||||
->modalSubmitAction(false)
|
||||
->modalCancelAction(false)
|
||||
->modalFooterActionsAlignment(Alignment::Center)
|
||||
->form([
|
||||
->schema([
|
||||
ToggleButtons::make('docker')
|
||||
->label('Type')
|
||||
->label(trans('admin/node.auto_label'))
|
||||
->live()
|
||||
->helperText(trans('admin/node.auto_question'))
|
||||
->inline()
|
||||
->default(false)
|
||||
->afterStateUpdated(fn (bool $state, NodeAutoDeployService $service, Node $node, Set $set) => $set('generatedToken', $service->handle(request(), $node, $state)))
|
||||
->stateCast(new BooleanStateCast(false, true))
|
||||
->options([
|
||||
false => trans('admin/node.standalone'),
|
||||
true => trans('admin/node.docker'),
|
||||
0 => trans('admin/node.standalone'),
|
||||
1 => trans('admin/node.docker'),
|
||||
])
|
||||
->colors([
|
||||
false => 'primary',
|
||||
true => 'success',
|
||||
0 => 'primary',
|
||||
1 => 'success',
|
||||
])
|
||||
->columnSpan(1),
|
||||
Textarea::make('generatedToken')
|
||||
->label(trans('admin/node.auto_command'))
|
||||
->readOnly()
|
||||
->autosize()
|
||||
->hintAction(fn (string $state) => request()->isSecure() ? CopyAction::make()->copyable($state) : null)
|
||||
->hintCopy()
|
||||
->formatStateUsing(fn (NodeAutoDeployService $service, Node $node, Set $set, Get $get) => $set('generatedToken', $service->handle(request(), $node, $get('docker')))),
|
||||
])
|
||||
->mountUsing(function (Forms\Form $form) {
|
||||
$form->fill();
|
||||
->mountUsing(function (Schema $schema) {
|
||||
$schema->fill();
|
||||
}),
|
||||
])->fullWidth(),
|
||||
FormActions::make([
|
||||
FormActions\Action::make('resetKey')
|
||||
Actions::make([
|
||||
Action::make('resetKey')
|
||||
->label(trans('admin/node.reset_token'))
|
||||
->color('danger')
|
||||
->requiresConfirmation()
|
||||
@@ -605,6 +630,154 @@ class EditNode extends EditRecord
|
||||
])->fullWidth(),
|
||||
]),
|
||||
]),
|
||||
Tab::make('diagnostics')
|
||||
->label(trans('admin/node.tabs.diagnostics'))
|
||||
->icon('tabler-heart-search')
|
||||
->schema([
|
||||
Section::make('diag')
|
||||
->heading(trans('admin/node.tabs.diagnostics'))
|
||||
->columnSpanFull()
|
||||
->columns(4)
|
||||
->disabled(fn (Get $get) => $get('pulled'))
|
||||
->headerActions([
|
||||
Action::make('pull')
|
||||
->label(trans('admin/node.diagnostics.pull'))
|
||||
->icon('tabler-cloud-download')->iconButton()->iconSize(IconSize::ExtraLarge)
|
||||
->hidden(fn (Get $get) => $get('pulled'))
|
||||
->action(function (Get $get, Set $set, Node $node) {
|
||||
$includeEndpoints = $get('include_endpoints') ?? true;
|
||||
$includeLogs = $get('include_logs') ?? true;
|
||||
$logLines = $get('log_lines') ?? 200;
|
||||
|
||||
try {
|
||||
$response = $this->daemonSystemRepository->setNode($node)->getDiagnostics($logLines, $includeEndpoints, $includeLogs);
|
||||
|
||||
if ($response->status() === 404) {
|
||||
Notification::make()
|
||||
->title(trans('admin/node.diagnostics.404'))
|
||||
->warning()
|
||||
->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$set('pulled', true);
|
||||
$set('uploaded', false);
|
||||
$set('log', $response->body());
|
||||
|
||||
Notification::make()
|
||||
->title(trans('admin/node.diagnostics.logs_pulled'))
|
||||
->success()
|
||||
->send();
|
||||
} catch (ConnectionException $e) {
|
||||
Notification::make()
|
||||
->title(trans('admin/node.error_connecting', ['node' => $node->name]))
|
||||
->body($e->getMessage())
|
||||
->danger()
|
||||
->send();
|
||||
|
||||
}
|
||||
}),
|
||||
Action::make('upload')
|
||||
->label(trans('admin/node.diagnostics.upload'))
|
||||
->visible(fn (Get $get) => $get('pulled') ?? false)
|
||||
->icon('tabler-cloud-upload')->iconButton()->iconSize(IconSize::ExtraLarge)
|
||||
->action(function (Get $get, Set $set) {
|
||||
try {
|
||||
$response = Http::asMultipart()->post('https://logs.pelican.dev', [
|
||||
[
|
||||
'name' => 'c',
|
||||
'contents' => $get('log'),
|
||||
],
|
||||
[
|
||||
'name' => 'e',
|
||||
'contents' => '14d',
|
||||
],
|
||||
]);
|
||||
|
||||
if ($response->failed()) {
|
||||
Notification::make()
|
||||
->title(trans('admin/node.diagnostics.upload_failed'))
|
||||
->body(fn () => $response->status() . ' - ' . $response->body())
|
||||
->danger()
|
||||
->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$data = $response->json();
|
||||
$url = $data['url'];
|
||||
|
||||
Notification::make()
|
||||
->title(trans('admin/node.diagnostics.logs_uploaded'))
|
||||
->body("{$url}")
|
||||
->success()
|
||||
->actions([
|
||||
Action::make('viewLogs')
|
||||
->label(trans('admin/node.diagnostics.view_logs'))
|
||||
->url($url)
|
||||
->openUrlInNewTab(true),
|
||||
])
|
||||
->persistent()
|
||||
->send();
|
||||
$set('log', $url);
|
||||
$set('pulled', false);
|
||||
$set('uploaded', true);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Notification::make()
|
||||
->title(trans('admin/node.diagnostics.upload_failed'))
|
||||
->body($e->getMessage())
|
||||
->danger()
|
||||
->send();
|
||||
}
|
||||
}),
|
||||
Action::make('clear')
|
||||
->label(trans('admin/node.diagnostics.clear'))
|
||||
->visible(fn (Get $get) => $get('pulled') ?? false)
|
||||
->icon('tabler-trash')->iconButton()->iconSize(IconSize::ExtraLarge)->color('danger')
|
||||
->action(function (Get $get, Set $set) {
|
||||
$set('pulled', false);
|
||||
$set('uploaded', false);
|
||||
$set('log', null);
|
||||
$this->refresh();
|
||||
}
|
||||
),
|
||||
])
|
||||
->schema([
|
||||
ToggleButtons::make('include_endpoints')
|
||||
->hintIcon('tabler-question-mark')->inline()
|
||||
->hintIconTooltip(trans('admin/node.diagnostics.include_endpoints_hint'))
|
||||
->formatStateUsing(fn () => 1)
|
||||
->boolean(),
|
||||
ToggleButtons::make('include_logs')
|
||||
->live()
|
||||
->hintIcon('tabler-question-mark')->inline()
|
||||
->hintIconTooltip(trans('admin/node.diagnostics.include_logs_hint'))
|
||||
->formatStateUsing(fn () => 1)
|
||||
->boolean(),
|
||||
Slider::make('log_lines')
|
||||
->columnSpan(2)
|
||||
->hiddenLabel()
|
||||
->live()
|
||||
->tooltips(RawJs::make(<<<'JS'
|
||||
`${$value} lines`
|
||||
JS))
|
||||
->visible(fn (Get $get) => $get('include_logs'))
|
||||
->range(minValue: 100, maxValue: 500)
|
||||
->pips(PipsMode::Steps, density: 10)
|
||||
->step(50)
|
||||
->formatStateUsing(fn () => 200)
|
||||
->fillTrack(),
|
||||
Hidden::make('pulled'),
|
||||
Hidden::make('uploaded'),
|
||||
]),
|
||||
Textarea::make('log')
|
||||
->hiddenLabel()
|
||||
->columnSpanFull()
|
||||
->rows(35)
|
||||
->visible(fn (Get $get) => ($get('pulled') ?? false) || ($get('uploaded') ?? false)),
|
||||
]),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
@@ -613,8 +786,6 @@ class EditNode extends EditRecord
|
||||
{
|
||||
$node = Node::findOrFail($data['id']);
|
||||
|
||||
$data['config'] = $node->getYamlConfiguration();
|
||||
|
||||
if (!is_ip($node->fqdn)) {
|
||||
$ip = get_ip_from_hostname($node->fqdn);
|
||||
if ($ip) {
|
||||
@@ -633,11 +804,11 @@ class EditNode extends EditRecord
|
||||
return [];
|
||||
}
|
||||
|
||||
/** @return array<Actions\Action|Actions\ActionGroup> */
|
||||
/** @return array<Action|Actions> */
|
||||
protected function getDefaultHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\DeleteAction::make()
|
||||
DeleteAction::make()
|
||||
->disabled(fn (Node $node) => $node->servers()->count() > 0)
|
||||
->label(fn (Node $node) => $node->servers()->count() > 0 ? trans('admin/node.node_has_servers') : trans('filament-actions::delete.single.label')),
|
||||
$this->getSaveFormAction()->formId('form'),
|
||||
@@ -664,7 +835,7 @@ class EditNode extends EditRecord
|
||||
|
||||
try {
|
||||
if ($changed) {
|
||||
$this->daemonConfigurationRepository->setNode($node)->update($node);
|
||||
$this->daemonSystemRepository->setNode($node)->update($node);
|
||||
}
|
||||
parent::getSavedNotification()?->send();
|
||||
} catch (ConnectionException) {
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user