mirror of
https://github.com/pelican-dev/panel.git
synced 2026-05-04 18:00:48 +03:00
Compare commits
156 Commits
v1.0.0-bet
...
boy132/bac
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0a04c76d66 | ||
|
|
1a2e155302 | ||
|
|
4fdbbff74b | ||
|
|
2ed891633c | ||
|
|
58dcbeac0b | ||
|
|
91c5ddb2bd | ||
|
|
562be98b20 | ||
|
|
fd3b8a7ab3 | ||
|
|
1817383bf5 | ||
|
|
d39a0c4464 | ||
|
|
2e48095379 | ||
|
|
06c662988a | ||
|
|
98d7158dfc | ||
|
|
e01d9f2cf3 | ||
|
|
b693d0e728 | ||
|
|
7a8aaad66b | ||
|
|
b7aea4c26e | ||
|
|
612041e1f8 | ||
|
|
64bcdb514b | ||
|
|
c1105db702 | ||
|
|
761492d09f | ||
|
|
cdd69c29c5 | ||
|
|
5057d72cb7 | ||
|
|
f30025994e | ||
|
|
1a9cc5f565 | ||
|
|
a9f6bcb1e8 | ||
|
|
a6ba81eb2d | ||
|
|
c3b597db9b | ||
|
|
a949a565a8 | ||
|
|
bc727b72fd | ||
|
|
e35ce1e79d | ||
|
|
42c127c004 | ||
|
|
593f209142 | ||
|
|
9bf5b2cf0a | ||
|
|
f76e864a30 | ||
|
|
01cfa31ee1 | ||
|
|
dead664e4d | ||
|
|
c215e95133 | ||
|
|
1bbbcd0e25 | ||
|
|
677d2f742c | ||
|
|
650fb16d2d | ||
|
|
58814ea782 | ||
|
|
0918ed308b | ||
|
|
85d5f2ec3f | ||
|
|
810f237547 | ||
|
|
8f191890a1 | ||
|
|
160e0e54f5 | ||
|
|
fdcfbb00ca | ||
|
|
44f6cf8928 | ||
|
|
cf2a26bbf0 | ||
|
|
adb6678eee | ||
|
|
9539e21b39 | ||
|
|
33660f635f | ||
|
|
8c475ed95f | ||
|
|
d43cb1d180 | ||
|
|
fe55dbd200 | ||
|
|
0bb4503c2b | ||
|
|
3c1168beb5 | ||
|
|
1a092aedc8 | ||
|
|
8c99a8030f | ||
|
|
6e53b1cd7d | ||
|
|
4f2a4726a2 | ||
|
|
4042e0416b | ||
|
|
cc8973cf00 | ||
|
|
8ebe75b947 | ||
|
|
f8144407d1 | ||
|
|
e431ccb66a | ||
|
|
9291bb4477 | ||
|
|
e8c80ae420 | ||
|
|
f1be003276 | ||
|
|
e532a9a180 | ||
|
|
41fdd7bc8e | ||
|
|
9b01203b7c | ||
|
|
ab4eadec32 | ||
|
|
789c4c7284 | ||
|
|
b1a39f1724 | ||
|
|
6a548c09a0 | ||
|
|
55bda569cc | ||
|
|
adf1249086 | ||
|
|
dbf77bf146 | ||
|
|
a34bf9fd49 | ||
|
|
7a9deba0e1 | ||
|
|
e3893ff872 | ||
|
|
159bfe2210 | ||
|
|
a821db8aae | ||
|
|
1556f8efb8 | ||
|
|
57c2aa6f21 | ||
|
|
36de4c3786 | ||
|
|
26312e3897 | ||
|
|
a477c89025 | ||
|
|
93e81c26a9 | ||
|
|
23e91e8df3 | ||
|
|
833294bfaf | ||
|
|
3a9f09c188 | ||
|
|
9252b21205 | ||
|
|
abaeeff86d | ||
|
|
dd77555c42 | ||
|
|
297ecb544d | ||
|
|
e14bb7d030 | ||
|
|
6c8c2a0b91 | ||
|
|
c770937880 | ||
|
|
426643eaa6 | ||
|
|
3ca0f64e6e | ||
|
|
7da9d8c21d | ||
|
|
f1dbbbb7b0 | ||
|
|
150f8035d6 | ||
|
|
efebb999df | ||
|
|
53761f8b21 | ||
|
|
a181978a96 | ||
|
|
12d8b23c98 | ||
|
|
8e8ce3b50f | ||
|
|
b1e9cadc10 | ||
|
|
7bf1f18c2d | ||
|
|
6fe7d29960 | ||
|
|
15172b1d86 | ||
|
|
9f744d39a2 | ||
|
|
b79511568e | ||
|
|
adeb1b4217 | ||
|
|
d064bf9734 | ||
|
|
107286d618 | ||
|
|
a3203f7dda | ||
|
|
ad2333ea9d | ||
|
|
dd4e7231d0 | ||
|
|
e9abd56f7a | ||
|
|
675ab057b0 | ||
|
|
943d9d3ef5 | ||
|
|
c06a525be2 | ||
|
|
2ff5fdf831 | ||
|
|
0e810f3110 | ||
|
|
eadbe6e8fd | ||
|
|
53aa49b11a | ||
|
|
6ae4f007c8 | ||
|
|
6b9d683f06 | ||
|
|
3b24e22316 | ||
|
|
bd012f52a9 | ||
|
|
af202d9827 | ||
|
|
6ebeb40ba0 | ||
|
|
333eeda065 | ||
|
|
fcfafadec7 | ||
|
|
76b6118fd1 | ||
|
|
3141fe61b4 | ||
|
|
bed9dbeb2b | ||
|
|
976cb00c0d | ||
|
|
e3534bbb29 | ||
|
|
5740c93032 | ||
|
|
d72e075977 | ||
|
|
9af608f808 | ||
|
|
ac36e7a4b5 | ||
|
|
b1c64e2ef1 | ||
|
|
da2e930d4d | ||
|
|
460a5dfaf8 | ||
|
|
576f04be58 | ||
|
|
43fb030133 | ||
|
|
ae054f6e9b | ||
|
|
fef91791c3 | ||
|
|
1d5ace3a6d |
49
.github/workflows/ci.yaml
vendored
49
.github/workflows/ci.yaml
vendored
@@ -26,7 +26,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
php: [8.2, 8.3, 8.4]
|
||||
php: [8.3, 8.4, 8.5]
|
||||
env:
|
||||
DB_CONNECTION: sqlite
|
||||
DB_DATABASE: testing.sqlite
|
||||
@@ -61,14 +61,17 @@ jobs:
|
||||
- name: Create SQLite file
|
||||
run: touch database/testing.sqlite
|
||||
|
||||
- name: Run Migrations
|
||||
run: php artisan migrate --force --seed
|
||||
|
||||
- name: Unit tests
|
||||
run: vendor/bin/pest tests/Unit
|
||||
run: vendor/bin/pest tests/Unit --parallel
|
||||
env:
|
||||
DB_HOST: UNIT_NO_DB
|
||||
SKIP_MIGRATIONS: true
|
||||
|
||||
- name: Integration tests
|
||||
run: vendor/bin/pest tests/Integration
|
||||
run: vendor/bin/pest tests/Integration --parallel
|
||||
|
||||
mysql:
|
||||
name: MySQL
|
||||
@@ -76,8 +79,8 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
php: [8.2, 8.3, 8.4]
|
||||
database: ["mysql:8"]
|
||||
php: [8.5]
|
||||
database: ["mysql:8.4", "mysql:9.6"]
|
||||
services:
|
||||
database:
|
||||
image: ${{ matrix.database }}
|
||||
@@ -120,14 +123,20 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: composer install --no-interaction --no-suggest --no-progress --no-scripts
|
||||
|
||||
- name: Run Migrations
|
||||
run: php artisan migrate --force --seed
|
||||
env:
|
||||
DB_PORT: ${{ job.services.database.ports[3306] }}
|
||||
DB_USERNAME: root
|
||||
|
||||
- name: Unit tests
|
||||
run: vendor/bin/pest tests/Unit
|
||||
run: vendor/bin/pest tests/Unit --parallel
|
||||
env:
|
||||
DB_HOST: UNIT_NO_DB
|
||||
SKIP_MIGRATIONS: true
|
||||
|
||||
- name: Integration tests
|
||||
run: vendor/bin/pest tests/Integration
|
||||
run: vendor/bin/pest tests/Integration --parallel
|
||||
env:
|
||||
DB_PORT: ${{ job.services.database.ports[3306] }}
|
||||
DB_USERNAME: root
|
||||
@@ -138,8 +147,8 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
php: [8.2, 8.3, 8.4]
|
||||
database: ["mariadb:10.6", "mariadb:10.11", "mariadb:11.4"]
|
||||
php: [8.5]
|
||||
database: ["mariadb:10.11", "mariadb:11.4"]
|
||||
services:
|
||||
database:
|
||||
image: ${{ matrix.database }}
|
||||
@@ -182,14 +191,20 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: composer install --no-interaction --no-suggest --no-progress --no-scripts
|
||||
|
||||
- name: Run Migrations
|
||||
run: php artisan migrate --force --seed
|
||||
env:
|
||||
DB_PORT: ${{ job.services.database.ports[3306] }}
|
||||
DB_USERNAME: root
|
||||
|
||||
- name: Unit tests
|
||||
run: vendor/bin/pest tests/Unit
|
||||
run: vendor/bin/pest tests/Unit --parallel
|
||||
env:
|
||||
DB_HOST: UNIT_NO_DB
|
||||
SKIP_MIGRATIONS: true
|
||||
|
||||
- name: Integration tests
|
||||
run: vendor/bin/pest tests/Integration
|
||||
run: vendor/bin/pest tests/Integration --parallel
|
||||
env:
|
||||
DB_PORT: ${{ job.services.database.ports[3306] }}
|
||||
DB_USERNAME: root
|
||||
@@ -200,8 +215,8 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
php: [8.2, 8.3, 8.4]
|
||||
database: ["postgres:14"]
|
||||
php: [8.5]
|
||||
database: ["postgres:17", "postgres:18"]
|
||||
services:
|
||||
database:
|
||||
image: ${{ matrix.database }}
|
||||
@@ -238,6 +253,7 @@ jobs:
|
||||
path: ${{ steps.composer-cache.outputs.dir }}
|
||||
key: ${{ runner.os }}-composer-${{ matrix.php }}-${{ hashFiles('**/composer.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-composer-${{ matrix.php }}-
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
@@ -250,11 +266,14 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: composer install --no-interaction --no-suggest --no-progress --no-scripts
|
||||
|
||||
- name: Run Migrations
|
||||
run: php artisan migrate --force --seed
|
||||
|
||||
- name: Unit tests
|
||||
run: vendor/bin/pest tests/Unit
|
||||
run: vendor/bin/pest tests/Unit --parallel
|
||||
env:
|
||||
DB_HOST: UNIT_NO_DB
|
||||
SKIP_MIGRATIONS: true
|
||||
|
||||
- name: Integration tests
|
||||
run: vendor/bin/pest tests/Integration
|
||||
run: vendor/bin/pest tests/Integration --parallel
|
||||
|
||||
6
.github/workflows/lint.yaml
vendored
6
.github/workflows/lint.yaml
vendored
@@ -3,7 +3,7 @@ name: Lint
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- '**'
|
||||
- "**"
|
||||
|
||||
jobs:
|
||||
pint:
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: "8.3"
|
||||
php-version: "8.4"
|
||||
extensions: bcmath, curl, gd, mbstring, mysql, openssl, pdo, tokenizer, xml, zip
|
||||
tools: composer:v2
|
||||
coverage: none
|
||||
@@ -35,7 +35,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
php: [ 8.2, 8.3, 8.4 ]
|
||||
php: [8.2, 8.3, 8.4, 8.5]
|
||||
steps:
|
||||
- name: Code Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
51
Dockerfile
51
Dockerfile
@@ -38,7 +38,7 @@ RUN yarn config set network-timeout 300000 \
|
||||
FROM --platform=$TARGETOS/$TARGETARCH composer AS composerbuild
|
||||
|
||||
# Copy full code to optimize autoload
|
||||
COPY --exclude=Caddyfile --exclude=docker/ . ./
|
||||
COPY --exclude=docker/ . ./
|
||||
|
||||
RUN composer dump-autoload --optimize
|
||||
|
||||
@@ -50,7 +50,7 @@ FROM --platform=$TARGETOS/$TARGETARCH yarn AS yarnbuild
|
||||
WORKDIR /build
|
||||
|
||||
# Copy full code
|
||||
COPY --exclude=Caddyfile --exclude=docker/ . ./
|
||||
COPY --exclude=docker/ . ./
|
||||
COPY --from=composer /build .
|
||||
|
||||
RUN yarn run build
|
||||
@@ -62,38 +62,35 @@ FROM --platform=$TARGETOS/$TARGETARCH localhost:5000/base-php:$TARGETARCH AS fin
|
||||
|
||||
WORKDIR /var/www/html
|
||||
|
||||
# Install additional required libraries
|
||||
RUN apk add --no-cache \
|
||||
caddy ca-certificates supervisor supercronic fcgi
|
||||
# packages for running the panel
|
||||
caddy ca-certificates supervisor supercronic fcgi \
|
||||
# required for installing plugins. Pulled from https://github.com/pelican-dev/panel/pull/2034
|
||||
zip unzip 7zip bzip2-dev yarn git
|
||||
|
||||
COPY --chown=root:www-data --chmod=640 --from=composerbuild /build .
|
||||
COPY --chown=root:www-data --chmod=640 --from=yarnbuild /build/public ./public
|
||||
|
||||
# Set permissions
|
||||
# First ensure all files are owned by root and restrict www-data to read access
|
||||
RUN chown root:www-data ./ \
|
||||
&& chmod 750 ./ \
|
||||
# Files should not have execute set, but directories need it
|
||||
&& find ./ -type d -exec chmod 750 {} \; \
|
||||
# Create necessary directories
|
||||
&& mkdir -p /pelican-data/storage /pelican-data/plugins /var/www/html/storage/app/public /var/run/supervisord /etc/supercronic \
|
||||
# Symlinks for env, database, storage, and plugins
|
||||
&& ln -s /pelican-data/.env ./.env \
|
||||
&& ln -s /pelican-data/database/database.sqlite ./database/database.sqlite \
|
||||
&& ln -sf /var/www/html/storage/app/public /var/www/html/public/storage \
|
||||
&& ln -s /pelican-data/storage/avatars /var/www/html/storage/app/public/avatars \
|
||||
&& ln -s /pelican-data/storage/fonts /var/www/html/storage/app/public/fonts \
|
||||
&& ln -s /pelican-data/plugins /var/www/html/plugins \
|
||||
# Allow www-data write permissions where necessary
|
||||
&& chown -R www-data:www-data /pelican-data ./storage ./bootstrap/cache /var/run/supervisord /var/www/html/public/storage \
|
||||
&& chmod -R u+rwX,g+rwX,o-rwx /pelican-data ./storage ./bootstrap/cache /var/run/supervisord \
|
||||
&& chown -R www-data: /usr/local/etc/php/
|
||||
# Copy composer binary for runtime plugin dependency management
|
||||
COPY --from=composer /usr/local/bin/composer /usr/local/bin/composer
|
||||
COPY --chown=root:www-data --chmod=770 --from=composerbuild /build .
|
||||
COPY --chown=root:www-data --chmod=770 --from=yarnbuild /build/public ./public
|
||||
|
||||
# Create and remove directories
|
||||
RUN mkdir -p /pelican-data/storage /pelican-data/plugins /var/run/supervisord \
|
||||
&& rm -rf /var/www/html/plugins \
|
||||
# Symlinks for env, database, storage, and plugins
|
||||
&& ln -s /pelican-data/.env /var/www/html/.env \
|
||||
&& ln -s /pelican-data/database/database.sqlite ./database/database.sqlite \
|
||||
&& ln -s /pelican-data/storage /var/www/html/public/storage \
|
||||
&& ln -s /pelican-data/storage /var/www/html/storage/app/public \
|
||||
&& ln -s /pelican-data/plugins /var/www/html \
|
||||
# Allow www-data write permissions where necessary
|
||||
&& chown -R www-data: /pelican-data .env ./storage ./plugins ./bootstrap/cache /var/run/supervisord /var/www/html/public/storage \
|
||||
&& chmod -R 770 /pelican-data ./storage ./bootstrap/cache /var/run/supervisord \
|
||||
&& chown -R www-data: /usr/local/etc/php/ /usr/local/etc/php-fpm.d/ /var/www/html/composer.json /var/www/html/composer.lock
|
||||
# Configure Supervisor
|
||||
COPY docker/supervisord.conf /etc/supervisord.conf
|
||||
COPY docker/Caddyfile /etc/caddy/Caddyfile
|
||||
# Add Laravel scheduler to crontab
|
||||
COPY docker/crontab /etc/supercronic/crontab
|
||||
COPY docker/crontab /etc/crontabs/crontab
|
||||
|
||||
COPY docker/entrypoint.sh /entrypoint.sh
|
||||
COPY docker/healthcheck.sh /healthcheck.sh
|
||||
|
||||
@@ -5,6 +5,6 @@ FROM --platform=$TARGETOS/$TARGETARCH php:8.4-fpm-alpine
|
||||
|
||||
ADD --chmod=0755 https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions /usr/local/bin/
|
||||
|
||||
RUN install-php-extensions bcmath gd intl zip opcache pcntl posix pdo_mysql pdo_pgsql
|
||||
RUN install-php-extensions bcmath gd intl zip pcntl pdo_mysql pdo_pgsql bz2
|
||||
|
||||
RUN rm /usr/local/bin/install-php-extensions
|
||||
|
||||
@@ -5,7 +5,7 @@ FROM --platform=$TARGETOS/$TARGETARCH php:8.4-fpm-alpine AS base
|
||||
|
||||
ADD --chmod=0755 https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions /usr/local/bin/
|
||||
|
||||
RUN install-php-extensions bcmath gd intl zip opcache pcntl posix pdo_mysql pdo_pgsql
|
||||
RUN install-php-extensions bcmath gd intl zip pcntl pdo_mysql pdo_pgsql bz2
|
||||
|
||||
RUN rm /usr/local/bin/install-php-extensions
|
||||
|
||||
@@ -42,7 +42,7 @@ RUN yarn config set network-timeout 300000 \
|
||||
FROM --platform=$TARGETOS/$TARGETARCH composer AS composerbuild
|
||||
|
||||
# Copy full code to optimize autoload
|
||||
COPY --exclude=Caddyfile --exclude=docker/ . ./
|
||||
COPY --exclude=docker/ . ./
|
||||
|
||||
RUN composer dump-autoload --optimize
|
||||
|
||||
@@ -54,7 +54,7 @@ FROM --platform=$TARGETOS/$TARGETARCH yarn AS yarnbuild
|
||||
WORKDIR /build
|
||||
|
||||
# Copy full code
|
||||
COPY --exclude=Caddyfile --exclude=docker/ . ./
|
||||
COPY --exclude=docker/ . ./
|
||||
COPY --from=composer /build .
|
||||
|
||||
RUN yarn run build
|
||||
@@ -68,36 +68,35 @@ WORKDIR /var/www/html
|
||||
|
||||
# Install additional required libraries
|
||||
RUN apk add --no-cache \
|
||||
caddy ca-certificates supervisor supercronic fcgi coreutils
|
||||
# packages for running the panel
|
||||
caddy ca-certificates supervisor supercronic fcgi coreutils \
|
||||
# required for installing plugins. Pulled from https://github.com/pelican-dev/panel/pull/2034
|
||||
zip unzip 7zip bzip2-dev yarn git
|
||||
|
||||
COPY --chown=root:www-data --chmod=640 --from=composerbuild /build .
|
||||
COPY --chown=root:www-data --chmod=640 --from=yarnbuild /build/public ./public
|
||||
# Copy composer binary for runtime plugin dependency management
|
||||
COPY --from=composer /usr/local/bin/composer /usr/local/bin/composer
|
||||
COPY --chown=root:www-data --chmod=770 --from=composerbuild /build .
|
||||
COPY --chown=root:www-data --chmod=770 --from=yarnbuild /build/public ./public
|
||||
|
||||
# Set permissions
|
||||
# First ensure all files are owned by root and restrict www-data to read access
|
||||
RUN chown root:www-data ./ \
|
||||
&& chmod 750 ./ \
|
||||
# Files should not have execute set, but directories need it
|
||||
&& find ./ -type d -exec chmod 750 {} \; \
|
||||
# Create necessary directories
|
||||
&& mkdir -p /pelican-data/storage /pelican-data/plugins /var/www/html/storage/app/public /var/run/supervisord /etc/supercronic \
|
||||
# Symlinks for env, database, storage, and plugins
|
||||
&& ln -s /pelican-data/.env ./.env \
|
||||
&& ln -s /pelican-data/database/database.sqlite ./database/database.sqlite \
|
||||
&& ln -sf /var/www/html/storage/app/public /var/www/html/public/storage \
|
||||
&& ln -s /pelican-data/storage/avatars /var/www/html/storage/app/public/avatars \
|
||||
&& ln -s /pelican-data/storage/fonts /var/www/html/storage/app/public/fonts \
|
||||
&& ln -s /pelican-data/plugins /var/www/html/plugins \
|
||||
# Allow www-data write permissions where necessary
|
||||
&& chown -R www-data:www-data /pelican-data ./storage ./bootstrap/cache /var/run/supervisord /var/www/html/public/storage \
|
||||
&& chmod -R u+rwX,g+rwX,o-rwx /pelican-data ./storage ./bootstrap/cache /var/run/supervisord \
|
||||
&& chown -R www-data: /usr/local/etc/php/
|
||||
# Create and remove directories
|
||||
RUN mkdir -p /pelican-data/storage /pelican-data/plugins /var/run/supervisord \
|
||||
&& rm -rf /var/www/html/plugins \
|
||||
# Symlinks for env, database, storage, and plugins
|
||||
&& ln -s /pelican-data/.env /var/www/html/.env \
|
||||
&& ln -s /pelican-data/database/database.sqlite ./database/database.sqlite \
|
||||
&& ln -s /pelican-data/storage /var/www/html/public/storage \
|
||||
&& ln -s /pelican-data/storage /var/www/html/storage/app/public \
|
||||
&& ln -s /pelican-data/plugins /var/www/html \
|
||||
# Allow www-data write permissions where necessary
|
||||
&& chown -R www-data: /pelican-data .env ./storage ./plugins ./bootstrap/cache /var/run/supervisord /var/www/html/public/storage \
|
||||
&& chmod -R 770 /pelican-data ./storage ./bootstrap/cache /var/run/supervisord \
|
||||
&& chown -R www-data: /usr/local/etc/php/ /usr/local/etc/php-fpm.d/ /var/www/html/composer.json /var/www/html/composer.lock
|
||||
|
||||
# Configure Supervisor
|
||||
COPY docker/supervisord.conf /etc/supervisord.conf
|
||||
COPY docker/Caddyfile /etc/caddy/Caddyfile
|
||||
# Add Laravel scheduler to crontab
|
||||
COPY docker/crontab /etc/supercronic/crontab
|
||||
COPY docker/crontab /etc/crontabs/crontab
|
||||
|
||||
COPY docker/entrypoint.sh /entrypoint.sh
|
||||
COPY docker/healthcheck.sh /healthcheck.sh
|
||||
|
||||
48
app/Console/Commands/Dev/GenerateTablerIconsEnum.php
Normal file
48
app/Console/Commands/Dev/GenerateTablerIconsEnum.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands\Dev;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\File;
|
||||
|
||||
class GenerateTablerIconsEnum extends Command
|
||||
{
|
||||
protected $signature = 'dev:generate-tabler-icons-enum';
|
||||
|
||||
protected $description = 'Generate an enum for tabler icons based on the secondnetwork/blade-tabler-icons svgs';
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
$files = File::files(base_path('vendor/secondnetwork/blade-tabler-icons/resources/svg'));
|
||||
$files = array_filter($files, fn ($file) => $file->getExtension() === 'svg');
|
||||
|
||||
$enumContent = "<?php\n\n";
|
||||
$enumContent .= "namespace App\\Enums;\n\n";
|
||||
$enumContent .= "enum TablerIcon: string\n{\n";
|
||||
|
||||
foreach ($files as $file) {
|
||||
$filename = pathinfo($file->getFilename(), PATHINFO_FILENAME);
|
||||
|
||||
// Letter V is duplicate, as "letter-v" and "letter-letter-v"
|
||||
if (str($filename)->contains('letter-letter')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Filled icons exist with "-f" and "-filled", we only want the later
|
||||
if (str($filename)->endsWith('-f') && file_exists(base_path("vendor/secondnetwork/blade-tabler-icons/resources/svg/{$filename}illed.svg"))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$caseName = str($filename)->title()->replace('-', '');
|
||||
$value = str($filename)->slug()->prepend('tabler-');
|
||||
|
||||
$enumContent .= " case $caseName = '$value';\n";
|
||||
}
|
||||
|
||||
$enumContent .= "}\n";
|
||||
|
||||
File::put(base_path('app/Enums/TablerIcon.php'), $enumContent);
|
||||
|
||||
$this->info('Enum generated');
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,6 @@ use App\Services\Eggs\Sharing\EggExporterService;
|
||||
use Exception;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use JsonException;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
class CheckEggUpdatesCommand extends Command
|
||||
@@ -22,14 +21,12 @@ class CheckEggUpdatesCommand extends Command
|
||||
try {
|
||||
$this->check($egg, $exporterService);
|
||||
} catch (Exception $exception) {
|
||||
$this->error("{$egg->name}: Error ({$exception->getMessage()})");
|
||||
$this->error("$egg->name: Error ({$exception->getMessage()})");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws JsonException
|
||||
*/
|
||||
/** @throws Exception */
|
||||
private function check(Egg $egg, EggExporterService $exporterService): void
|
||||
{
|
||||
if (is_null($egg->update_url)) {
|
||||
@@ -45,7 +42,13 @@ class CheckEggUpdatesCommand extends Command
|
||||
? Yaml::parse($exporterService->handle($egg->id, EggFormat::YAML))
|
||||
: json_decode($exporterService->handle($egg->id, EggFormat::JSON), true);
|
||||
|
||||
$remote = Http::timeout(5)->connectTimeout(1)->get($egg->update_url)->throw()->body();
|
||||
$remote = Http::timeout(5)->connectTimeout(1)->get($egg->update_url);
|
||||
|
||||
if ($remote->failed()) {
|
||||
throw new Exception("HTTP request returned status code {$remote->status()}");
|
||||
}
|
||||
|
||||
$remote = $remote->body();
|
||||
$remote = $isYaml ? Yaml::parse($remote) : json_decode($remote, true);
|
||||
|
||||
unset($local['exported_at'], $remote['exported_at']);
|
||||
|
||||
@@ -13,7 +13,7 @@ class UpdateEggIndexCommand extends Command
|
||||
public function handle(): int
|
||||
{
|
||||
try {
|
||||
$data = Http::timeout(5)->connectTimeout(1)->get('https://raw.githubusercontent.com/pelican-eggs/pelican-eggs.github.io/refs/heads/main/content/pelican.json')->throw()->json();
|
||||
$data = Http::timeout(5)->connectTimeout(1)->get(config('panel.cdn.egg_index_url'))->throw()->json();
|
||||
} catch (Exception $exception) {
|
||||
$this->error($exception->getMessage());
|
||||
|
||||
|
||||
19
app/Console/Commands/Overrides/ConfigCacheCommand.php
Normal file
19
app/Console/Commands/Overrides/ConfigCacheCommand.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands\Overrides;
|
||||
|
||||
use Illuminate\Foundation\Console\ConfigCacheCommand as BaseConfigCacheCommand;
|
||||
|
||||
class ConfigCacheCommand extends BaseConfigCacheCommand
|
||||
{
|
||||
/**
|
||||
* Prevent config from being cached
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$this->components->warn('Configuration caching has been disabled.');
|
||||
|
||||
$this->line(' Reason: This application uses dynamic plugins. Caching config');
|
||||
$this->line(' prevents /plugins/config/*.php files from being loaded correctly.');
|
||||
}
|
||||
}
|
||||
18
app/Console/Commands/Overrides/OptimizeCommand.php
Normal file
18
app/Console/Commands/Overrides/OptimizeCommand.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands\Overrides;
|
||||
|
||||
use Illuminate\Foundation\Console\OptimizeCommand as BaseOptimizeCommand;
|
||||
|
||||
class OptimizeCommand extends BaseOptimizeCommand
|
||||
{
|
||||
/**
|
||||
* Prevent config from being cached
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
protected function getOptimizeTasks()
|
||||
{
|
||||
return array_except(parent::getOptimizeTasks(), 'config');
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@ class DisablePluginCommand extends Command
|
||||
{
|
||||
$id = $this->argument('id') ?? $this->choice('Plugin', Plugin::pluck('name', 'id')->toArray());
|
||||
|
||||
$plugin = Plugin::find($id);
|
||||
$plugin = Plugin::find(str($id)->lower()->toString());
|
||||
|
||||
if (!$plugin) {
|
||||
$this->error('Plugin does not exist!');
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace App\Console\Commands\Plugin;
|
||||
use App\Enums\PluginStatus;
|
||||
use App\Models\Plugin;
|
||||
use App\Services\Helpers\PluginService;
|
||||
use Exception;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class InstallPluginCommand extends Command
|
||||
@@ -17,7 +18,7 @@ class InstallPluginCommand extends Command
|
||||
{
|
||||
$id = $this->argument('id') ?? $this->choice('Plugin', Plugin::pluck('name', 'id')->toArray());
|
||||
|
||||
$plugin = Plugin::find($id);
|
||||
$plugin = Plugin::find(str($id)->lower()->toString());
|
||||
|
||||
if (!$plugin) {
|
||||
$this->error('Plugin does not exist!');
|
||||
@@ -31,8 +32,12 @@ class InstallPluginCommand extends Command
|
||||
return;
|
||||
}
|
||||
|
||||
$pluginService->installPlugin($plugin);
|
||||
try {
|
||||
$pluginService->installPlugin($plugin);
|
||||
|
||||
$this->info('Plugin installed and enabled.');
|
||||
$this->info('Plugin installed and enabled.');
|
||||
} catch (Exception $exception) {
|
||||
$this->error('Could not install plugin: ' . $exception->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace App\Console\Commands\Plugin;
|
||||
use App\Enums\PluginStatus;
|
||||
use App\Models\Plugin;
|
||||
use App\Services\Helpers\PluginService;
|
||||
use Exception;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class UninstallPluginCommand extends Command
|
||||
@@ -17,7 +18,7 @@ class UninstallPluginCommand extends Command
|
||||
{
|
||||
$id = $this->argument('id') ?? $this->choice('Plugin', Plugin::pluck('name', 'id')->toArray());
|
||||
|
||||
$plugin = Plugin::find($id);
|
||||
$plugin = Plugin::find(str($id)->lower()->toString());
|
||||
|
||||
if (!$plugin) {
|
||||
$this->error('Plugin does not exist!');
|
||||
@@ -36,8 +37,12 @@ class UninstallPluginCommand extends Command
|
||||
$deleteFiles = $this->confirm('Do you also want to delete the plugin files?');
|
||||
}
|
||||
|
||||
$pluginService->uninstallPlugin($plugin, $deleteFiles);
|
||||
try {
|
||||
$pluginService->uninstallPlugin($plugin, $deleteFiles);
|
||||
|
||||
$this->info('Plugin uninstalled' . ($deleteFiles ? ' and files deleted' : '') . '.');
|
||||
$this->info('Plugin uninstalled' . ($deleteFiles ? ' and files deleted' : '') . '.');
|
||||
} catch (Exception $exception) {
|
||||
$this->error('Could not uninstall plugin: ' . $exception->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace App\Console\Commands\Plugin;
|
||||
|
||||
use App\Models\Plugin;
|
||||
use App\Services\Helpers\PluginService;
|
||||
use Exception;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class UpdatePluginCommand extends Command
|
||||
@@ -16,7 +17,7 @@ class UpdatePluginCommand extends Command
|
||||
{
|
||||
$id = $this->argument('id') ?? $this->choice('Plugin', Plugin::pluck('name', 'id')->toArray());
|
||||
|
||||
$plugin = Plugin::find($id);
|
||||
$plugin = Plugin::find(str($id)->lower()->toString());
|
||||
|
||||
if (!$plugin) {
|
||||
$this->error('Plugin does not exist!');
|
||||
@@ -30,8 +31,12 @@ class UpdatePluginCommand extends Command
|
||||
return;
|
||||
}
|
||||
|
||||
$pluginService->updatePlugin($plugin);
|
||||
try {
|
||||
$pluginService->updatePlugin($plugin);
|
||||
|
||||
$this->info('Plugin updated.');
|
||||
$this->info('Plugin updated.');
|
||||
} catch (Exception $exception) {
|
||||
$this->error('Could not update plugin: ' . $exception->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
use BackedEnum;
|
||||
use Filament\Support\Contracts\HasColor;
|
||||
use Filament\Support\Contracts\HasIcon;
|
||||
use Filament\Support\Contracts\HasLabel;
|
||||
@@ -12,12 +13,12 @@ enum BackupStatus: string implements HasColor, HasIcon, HasLabel
|
||||
case Successful = 'successful';
|
||||
case Failed = 'failed';
|
||||
|
||||
public function getIcon(): string
|
||||
public function getIcon(): BackedEnum
|
||||
{
|
||||
return match ($this) {
|
||||
self::InProgress => 'tabler-circle-dashed',
|
||||
self::Successful => 'tabler-circle-check',
|
||||
self::Failed => 'tabler-circle-x',
|
||||
self::InProgress => TablerIcon::CircleDashed,
|
||||
self::Successful => TablerIcon::CircleCheck,
|
||||
self::Failed => TablerIcon::CircleX,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
use BackedEnum;
|
||||
use Filament\Support\Contracts\HasColor;
|
||||
use Filament\Support\Contracts\HasIcon;
|
||||
use Filament\Support\Contracts\HasLabel;
|
||||
@@ -23,20 +24,20 @@ enum ContainerStatus: string implements HasColor, HasIcon, HasLabel
|
||||
// HTTP Based
|
||||
case Missing = 'missing';
|
||||
|
||||
public function getIcon(): string
|
||||
public function getIcon(): BackedEnum
|
||||
{
|
||||
return match ($this) {
|
||||
|
||||
self::Created => 'tabler-heart-plus',
|
||||
self::Starting => 'tabler-heart-up',
|
||||
self::Running => 'tabler-heartbeat',
|
||||
self::Restarting => 'tabler-heart-bolt',
|
||||
self::Exited => 'tabler-heart-exclamation',
|
||||
self::Paused => 'tabler-heart-pause',
|
||||
self::Dead, self::Offline => 'tabler-heart-x',
|
||||
self::Removing => 'tabler-heart-down',
|
||||
self::Missing => 'tabler-heart-search',
|
||||
self::Stopping => 'tabler-heart-minus',
|
||||
self::Created => TablerIcon::HeartPlus,
|
||||
self::Starting => TablerIcon::HeartUp,
|
||||
self::Running => TablerIcon::Heartbeat,
|
||||
self::Restarting => TablerIcon::HeartBolt,
|
||||
self::Exited => TablerIcon::HeartExclamation,
|
||||
self::Paused => TablerIcon::HeartPause,
|
||||
self::Dead, self::Offline => TablerIcon::HeartX,
|
||||
self::Removing => TablerIcon::HeartDown,
|
||||
self::Missing => TablerIcon::HeartSearch,
|
||||
self::Stopping => TablerIcon::HeartMinus,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,9 @@ enum CustomizationKey: string
|
||||
case TopNavigation = 'top_navigation';
|
||||
case DashboardLayout = 'dashboard_layout';
|
||||
|
||||
case ButtonStyle = 'button_style';
|
||||
case RedirectToAdmin = 'redirect_to_admin';
|
||||
|
||||
public function getDefaultValue(): string|int|bool
|
||||
{
|
||||
return match ($this) {
|
||||
@@ -20,6 +23,8 @@ enum CustomizationKey: string
|
||||
self::ConsoleGraphPeriod => 30,
|
||||
self::TopNavigation => config('panel.filament.default-navigation', 'sidebar'),
|
||||
self::DashboardLayout => 'grid',
|
||||
self::ButtonStyle => true,
|
||||
self::RedirectToAdmin => false,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
use BackedEnum;
|
||||
use Filament\Support\Contracts\HasIcon;
|
||||
use Filament\Support\Contracts\HasLabel;
|
||||
|
||||
@@ -11,12 +12,12 @@ enum PluginCategory: string implements HasIcon, HasLabel
|
||||
case Theme = 'theme';
|
||||
case Language = 'language';
|
||||
|
||||
public function getIcon(): string
|
||||
public function getIcon(): BackedEnum
|
||||
{
|
||||
return match ($this) {
|
||||
self::Plugin => 'tabler-package',
|
||||
self::Theme => 'tabler-palette',
|
||||
self::Language => 'tabler-language',
|
||||
self::Plugin => TablerIcon::Package,
|
||||
self::Theme => TablerIcon::Palette,
|
||||
self::Language => TablerIcon::Language,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
use BackedEnum;
|
||||
use Filament\Support\Contracts\HasColor;
|
||||
use Filament\Support\Contracts\HasIcon;
|
||||
use Filament\Support\Contracts\HasLabel;
|
||||
@@ -14,14 +15,14 @@ enum PluginStatus: string implements HasColor, HasIcon, HasLabel
|
||||
case Errored = 'errored';
|
||||
case Incompatible = 'incompatible';
|
||||
|
||||
public function getIcon(): string
|
||||
public function getIcon(): BackedEnum
|
||||
{
|
||||
return match ($this) {
|
||||
self::NotInstalled => 'tabler-heart-off',
|
||||
self::Disabled => 'tabler-heart-x',
|
||||
self::Enabled => 'tabler-heart-check',
|
||||
self::Errored => 'tabler-heart-broken',
|
||||
self::Incompatible => 'tabler-heart-cancel',
|
||||
self::NotInstalled => TablerIcon::HeartOff,
|
||||
self::Disabled => TablerIcon::HeartX,
|
||||
self::Enabled => TablerIcon::HeartCheck,
|
||||
self::Errored => TablerIcon::HeartBroken,
|
||||
self::Incompatible => TablerIcon::HeartCancel,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
65
app/Enums/ResourceLimit.php
Normal file
65
app/Enums/ResourceLimit.php
Normal file
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
use App\Models\Server;
|
||||
use Illuminate\Cache\RateLimiting\Limit;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Routing\Middleware\ThrottleRequests;
|
||||
use Illuminate\Support\Facades\RateLimiter;
|
||||
use Webmozart\Assert\Assert;
|
||||
|
||||
/**
|
||||
* A basic resource throttler for individual servers. This is applied in addition
|
||||
* to existing rate limits and allows the code to slow down speedy users that might
|
||||
* be creating resources a little too quickly for comfort. This throttle generally
|
||||
* only applies to creation flows, and not general view/edit/delete flows.
|
||||
*/
|
||||
enum ResourceLimit: string
|
||||
{
|
||||
case Websocket = 'websocket';
|
||||
case AllocationCreate = 'allocation-create';
|
||||
case BackupRestore = 'backup-restore';
|
||||
case DatabaseCreate = 'database-create';
|
||||
case ScheduleCreate = 'schedule-create';
|
||||
case SubuserCreate = 'subuser-create';
|
||||
case FilePull = 'file-pull';
|
||||
|
||||
public function throttleKey(): string
|
||||
{
|
||||
return "api.client:server-resource:{$this->name}";
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a middleware that will throttle the specific resource by server. This
|
||||
* throttle applies to any user making changes to that resource on the specific
|
||||
* server, it is NOT per-user.
|
||||
*/
|
||||
public function middleware(): string
|
||||
{
|
||||
return ThrottleRequests::using($this->throttleKey());
|
||||
}
|
||||
|
||||
public function limit(): Limit
|
||||
{
|
||||
return match ($this) {
|
||||
self::Websocket => Limit::perMinute(5),
|
||||
self::BackupRestore => Limit::perMinutes(15, 3),
|
||||
self::DatabaseCreate => Limit::perMinute(2),
|
||||
self::SubuserCreate => Limit::perMinutes(15, 10),
|
||||
self::FilePull => Limit::perMinutes(10, 5),
|
||||
default => Limit::perMinute(2),
|
||||
};
|
||||
}
|
||||
|
||||
public static function boot(): void
|
||||
{
|
||||
foreach (self::cases() as $case) {
|
||||
RateLimiter::for($case->throttleKey(), function (Request $request) use ($case) {
|
||||
Assert::isInstanceOf($server = $request->route()->parameter('server'), Server::class);
|
||||
|
||||
return $case->limit()->by($server->uuid);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,8 @@ namespace App\Enums;
|
||||
enum RolePermissionModels: string
|
||||
{
|
||||
case ApiKey = 'apiKey';
|
||||
case Allocation = 'allocation';
|
||||
case BackupHost = 'backupHost';
|
||||
case DatabaseHost = 'databaseHost';
|
||||
case Database = 'database';
|
||||
case Egg = 'egg';
|
||||
@@ -34,4 +36,9 @@ enum RolePermissionModels: string
|
||||
{
|
||||
return RolePermissionPrefixes::Update->value . ' ' . $this->value;
|
||||
}
|
||||
|
||||
public function delete(): string
|
||||
{
|
||||
return RolePermissionPrefixes::Delete->value . ' ' . $this->value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
use BackedEnum;
|
||||
use Filament\Support\Contracts\HasColor;
|
||||
use Filament\Support\Contracts\HasIcon;
|
||||
use Filament\Support\Contracts\HasLabel;
|
||||
@@ -14,14 +15,13 @@ enum ServerState: string implements HasColor, HasIcon, HasLabel
|
||||
case Suspended = 'suspended';
|
||||
case RestoringBackup = 'restoring_backup';
|
||||
|
||||
public function getIcon(): string
|
||||
public function getIcon(): BackedEnum
|
||||
{
|
||||
return match ($this) {
|
||||
self::Installing => 'tabler-heart-bolt',
|
||||
self::InstallFailed => 'tabler-heart-x',
|
||||
self::ReinstallFailed => 'tabler-heart-x',
|
||||
self::Suspended => 'tabler-heart-cancel',
|
||||
self::RestoringBackup => 'tabler-heart-up',
|
||||
self::Installing => TablerIcon::HeartBolt,
|
||||
self::InstallFailed, self::ReinstallFailed => TablerIcon::HeartX,
|
||||
self::Suspended => TablerIcon::HeartCancel,
|
||||
self::RestoringBackup => TablerIcon::HeartUp,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
9
app/Enums/StepPosition.php
Normal file
9
app/Enums/StepPosition.php
Normal file
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
enum StepPosition: string
|
||||
{
|
||||
case Before = 'before';
|
||||
case After = 'after';
|
||||
}
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
use BackedEnum;
|
||||
|
||||
enum SubuserPermission: string
|
||||
{
|
||||
case WebsocketConnect = 'websocket.connect';
|
||||
@@ -48,6 +50,9 @@ enum SubuserPermission: string
|
||||
|
||||
case ActivityRead = 'activity.read';
|
||||
|
||||
case MountRead = 'mount.read';
|
||||
case MountUpdate = 'mount.update';
|
||||
|
||||
case StartupRead = 'startup.read';
|
||||
case StartupUpdate = 'startup.update';
|
||||
case StartupDockerImage = 'startup.docker-image';
|
||||
@@ -55,6 +60,7 @@ enum SubuserPermission: string
|
||||
case SettingsRename = 'settings.rename';
|
||||
case SettingsDescription = 'settings.description';
|
||||
case SettingsReinstall = 'settings.reinstall';
|
||||
case SettingsChangeIcon = 'settings.change-icon';
|
||||
|
||||
/** @return string[] */
|
||||
public function split(): array
|
||||
@@ -67,21 +73,22 @@ enum SubuserPermission: string
|
||||
return $this === self::WebsocketConnect;
|
||||
}
|
||||
|
||||
public function getIcon(): ?string
|
||||
public function getIcon(): ?BackedEnum
|
||||
{
|
||||
[$group, $permission] = $this->split();
|
||||
|
||||
return match ($group) {
|
||||
'control' => 'tabler-terminal-2',
|
||||
'user' => 'tabler-users',
|
||||
'file' => 'tabler-files',
|
||||
'backup' => 'tabler-file-zip',
|
||||
'allocation' => 'tabler-network',
|
||||
'startup' => 'tabler-player-play',
|
||||
'database' => 'tabler-database',
|
||||
'schedule' => 'tabler-clock',
|
||||
'settings' => 'tabler-settings',
|
||||
'activity' => 'tabler-stack',
|
||||
'control' => TablerIcon::Terminal2,
|
||||
'user' => TablerIcon::Users,
|
||||
'file' => TablerIcon::Files,
|
||||
'backup' => TablerIcon::FileZip,
|
||||
'allocation' => TablerIcon::Network,
|
||||
'startup' => TablerIcon::PlayerPlay,
|
||||
'database' => TablerIcon::Database,
|
||||
'schedule' => TablerIcon::Clock,
|
||||
'settings' => TablerIcon::Settings,
|
||||
'activity' => TablerIcon::Stack,
|
||||
'mount' => TablerIcon::LayersLinked,
|
||||
default => null,
|
||||
};
|
||||
}
|
||||
|
||||
9
app/Enums/TabPosition.php
Normal file
9
app/Enums/TabPosition.php
Normal file
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
enum TabPosition: string
|
||||
{
|
||||
case Before = 'before';
|
||||
case After = 'after';
|
||||
}
|
||||
5997
app/Enums/TablerIcon.php
Normal file
5997
app/Enums/TablerIcon.php
Normal file
File diff suppressed because it is too large
Load Diff
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
use BackedEnum;
|
||||
use Filament\Support\Contracts\HasColor;
|
||||
use Filament\Support\Contracts\HasIcon;
|
||||
use Filament\Support\Contracts\HasLabel;
|
||||
@@ -24,11 +25,11 @@ enum WebhookType: string implements HasColor, HasIcon, HasLabel
|
||||
};
|
||||
}
|
||||
|
||||
public function getIcon(): string
|
||||
public function getIcon(): BackedEnum
|
||||
{
|
||||
return match ($this) {
|
||||
self::Regular => 'tabler-world-www',
|
||||
self::Discord => 'tabler-brand-discord',
|
||||
self::Regular => TablerIcon::WorldWww,
|
||||
self::Discord => TablerIcon::BrandDiscord,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
17
app/Events/User/Deleting.php
Normal file
17
app/Events/User/Deleting.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace App\Events\User;
|
||||
|
||||
use App\Events\Event;
|
||||
use App\Models\User;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class Deleting extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*/
|
||||
public function __construct(public User $user) {}
|
||||
}
|
||||
13
app/Events/User/PasswordChanged.php
Normal file
13
app/Events/User/PasswordChanged.php
Normal file
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Events\User;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
|
||||
final class PasswordChanged
|
||||
{
|
||||
use Dispatchable;
|
||||
|
||||
public function __construct(public readonly User $user) {}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
use App\Exceptions\Solutions\ManifestDoesNotExistSolution;
|
||||
use Exception;
|
||||
use Spatie\Ignition\Contracts\ProvidesSolution;
|
||||
use Spatie\Ignition\Contracts\Solution;
|
||||
|
||||
class ManifestDoesNotExistException extends Exception implements ProvidesSolution
|
||||
{
|
||||
public function getSolution(): Solution
|
||||
{
|
||||
return new ManifestDoesNotExistSolution();
|
||||
}
|
||||
}
|
||||
7
app/Exceptions/PluginIdMismatchException.php
Normal file
7
app/Exceptions/PluginIdMismatchException.php
Normal file
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
use Exception;
|
||||
|
||||
class PluginIdMismatchException extends Exception {}
|
||||
@@ -1,25 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions\Solutions;
|
||||
|
||||
use Spatie\Ignition\Contracts\Solution;
|
||||
|
||||
class ManifestDoesNotExistSolution implements Solution
|
||||
{
|
||||
public function getSolutionTitle(): string
|
||||
{
|
||||
return "The manifest.json file hasn't been generated yet";
|
||||
}
|
||||
|
||||
public function getSolutionDescription(): string
|
||||
{
|
||||
return 'Run yarn run build:production to build the frontend first.';
|
||||
}
|
||||
|
||||
public function getDocumentationLinks(): array
|
||||
{
|
||||
return [
|
||||
'Docs' => 'https://github.com/pelican/panel/blob/master/package.json',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace App\Extensions\BackupAdapter;
|
||||
|
||||
use App\Models\Backup;
|
||||
use App\Models\User;
|
||||
use Filament\Schemas\Components\Component;
|
||||
|
||||
interface BackupAdapterSchemaInterface
|
||||
{
|
||||
public function getId(): string;
|
||||
|
||||
public function getName(): string;
|
||||
|
||||
public function createBackup(Backup $backup): void;
|
||||
|
||||
public function deleteBackup(Backup $backup): void;
|
||||
|
||||
public function getDownloadLink(Backup $backup, User $user): string;
|
||||
|
||||
/** @return Component[] */
|
||||
public function getConfigurationForm(): array;
|
||||
}
|
||||
35
app/Extensions/BackupAdapter/BackupAdapterService.php
Normal file
35
app/Extensions/BackupAdapter/BackupAdapterService.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace App\Extensions\BackupAdapter;
|
||||
|
||||
class BackupAdapterService
|
||||
{
|
||||
/** @var array<string, BackupAdapterSchemaInterface> */
|
||||
private array $schemas = [];
|
||||
|
||||
/** @return BackupAdapterSchemaInterface[] */
|
||||
public function getAll(): array
|
||||
{
|
||||
return $this->schemas;
|
||||
}
|
||||
|
||||
public function get(string $id): ?BackupAdapterSchemaInterface
|
||||
{
|
||||
return array_get($this->schemas, $id);
|
||||
}
|
||||
|
||||
public function register(BackupAdapterSchemaInterface $schema): void
|
||||
{
|
||||
if (array_key_exists($schema->getId(), $this->schemas)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->schemas[$schema->getId()] = $schema;
|
||||
}
|
||||
|
||||
/** @return array<string, string> */
|
||||
public function getMappings(): array
|
||||
{
|
||||
return collect($this->schemas)->mapWithKeys(fn ($schema) => [$schema->getId() => $schema->getName()])->all();
|
||||
}
|
||||
}
|
||||
14
app/Extensions/BackupAdapter/Schemas/BackupAdapterSchema.php
Normal file
14
app/Extensions/BackupAdapter/Schemas/BackupAdapterSchema.php
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace App\Extensions\BackupAdapter\Schemas;
|
||||
|
||||
use App\Extensions\BackupAdapter\BackupAdapterSchemaInterface;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
abstract class BackupAdapterSchema implements BackupAdapterSchemaInterface
|
||||
{
|
||||
public function getName(): string
|
||||
{
|
||||
return Str::title($this->getId());
|
||||
}
|
||||
}
|
||||
207
app/Extensions/BackupAdapter/Schemas/S3BackupSchema.php
Normal file
207
app/Extensions/BackupAdapter/Schemas/S3BackupSchema.php
Normal file
@@ -0,0 +1,207 @@
|
||||
<?php
|
||||
|
||||
namespace App\Extensions\BackupAdapter\Schemas;
|
||||
|
||||
use App\Enums\TablerIcon;
|
||||
use App\Http\Controllers\Api\Remote\Backups\BackupRemoteUploadController;
|
||||
use App\Models\Backup;
|
||||
use App\Models\BackupHost;
|
||||
use App\Models\User;
|
||||
use App\Repositories\Daemon\DaemonBackupRepository;
|
||||
use Aws\S3\S3Client;
|
||||
use Carbon\CarbonImmutable;
|
||||
use Exception;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Toggle;
|
||||
use Filament\Schemas\Components\Component;
|
||||
use Filament\Schemas\Components\StateCasts\BooleanStateCast;
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
final class S3BackupSchema extends BackupAdapterSchema
|
||||
{
|
||||
public function __construct(private readonly DaemonBackupRepository $repository) {}
|
||||
|
||||
private function createClient(BackupHost $backupHost): S3Client
|
||||
{
|
||||
$config = $backupHost->configuration;
|
||||
$config['version'] = 'latest';
|
||||
|
||||
if (!empty($config['key']) && !empty($config['secret'])) {
|
||||
$config['credentials'] = Arr::only($config, ['key', 'secret', 'token']);
|
||||
}
|
||||
|
||||
return new S3Client($config);
|
||||
}
|
||||
|
||||
public function getId(): string
|
||||
{
|
||||
return 's3';
|
||||
}
|
||||
|
||||
public function createBackup(Backup $backup): void
|
||||
{
|
||||
$this->repository->setServer($backup->server)->create($backup);
|
||||
}
|
||||
|
||||
public function deleteBackup(Backup $backup): void
|
||||
{
|
||||
$client = $this->createClient($backup->backupHost);
|
||||
|
||||
$client->deleteObject([
|
||||
'Bucket' => $backup->backupHost->configuration['bucket'],
|
||||
'Key' => "{$backup->server->uuid}/$backup->uuid.tar.gz",
|
||||
]);
|
||||
}
|
||||
|
||||
public function getDownloadLink(Backup $backup, User $user): string
|
||||
{
|
||||
$client = $this->createClient($backup->backupHost);
|
||||
|
||||
$request = $client->createPresignedRequest(
|
||||
$client->getCommand('GetObject', [
|
||||
'Bucket' => $backup->backupHost->configuration['bucket'],
|
||||
'Key' => "{$backup->server->uuid}/$backup->uuid.tar.gz",
|
||||
'ContentType' => 'application/x-gzip',
|
||||
]),
|
||||
CarbonImmutable::now()->addMinutes(5)
|
||||
);
|
||||
|
||||
return $request->getUri()->__toString();
|
||||
}
|
||||
|
||||
/** @return Component[] */
|
||||
public function getConfigurationForm(): array
|
||||
{
|
||||
return [
|
||||
TextInput::make('configuration.region')
|
||||
->label(trans('admin/setting.backup.s3.default_region'))
|
||||
->required(),
|
||||
TextInput::make('configuration.key')
|
||||
->label(trans('admin/setting.backup.s3.access_key'))
|
||||
->required(),
|
||||
TextInput::make('configuration.secret')
|
||||
->label(trans('admin/setting.backup.s3.secret_key'))
|
||||
->required(),
|
||||
TextInput::make('configuration.bucket')
|
||||
->label(trans('admin/setting.backup.s3.bucket'))
|
||||
->required(),
|
||||
TextInput::make('configuration.endpoint')
|
||||
->label(trans('admin/setting.backup.s3.endpoint'))
|
||||
->required(),
|
||||
Toggle::make('configuration.use_path_style_endpoint')
|
||||
->label(trans('admin/setting.backup.s3.use_path_style_endpoint'))
|
||||
->inline(false)
|
||||
->onIcon(TablerIcon::Check)
|
||||
->offIcon(TablerIcon::X)
|
||||
->onColor('success')
|
||||
->offColor('danger')
|
||||
->live()
|
||||
->stateCast(new BooleanStateCast(false)),
|
||||
];
|
||||
}
|
||||
|
||||
/** @return array{parts: string[], part_size: int} */
|
||||
public function getUploadParts(Backup $backup, int $size): array
|
||||
{
|
||||
$expires = CarbonImmutable::now()->addMinutes(config('backups.presigned_url_lifespan', 60));
|
||||
|
||||
// Params for generating the presigned urls
|
||||
$params = [
|
||||
'Bucket' => $backup->backupHost->configuration['bucket'],
|
||||
'Key' => "{$backup->server->uuid}/$backup->uuid.tar.gz",
|
||||
'ContentType' => 'application/x-gzip',
|
||||
];
|
||||
|
||||
$storageClass = $backup->backupHost->configuration['storage_class'];
|
||||
if (!is_null($storageClass)) {
|
||||
$params['StorageClass'] = $storageClass;
|
||||
}
|
||||
|
||||
$client = $this->createClient($backup->backupHost);
|
||||
|
||||
// Execute the CreateMultipartUpload request
|
||||
$result = $client->execute($client->getCommand('CreateMultipartUpload', $params));
|
||||
|
||||
// Get the UploadId from the CreateMultipartUpload request, this is needed to create
|
||||
// the other presigned urls.
|
||||
$params['UploadId'] = $result->get('UploadId');
|
||||
|
||||
// Retrieve configured part size
|
||||
$maxPartSize = config('backups.max_part_size', BackupRemoteUploadController::DEFAULT_MAX_PART_SIZE);
|
||||
if ($maxPartSize <= 0) {
|
||||
$maxPartSize = BackupRemoteUploadController::DEFAULT_MAX_PART_SIZE;
|
||||
}
|
||||
|
||||
// Create as many UploadPart presigned urls as needed
|
||||
$parts = [];
|
||||
for ($i = 0; $i < ($size / $maxPartSize); $i++) {
|
||||
$parts[] = $client->createPresignedRequest(
|
||||
$client->getCommand('UploadPart', array_merge($params, ['PartNumber' => $i + 1])),
|
||||
$expires
|
||||
)->getUri()->__toString();
|
||||
}
|
||||
|
||||
// Set the upload_id on the backup in the database.
|
||||
$backup->update(['upload_id' => $params['UploadId']]);
|
||||
|
||||
return [
|
||||
'parts' => $parts,
|
||||
'part_size' => $maxPartSize,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks a multipart upload in a given S3-compatible instance as failed or successful for the given backup.
|
||||
*
|
||||
* @param ?array<array{int, etag: string, part_number: string}> $parts
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function completeMultipartUpload(Backup $backup, bool $successful, ?array $parts): void
|
||||
{
|
||||
// This should never really happen, but if it does don't let us fall victim to Amazon's
|
||||
// wildly fun error messaging. Just stop the process right here.
|
||||
if (empty($backup->upload_id)) {
|
||||
// A failed backup doesn't need to error here, this can happen if the backup encounters
|
||||
// an error before we even start the upload. AWS gives you tooling to clear these failed
|
||||
// multipart uploads as needed too.
|
||||
if (!$successful) {
|
||||
return;
|
||||
}
|
||||
|
||||
throw new Exception('Cannot complete backup request: no upload_id present on model.');
|
||||
}
|
||||
|
||||
$params = [
|
||||
'Bucket' => $backup->backupHost->configuration['bucket'],
|
||||
'Key' => "{$backup->server->uuid}/$backup->uuid.tar.gz",
|
||||
'UploadId' => $backup->upload_id,
|
||||
];
|
||||
|
||||
$client = $this->createClient($backup->backupHost);
|
||||
|
||||
if (!$successful) {
|
||||
$client->execute($client->getCommand('AbortMultipartUpload', $params));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise send a CompleteMultipartUpload request.
|
||||
$params['MultipartUpload'] = [
|
||||
'Parts' => [],
|
||||
];
|
||||
|
||||
if (is_null($parts)) {
|
||||
$params['MultipartUpload']['Parts'] = $client->execute($client->getCommand('ListParts', $params))['Parts'];
|
||||
} else {
|
||||
foreach ($parts as $part) {
|
||||
$params['MultipartUpload']['Parts'][] = [
|
||||
'ETag' => $part['etag'],
|
||||
'PartNumber' => $part['part_number'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$client->execute($client->getCommand('CompleteMultipartUpload', $params));
|
||||
}
|
||||
}
|
||||
64
app/Extensions/BackupAdapter/Schemas/WingsBackupSchema.php
Normal file
64
app/Extensions/BackupAdapter/Schemas/WingsBackupSchema.php
Normal file
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace App\Extensions\BackupAdapter\Schemas;
|
||||
|
||||
use App\Models\Backup;
|
||||
use App\Models\User;
|
||||
use App\Repositories\Daemon\DaemonBackupRepository;
|
||||
use App\Services\Nodes\NodeJWTService;
|
||||
use Carbon\CarbonImmutable;
|
||||
use Exception;
|
||||
use Filament\Infolists\Components\TextEntry;
|
||||
use Filament\Schemas\Components\Component;
|
||||
use Illuminate\Http\Response;
|
||||
|
||||
final class WingsBackupSchema extends BackupAdapterSchema
|
||||
{
|
||||
public function __construct(private readonly DaemonBackupRepository $repository, private readonly NodeJWTService $jwtService) {}
|
||||
|
||||
public function getId(): string
|
||||
{
|
||||
return 'wings';
|
||||
}
|
||||
|
||||
public function createBackup(Backup $backup): void
|
||||
{
|
||||
$this->repository->setServer($backup->server)->create($backup);
|
||||
}
|
||||
|
||||
/** @throws Exception */
|
||||
public function deleteBackup(Backup $backup): void
|
||||
{
|
||||
try {
|
||||
$this->repository->setServer($backup->server)->delete($backup);
|
||||
} catch (Exception $exception) {
|
||||
// Don't fail the request if the Daemon responds with a 404, just assume the backup
|
||||
// doesn't actually exist and remove its reference from the Panel as well.
|
||||
if ($exception->getCode() !== Response::HTTP_NOT_FOUND) {
|
||||
throw $exception;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getDownloadLink(Backup $backup, User $user): string
|
||||
{
|
||||
$token = $this->jwtService
|
||||
->setExpiresAt(CarbonImmutable::now()->addMinutes(15))
|
||||
->setUser($user)
|
||||
->setClaims([
|
||||
'backup_uuid' => $backup->uuid,
|
||||
'server_uuid' => $backup->server->uuid,
|
||||
])
|
||||
->handle($backup->server->node, $user->id . $backup->server->uuid);
|
||||
|
||||
return $backup->server->node->getConnectionAddress() . '/download/backup?token=' . $token->toString();
|
||||
}
|
||||
|
||||
/** @return Component[] */
|
||||
public function getConfigurationForm(): array
|
||||
{
|
||||
return [
|
||||
TextEntry::make(trans('admin/backuphost.no_configuration')),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,177 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Extensions\Backups;
|
||||
|
||||
use App\Extensions\Filesystem\S3Filesystem;
|
||||
use Aws\S3\S3Client;
|
||||
use Closure;
|
||||
use Illuminate\Foundation\Application;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Str;
|
||||
use InvalidArgumentException;
|
||||
use League\Flysystem\FilesystemAdapter;
|
||||
use League\Flysystem\InMemory\InMemoryFilesystemAdapter;
|
||||
use Webmozart\Assert\Assert;
|
||||
|
||||
class BackupManager
|
||||
{
|
||||
/**
|
||||
* The array of resolved backup drivers.
|
||||
*
|
||||
* @var array<string, FilesystemAdapter>
|
||||
*/
|
||||
protected array $adapters = [];
|
||||
|
||||
/**
|
||||
* The registered custom driver creators.
|
||||
*
|
||||
* @var array<string, callable>
|
||||
*/
|
||||
protected array $customCreators;
|
||||
|
||||
public function __construct(protected Application $app) {}
|
||||
|
||||
/**
|
||||
* Returns a backup adapter instance.
|
||||
*/
|
||||
public function adapter(?string $name = null): FilesystemAdapter
|
||||
{
|
||||
return $this->get($name ?: $this->getDefaultAdapter());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the given backup adapter instance.
|
||||
*/
|
||||
public function set(string $name, FilesystemAdapter $disk): self
|
||||
{
|
||||
$this->adapters[$name] = $disk;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a backup adapter.
|
||||
*/
|
||||
protected function get(string $name): FilesystemAdapter
|
||||
{
|
||||
return $this->adapters[$name] = $this->resolve($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the given backup disk.
|
||||
*/
|
||||
protected function resolve(string $name): FilesystemAdapter
|
||||
{
|
||||
$config = $this->getConfig($name);
|
||||
|
||||
if (empty($config['adapter'])) {
|
||||
throw new InvalidArgumentException("Backup disk [$name] does not have a configured adapter.");
|
||||
}
|
||||
|
||||
$adapter = $config['adapter'];
|
||||
|
||||
if (isset($this->customCreators[$name])) {
|
||||
return $this->callCustomCreator($config);
|
||||
}
|
||||
|
||||
$adapterMethod = 'create' . Str::studly($adapter) . 'Adapter';
|
||||
if (method_exists($this, $adapterMethod)) {
|
||||
$instance = $this->{$adapterMethod}($config);
|
||||
|
||||
Assert::isInstanceOf($instance, FilesystemAdapter::class);
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException("Adapter [$adapter] is not supported.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls a custom creator for a given adapter type.
|
||||
*
|
||||
* @param array{adapter: string} $config
|
||||
*/
|
||||
protected function callCustomCreator(array $config): mixed
|
||||
{
|
||||
return $this->customCreators[$config['adapter']]($this->app, $config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new daemon adapter.
|
||||
*
|
||||
* @param array<string, string> $config
|
||||
*/
|
||||
public function createWingsAdapter(array $config): FilesystemAdapter
|
||||
{
|
||||
return new InMemoryFilesystemAdapter();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new S3 adapter.
|
||||
*
|
||||
* @param array<string, string> $config
|
||||
*/
|
||||
public function createS3Adapter(array $config): FilesystemAdapter
|
||||
{
|
||||
$config['version'] = 'latest';
|
||||
|
||||
if (!empty($config['key']) && !empty($config['secret'])) {
|
||||
$config['credentials'] = Arr::only($config, ['key', 'secret', 'token']);
|
||||
}
|
||||
|
||||
$client = new S3Client($config);
|
||||
|
||||
return new S3Filesystem($client, $config['bucket'], $config['prefix'] ?? '', $config['options'] ?? []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the configuration associated with a given backup type.
|
||||
*
|
||||
* @return array<mixed>
|
||||
*/
|
||||
protected function getConfig(string $name): array
|
||||
{
|
||||
return config("backups.disks.$name") ?: [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default backup driver name.
|
||||
*/
|
||||
public function getDefaultAdapter(): string
|
||||
{
|
||||
return config('backups.default');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the default session driver name.
|
||||
*/
|
||||
public function setDefaultAdapter(string $name): void
|
||||
{
|
||||
config()->set('backups.default', $name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unset the given adapter instances.
|
||||
*
|
||||
* @param string|string[] $adapter
|
||||
*/
|
||||
public function forget(array|string $adapter): self
|
||||
{
|
||||
$adapters = &$this->adapters;
|
||||
foreach ((array) $adapter as $adapterName) {
|
||||
unset($adapters[$adapterName]);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a custom adapter creator closure.
|
||||
*/
|
||||
public function extend(string $adapter, Closure $callback): self
|
||||
{
|
||||
$this->customCreators[$adapter] = $callback;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Extensions\Captcha\Schemas;
|
||||
|
||||
use BackedEnum;
|
||||
use Filament\Schemas\Components\Component;
|
||||
|
||||
interface CaptchaSchemaInterface
|
||||
@@ -24,7 +25,7 @@ interface CaptchaSchemaInterface
|
||||
*/
|
||||
public function getSettingsForm(): array;
|
||||
|
||||
public function getIcon(): ?string;
|
||||
public function getIcon(): null|string|BackedEnum;
|
||||
|
||||
public function validateResponse(?string $captchaResponse = null): void;
|
||||
}
|
||||
|
||||
@@ -2,8 +2,10 @@
|
||||
|
||||
namespace App\Extensions\Captcha\Schemas\Turnstile;
|
||||
|
||||
use App\Enums\TablerIcon;
|
||||
use App\Extensions\Captcha\Schemas\BaseSchema;
|
||||
use App\Extensions\Captcha\Schemas\CaptchaSchemaInterface;
|
||||
use BackedEnum;
|
||||
use Exception;
|
||||
use Filament\Forms\Components\Toggle;
|
||||
use Filament\Infolists\Components\TextEntry;
|
||||
@@ -49,8 +51,8 @@ class TurnstileSchema extends BaseSchema implements CaptchaSchemaInterface
|
||||
->label(trans('admin/setting.captcha.verify'))
|
||||
->columnSpan(2)
|
||||
->inline(false)
|
||||
->onIcon('tabler-check')
|
||||
->offIcon('tabler-x')
|
||||
->onIcon(TablerIcon::Check)
|
||||
->offIcon(TablerIcon::X)
|
||||
->onColor('success')
|
||||
->offColor('danger')
|
||||
->default(env('CAPTCHA_TURNSTILE_VERIFY_DOMAIN', true)),
|
||||
@@ -61,9 +63,9 @@ class TurnstileSchema extends BaseSchema implements CaptchaSchemaInterface
|
||||
]);
|
||||
}
|
||||
|
||||
public function getIcon(): ?string
|
||||
public function getIcon(): BackedEnum
|
||||
{
|
||||
return 'tabler-brand-cloudflare';
|
||||
return TablerIcon::BrandCloudflare;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Extensions\Features\Schemas;
|
||||
|
||||
use App\Enums\SubuserPermission;
|
||||
use App\Enums\TablerIcon;
|
||||
use App\Extensions\Features\FeatureSchemaInterface;
|
||||
use App\Facades\Activity;
|
||||
use App\Models\Server;
|
||||
@@ -73,7 +74,7 @@ class GSLTokenSchema implements FeatureSchemaInterface
|
||||
}
|
||||
},
|
||||
])
|
||||
->hintIcon('tabler-code', fn () => implode('|', $serverVariable->variable->rules))
|
||||
->hintIcon(TablerIcon::Code, fn () => implode('|', $serverVariable->variable->rules))
|
||||
->label(fn () => $serverVariable->variable->name)
|
||||
->prefix(fn () => '{{' . $serverVariable->variable->env_variable . '}}')
|
||||
->helperText(fn () => empty($serverVariable->variable->description) ? '—' : $serverVariable->variable->description),
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Extensions\Features\Schemas;
|
||||
|
||||
use App\Enums\TablerIcon;
|
||||
use App\Extensions\Features\FeatureSchemaInterface;
|
||||
use Filament\Actions\Action;
|
||||
use Illuminate\Support\Facades\Blade;
|
||||
@@ -31,7 +32,7 @@ class PIDLimitSchema implements FeatureSchemaInterface
|
||||
{
|
||||
return Action::make($this->getId())
|
||||
->requiresConfirmation()
|
||||
->icon('tabler-alert-triangle')
|
||||
->icon(TablerIcon::AlertTriangle)
|
||||
->modalHeading(fn () => user()?->isAdmin() ? 'Memory or process limit reached...' : 'Possible resource limit reached...')
|
||||
->modalDescription(new HtmlString(Blade::render(
|
||||
user()?->isAdmin() ? <<<'HTML'
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Extensions\Filesystem;
|
||||
|
||||
use Aws\S3\S3ClientInterface;
|
||||
use League\Flysystem\AwsS3V3\AwsS3V3Adapter;
|
||||
|
||||
class S3Filesystem extends AwsS3V3Adapter
|
||||
{
|
||||
/**
|
||||
* @param array<mixed> $options
|
||||
*/
|
||||
public function __construct(
|
||||
private S3ClientInterface $client,
|
||||
private string $bucket,
|
||||
string $prefix = '',
|
||||
array $options = [],
|
||||
) {
|
||||
parent::__construct(
|
||||
$client,
|
||||
$bucket,
|
||||
$prefix,
|
||||
null,
|
||||
null,
|
||||
$options,
|
||||
);
|
||||
}
|
||||
|
||||
public function getClient(): S3ClientInterface
|
||||
{
|
||||
return $this->client;
|
||||
}
|
||||
|
||||
public function getBucket(): string
|
||||
{
|
||||
return $this->bucket;
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,11 @@
|
||||
|
||||
namespace App\Extensions\OAuth;
|
||||
|
||||
use App\Models\User;
|
||||
use BackedEnum;
|
||||
use Filament\Schemas\Components\Component;
|
||||
use Filament\Schemas\Components\Wizard\Step;
|
||||
use Laravel\Socialite\Contracts\User as OAuthUser;
|
||||
|
||||
interface OAuthSchemaInterface
|
||||
{
|
||||
@@ -27,13 +30,13 @@ interface OAuthSchemaInterface
|
||||
/** @return Step[] */
|
||||
public function getSetupSteps(): array;
|
||||
|
||||
public function getIcon(): ?string;
|
||||
public function getIcon(): null|string|BackedEnum;
|
||||
|
||||
public function getHexColor(): ?string;
|
||||
|
||||
public function isEnabled(): bool;
|
||||
|
||||
public function shouldCreateMissingUsers(): bool;
|
||||
public function shouldCreateMissingUser(OAuthUser $user): bool;
|
||||
|
||||
public function shouldLinkMissingUsers(): bool;
|
||||
public function shouldLinkMissingUser(User $user, OAuthUser $oauthUser): bool;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
namespace App\Extensions\OAuth\Schemas;
|
||||
|
||||
use App\Enums\TablerIcon;
|
||||
use BackedEnum;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Infolists\Components\TextEntry;
|
||||
use Filament\Schemas\Components\Wizard\Step;
|
||||
@@ -33,9 +35,9 @@ final class BitbucketSchema extends OAuthSchema
|
||||
], parent::getSetupSteps());
|
||||
}
|
||||
|
||||
public function getIcon(): string
|
||||
public function getIcon(): BackedEnum
|
||||
{
|
||||
return 'tabler-brand-bitbucket-f';
|
||||
return TablerIcon::BrandBitbucketFilled;
|
||||
}
|
||||
|
||||
public function getHexColor(): string
|
||||
|
||||
@@ -2,13 +2,15 @@
|
||||
|
||||
namespace App\Extensions\OAuth\Schemas;
|
||||
|
||||
use BackedEnum;
|
||||
|
||||
final class CommonSchema extends OAuthSchema
|
||||
{
|
||||
public function __construct(
|
||||
private readonly string $id,
|
||||
private readonly ?string $name = null,
|
||||
private readonly ?string $configName = null,
|
||||
private readonly ?string $icon = null,
|
||||
private readonly null|string|BackedEnum $icon = null,
|
||||
private readonly ?string $hexColor = null,
|
||||
) {}
|
||||
|
||||
@@ -27,7 +29,7 @@ final class CommonSchema extends OAuthSchema
|
||||
return $this->configName ?? parent::getConfigKey();
|
||||
}
|
||||
|
||||
public function getIcon(): ?string
|
||||
public function getIcon(): null|string|BackedEnum
|
||||
{
|
||||
return $this->icon;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
namespace App\Extensions\OAuth\Schemas;
|
||||
|
||||
use App\Enums\TablerIcon;
|
||||
use BackedEnum;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Infolists\Components\TextEntry;
|
||||
use Filament\Schemas\Components\Wizard\Step;
|
||||
@@ -42,9 +44,9 @@ final class DiscordSchema extends OAuthSchema
|
||||
], parent::getSetupSteps());
|
||||
}
|
||||
|
||||
public function getIcon(): string
|
||||
public function getIcon(): BackedEnum
|
||||
{
|
||||
return 'tabler-brand-discord-f';
|
||||
return TablerIcon::BrandDiscordFilled;
|
||||
}
|
||||
|
||||
public function getHexColor(): string
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
namespace App\Extensions\OAuth\Schemas;
|
||||
|
||||
use App\Enums\TablerIcon;
|
||||
use BackedEnum;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Infolists\Components\TextEntry;
|
||||
use Filament\Schemas\Components\Wizard\Step;
|
||||
@@ -36,9 +38,9 @@ final class FacebookSchema extends OAuthSchema
|
||||
], parent::getSetupSteps());
|
||||
}
|
||||
|
||||
public function getIcon(): string
|
||||
public function getIcon(): BackedEnum
|
||||
{
|
||||
return 'tabler-brand-facebook-f';
|
||||
return TablerIcon::BrandFacebookFilled;
|
||||
}
|
||||
|
||||
public function getHexColor(): string
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
namespace App\Extensions\OAuth\Schemas;
|
||||
|
||||
use App\Enums\TablerIcon;
|
||||
use BackedEnum;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Infolists\Components\TextEntry;
|
||||
use Filament\Schemas\Components\Wizard\Step;
|
||||
@@ -18,11 +20,11 @@ final class GithubSchema extends OAuthSchema
|
||||
public function getSetupSteps(): array
|
||||
{
|
||||
return array_merge([
|
||||
Step::make('Register new Github OAuth App')
|
||||
Step::make('Register new GitHub OAuth App')
|
||||
->schema([
|
||||
TextEntry::make('create_application')
|
||||
->hiddenLabel()
|
||||
->state(new HtmlString(Blade::render('<p>Visit the <x-filament::link href="https://github.com/settings/developers" target="_blank">Github Developer Dashboard</x-filament::link>, go to <b>OAuth Apps</b> and click on <b>New OAuth App</b>.</p><p>Enter an <b>Application name</b> (e.g. your panel name), set <b>Homepage URL</b> to your panel url and enter the below url as <b>Authorization callback URL</b>.</p>'))),
|
||||
->state(new HtmlString(Blade::render('<p>Visit the <x-filament::link href="https://github.com/settings/developers" target="_blank">GitHub Developer Dashboard</x-filament::link>, go to <b>OAuth Apps</b> and click on <b>New OAuth App</b>.</p><p>Enter an <b>Application name</b> (e.g. your panel name), set <b>Homepage URL</b> to your panel url and enter the below url as <b>Authorization callback URL</b>.</p>'))),
|
||||
TextInput::make('_noenv_callback')
|
||||
->label('Authorization callback URL')
|
||||
->dehydrated()
|
||||
@@ -42,9 +44,9 @@ final class GithubSchema extends OAuthSchema
|
||||
], parent::getSetupSteps());
|
||||
}
|
||||
|
||||
public function getIcon(): string
|
||||
public function getIcon(): BackedEnum
|
||||
{
|
||||
return 'tabler-brand-github-f';
|
||||
return TablerIcon::BrandGithubFilled;
|
||||
}
|
||||
|
||||
public function getHexColor(): string
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
namespace App\Extensions\OAuth\Schemas;
|
||||
|
||||
use App\Enums\TablerIcon;
|
||||
use BackedEnum;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Infolists\Components\TextEntry;
|
||||
use Filament\Schemas\Components\Wizard\Step;
|
||||
@@ -53,9 +55,9 @@ final class GitlabSchema extends OAuthSchema
|
||||
], parent::getSetupSteps());
|
||||
}
|
||||
|
||||
public function getIcon(): string
|
||||
public function getIcon(): BackedEnum
|
||||
{
|
||||
return 'tabler-brand-gitlab';
|
||||
return TablerIcon::BrandGitlab;
|
||||
}
|
||||
|
||||
public function getHexColor(): string
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
namespace App\Extensions\OAuth\Schemas;
|
||||
|
||||
use App\Enums\TablerIcon;
|
||||
use BackedEnum;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Infolists\Components\TextEntry;
|
||||
use Filament\Schemas\Components\Wizard\Step;
|
||||
@@ -42,9 +44,9 @@ final class GoogleSchema extends OAuthSchema
|
||||
], parent::getSetupSteps());
|
||||
}
|
||||
|
||||
public function getIcon(): string
|
||||
public function getIcon(): BackedEnum
|
||||
{
|
||||
return 'tabler-brand-google-f';
|
||||
return TablerIcon::BrandGoogleFilled;
|
||||
}
|
||||
|
||||
public function getHexColor(): string
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
namespace App\Extensions\OAuth\Schemas;
|
||||
|
||||
use App\Enums\TablerIcon;
|
||||
use BackedEnum;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Infolists\Components\TextEntry;
|
||||
use Filament\Schemas\Components\Wizard\Step;
|
||||
@@ -33,9 +35,9 @@ final class LinkedinSchema extends OAuthSchema
|
||||
], parent::getSetupSteps());
|
||||
}
|
||||
|
||||
public function getIcon(): string
|
||||
public function getIcon(): BackedEnum
|
||||
{
|
||||
return 'tabler-brand-linkedin-f';
|
||||
return TablerIcon::BrandLinkedinFilled;
|
||||
}
|
||||
|
||||
public function getHexColor(): string
|
||||
|
||||
@@ -2,13 +2,17 @@
|
||||
|
||||
namespace App\Extensions\OAuth\Schemas;
|
||||
|
||||
use App\Enums\TablerIcon;
|
||||
use App\Extensions\OAuth\OAuthSchemaInterface;
|
||||
use App\Models\User;
|
||||
use BackedEnum;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Toggle;
|
||||
use Filament\Schemas\Components\Component;
|
||||
use Filament\Schemas\Components\Utilities\Set;
|
||||
use Filament\Schemas\Components\Wizard\Step;
|
||||
use Illuminate\Support\Str;
|
||||
use Laravel\Socialite\Contracts\User as OAuthUser;
|
||||
|
||||
abstract class OAuthSchema implements OAuthSchemaInterface
|
||||
{
|
||||
@@ -59,8 +63,8 @@ abstract class OAuthSchema implements OAuthSchemaInterface
|
||||
->label(trans('admin/setting.oauth.create_missing_users'))
|
||||
->columnSpan(2)
|
||||
->inline(false)
|
||||
->onIcon('tabler-check')
|
||||
->offIcon('tabler-x')
|
||||
->onIcon(TablerIcon::Check)
|
||||
->offIcon(TablerIcon::X)
|
||||
->onColor('success')
|
||||
->offColor('danger')
|
||||
->formatStateUsing(fn ($state) => (bool) $state)
|
||||
@@ -70,8 +74,8 @@ abstract class OAuthSchema implements OAuthSchemaInterface
|
||||
->label(trans('admin/setting.oauth.link_missing_users'))
|
||||
->columnSpan(2)
|
||||
->inline(false)
|
||||
->onIcon('tabler-check')
|
||||
->offIcon('tabler-x')
|
||||
->onIcon(TablerIcon::Check)
|
||||
->offIcon(TablerIcon::X)
|
||||
->onColor('success')
|
||||
->offColor('danger')
|
||||
->formatStateUsing(fn ($state) => (bool) $state)
|
||||
@@ -104,7 +108,7 @@ abstract class OAuthSchema implements OAuthSchemaInterface
|
||||
return "OAUTH_{$id}_ENABLED";
|
||||
}
|
||||
|
||||
public function getIcon(): ?string
|
||||
public function getIcon(): null|string|BackedEnum
|
||||
{
|
||||
return null;
|
||||
}
|
||||
@@ -116,19 +120,17 @@ abstract class OAuthSchema implements OAuthSchemaInterface
|
||||
|
||||
public function isEnabled(): bool
|
||||
{
|
||||
$id = Str::upper($this->getId());
|
||||
|
||||
return env("OAUTH_{$id}_ENABLED", false);
|
||||
return env($this->getConfigKey(), false);
|
||||
}
|
||||
|
||||
public function shouldCreateMissingUsers(): bool
|
||||
public function shouldCreateMissingUser(OAuthUser $user): bool
|
||||
{
|
||||
$id = Str::upper($this->getId());
|
||||
|
||||
return env("OAUTH_{$id}_SHOULD_CREATE_MISSING_USERS", false);
|
||||
}
|
||||
|
||||
public function shouldLinkMissingUsers(): bool
|
||||
public function shouldLinkMissingUser(User $user, OAuthUser $oauthUser): bool
|
||||
{
|
||||
$id = Str::upper($this->getId());
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
namespace App\Extensions\OAuth\Schemas;
|
||||
|
||||
use App\Enums\TablerIcon;
|
||||
use BackedEnum;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Infolists\Components\TextEntry;
|
||||
use Filament\Schemas\Components\Wizard\Step;
|
||||
@@ -33,9 +35,9 @@ final class SlackSchema extends OAuthSchema
|
||||
], parent::getSetupSteps());
|
||||
}
|
||||
|
||||
public function getIcon(): string
|
||||
public function getIcon(): BackedEnum
|
||||
{
|
||||
return 'tabler-brand-slack';
|
||||
return TablerIcon::BrandSlack;
|
||||
}
|
||||
|
||||
public function getHexColor(): string
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
namespace App\Extensions\OAuth\Schemas;
|
||||
|
||||
use App\Enums\TablerIcon;
|
||||
use BackedEnum;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Infolists\Components\TextEntry;
|
||||
use Filament\Schemas\Components\Wizard\Step;
|
||||
@@ -59,9 +61,9 @@ final class SteamSchema extends OAuthSchema
|
||||
], parent::getSetupSteps());
|
||||
}
|
||||
|
||||
public function getIcon(): string
|
||||
public function getIcon(): BackedEnum
|
||||
{
|
||||
return 'tabler-brand-steam-f';
|
||||
return TablerIcon::BrandSteamFilled;
|
||||
}
|
||||
|
||||
public function getHexColor(): string
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
namespace App\Extensions\OAuth\Schemas;
|
||||
|
||||
use App\Enums\TablerIcon;
|
||||
use BackedEnum;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Infolists\Components\TextEntry;
|
||||
use Filament\Schemas\Components\Wizard\Step;
|
||||
@@ -42,9 +44,9 @@ final class XSchema extends OAuthSchema
|
||||
], parent::getSetupSteps());
|
||||
}
|
||||
|
||||
public function getIcon(): string
|
||||
public function getIcon(): BackedEnum
|
||||
{
|
||||
return 'tabler-brand-x';
|
||||
return TablerIcon::BrandX;
|
||||
}
|
||||
|
||||
public function getHexColor(): string
|
||||
|
||||
@@ -8,7 +8,7 @@ use App\Services\Backups\InitiateBackupService;
|
||||
|
||||
final class CreateBackupSchema extends TaskSchema
|
||||
{
|
||||
public function __construct(private InitiateBackupService $backupService) {}
|
||||
public function __construct(private InitiateBackupService $initiateService) {}
|
||||
|
||||
public function getId(): string
|
||||
{
|
||||
@@ -17,7 +17,7 @@ final class CreateBackupSchema extends TaskSchema
|
||||
|
||||
public function runTask(Task $task): void
|
||||
{
|
||||
$this->backupService->setIgnoredFiles(explode(PHP_EOL, $task->payload))->handle($task->server, null, true);
|
||||
$this->initiateService->setIgnoredFiles(explode(PHP_EOL, $task->payload))->handle($task->server, null, true);
|
||||
}
|
||||
|
||||
public function canCreate(Schedule $schedule): bool
|
||||
|
||||
@@ -2,12 +2,14 @@
|
||||
|
||||
namespace App\Filament\Admin\Pages;
|
||||
|
||||
use App\Enums\TablerIcon;
|
||||
use App\Services\Helpers\SoftwareVersionService;
|
||||
use BackedEnum;
|
||||
use Filament\Pages\Dashboard as BaseDashboard;
|
||||
|
||||
class Dashboard extends BaseDashboard
|
||||
{
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'tabler-layout-dashboard';
|
||||
protected static string|BackedEnum|null $navigationIcon = TablerIcon::LayoutDashboard;
|
||||
|
||||
private SoftwareVersionService $softwareVersionService;
|
||||
|
||||
|
||||
@@ -2,11 +2,12 @@
|
||||
|
||||
namespace App\Filament\Admin\Pages;
|
||||
|
||||
use App\Enums\TablerIcon;
|
||||
use BackedEnum;
|
||||
use Carbon\Carbon;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Pages\Page;
|
||||
use Filament\Support\Enums\IconSize;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Spatie\Health\Commands\RunHealthChecksCommand;
|
||||
use Spatie\Health\Enums\Status;
|
||||
@@ -14,7 +15,7 @@ use Spatie\Health\ResultStores\ResultStore;
|
||||
|
||||
class Health extends Page
|
||||
{
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'tabler-heart';
|
||||
protected static string|BackedEnum|null $navigationIcon = TablerIcon::Heart;
|
||||
|
||||
protected string $view = 'filament.pages.health';
|
||||
|
||||
@@ -47,9 +48,9 @@ class Health extends Page
|
||||
{
|
||||
return [
|
||||
Action::make('refresh')
|
||||
->label(trans('admin/health.refresh'))
|
||||
->iconButton()->iconSize(IconSize::ExtraLarge)
|
||||
->icon('tabler-refresh')
|
||||
->hiddenLabel()
|
||||
->tooltip(trans('admin/health.refresh'))
|
||||
->icon(TablerIcon::Refresh)
|
||||
->action('refresh'),
|
||||
];
|
||||
}
|
||||
@@ -128,16 +129,16 @@ class Health extends Page
|
||||
return trans('admin/health.checks.failed', ['checks' => implode(', ', $failedNames)]);
|
||||
}
|
||||
|
||||
public static function getNavigationIcon(): string
|
||||
public static function getNavigationIcon(): BackedEnum
|
||||
{
|
||||
// @phpstan-ignore myCustomRules.forbiddenGlobalFunctions
|
||||
$results = app(ResultStore::class)->latestResults();
|
||||
|
||||
if ($results === null) {
|
||||
return 'tabler-heart-question';
|
||||
return TablerIcon::HeartQuestion;
|
||||
}
|
||||
|
||||
return $results->containsFailingCheck() ? 'tabler-heart-exclamation' : 'tabler-heart-check';
|
||||
return $results->containsFailingCheck() ? TablerIcon::HeartExclamation : TablerIcon::HeartCheck;
|
||||
}
|
||||
|
||||
public function backgroundColor(string $str): string
|
||||
@@ -162,14 +163,14 @@ class Health extends Page
|
||||
};
|
||||
}
|
||||
|
||||
public function icon(string $str): string
|
||||
public function icon(string $str): BackedEnum
|
||||
{
|
||||
return match ($str) {
|
||||
Status::ok()->value => 'tabler-circle-check',
|
||||
Status::warning()->value => 'tabler-exclamation-circle',
|
||||
Status::skipped()->value => 'tabler-circle-chevron-right',
|
||||
Status::failed()->value, Status::crashed()->value => 'tabler-circle-x',
|
||||
default => 'tabler-help-circle'
|
||||
Status::ok()->value => TablerIcon::CircleCheck,
|
||||
Status::warning()->value => TablerIcon::ExclamationCircle,
|
||||
Status::skipped()->value => TablerIcon::CircleChevronRight,
|
||||
Status::failed()->value, Status::crashed()->value => TablerIcon::CircleX,
|
||||
default => TablerIcon::HelpCircle
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,24 +2,26 @@
|
||||
|
||||
namespace App\Filament\Admin\Pages;
|
||||
|
||||
use App\Enums\TablerIcon;
|
||||
use Boquizo\FilamentLogViewer\Actions\DeleteAction;
|
||||
use Boquizo\FilamentLogViewer\Actions\DownloadAction;
|
||||
use Boquizo\FilamentLogViewer\Actions\ViewLogAction;
|
||||
use Boquizo\FilamentLogViewer\Pages\ListLogs as BaseListLogs;
|
||||
use Boquizo\FilamentLogViewer\Tables\Columns\LevelColumn;
|
||||
use Boquizo\FilamentLogViewer\Tables\Columns\NameColumn;
|
||||
use Boquizo\FilamentLogViewer\UseCases\ParseDateUseCase;
|
||||
use Boquizo\FilamentLogViewer\Utils\Level;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Support\Enums\IconSize;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Contracts\Support\Htmlable;
|
||||
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
|
||||
public function getHeading(): string|null|Htmlable
|
||||
{
|
||||
return trans('admin/log.navigation.panel_logs');
|
||||
}
|
||||
@@ -28,7 +30,7 @@ class ListLogs extends BaseListLogs
|
||||
{
|
||||
return parent::table($table)
|
||||
->emptyStateHeading(trans('admin/log.empty_table'))
|
||||
->emptyStateIcon('tabler-check')
|
||||
->emptyStateIcon(TablerIcon::Check)
|
||||
->columns([
|
||||
NameColumn::make('date'),
|
||||
LevelColumn::make(Level::ALL)
|
||||
@@ -46,17 +48,21 @@ class ListLogs extends BaseListLogs
|
||||
])
|
||||
->recordActions([
|
||||
ViewLogAction::make()
|
||||
->icon('tabler-file-description')->iconSize(IconSize::Large)->iconButton(),
|
||||
->icon(TablerIcon::FileDescription)->iconButton(),
|
||||
DownloadAction::make()
|
||||
->icon('tabler-file-download')->iconSize(IconSize::Large)->iconButton(),
|
||||
->tooltip(fn ($record) => trans('filament-log-viewer::log.table.actions.download.label', ['log' => ParseDateUseCase::execute($record['date'])]))
|
||||
->icon(TablerIcon::FileDownload)->iconButton(),
|
||||
Action::make('uploadLogs')
|
||||
->hiddenLabel()
|
||||
->icon('tabler-world-upload')->iconSize(IconSize::Large)->iconButton()
|
||||
->tooltip(trans('admin/log.actions.upload_tooltip', ['url' => 'logs.pelican.dev']))
|
||||
->icon(TablerIcon::WorldUpload)
|
||||
->requiresConfirmation()
|
||||
->modalHeading(trans('admin/log.actions.upload_logs'))
|
||||
->modalDescription(fn ($record) => trans('admin/log.actions.upload_logs_description', ['file' => $record['date'], 'url' => 'https://logs.pelican.dev']))
|
||||
->action(function ($record) {
|
||||
$logPath = storage_path('logs/' . $record['date']);
|
||||
$prefix = config('filament-log-viewer.pattern.prefix', 'laravel-');
|
||||
$extension = config('filament-log-viewer.pattern.extension', '.log');
|
||||
$logPath = storage_path('logs/' . $prefix . $record['date'] . $extension);
|
||||
|
||||
if (!file_exists($logPath)) {
|
||||
Notification::make()
|
||||
@@ -73,18 +79,12 @@ class ListLogs extends BaseListLogs
|
||||
$uploadLines = $totalLines <= 1000 ? $lines : array_slice($lines, -1000);
|
||||
$content = implode("\n", $uploadLines);
|
||||
|
||||
$logUrl = 'https://logs.pelican.dev';
|
||||
try {
|
||||
$response = Http::timeout(10)->asMultipart()->post($logUrl, [
|
||||
[
|
||||
'name' => 'c',
|
||||
'contents' => $content,
|
||||
],
|
||||
[
|
||||
'name' => 'e',
|
||||
'contents' => '14d',
|
||||
],
|
||||
]);
|
||||
$response = Http::timeout(10)
|
||||
->asMultipart()
|
||||
->attach('c', $content)
|
||||
->attach('e', '14d')
|
||||
->post('https://logs.pelican.dev');
|
||||
|
||||
if ($response->failed()) {
|
||||
Notification::make()
|
||||
@@ -104,7 +104,7 @@ class ListLogs extends BaseListLogs
|
||||
->body("{$url}")
|
||||
->success()
|
||||
->actions([
|
||||
Action::make('viewLogs')
|
||||
Action::make('exclude_viewLogs')
|
||||
->label(trans('admin/log.actions.view_logs'))
|
||||
->url($url)
|
||||
->openUrlInNewTab(true),
|
||||
@@ -123,7 +123,7 @@ class ListLogs extends BaseListLogs
|
||||
}
|
||||
}),
|
||||
DeleteAction::make()
|
||||
->iconSize(IconSize::Medium)->iconButton(),
|
||||
->icon(TablerIcon::Trash)->iconButton(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,15 +2,18 @@
|
||||
|
||||
namespace App\Filament\Admin\Pages;
|
||||
|
||||
use App\Enums\TablerIcon;
|
||||
use App\Extensions\Avatar\AvatarService;
|
||||
use App\Extensions\Captcha\CaptchaService;
|
||||
use App\Extensions\OAuth\OAuthService;
|
||||
use App\Models\Backup;
|
||||
use App\Notifications\MailTested;
|
||||
use App\Traits\EnvironmentWriterTrait;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use App\Traits\Filament\CanCustomizeTabs;
|
||||
use BackedEnum;
|
||||
use BladeUI\Icons\Exceptions\SvgNotFound;
|
||||
use BladeUI\Icons\Factory as IconFactory;
|
||||
use Exception;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\ActionGroup;
|
||||
@@ -36,7 +39,6 @@ use Filament\Schemas\Components\Utilities\Get;
|
||||
use Filament\Schemas\Components\Utilities\Set;
|
||||
use Filament\Schemas\Contracts\HasSchemas;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Support\Enums\IconSize;
|
||||
use Filament\Support\Enums\Width;
|
||||
use Illuminate\Http\Client\Factory;
|
||||
use Illuminate\Support\Arr;
|
||||
@@ -53,10 +55,11 @@ class Settings extends Page implements HasSchemas
|
||||
CanCustomizeHeaderActions::getHeaderActions insteadof InteractsWithHeaderActions;
|
||||
}
|
||||
use CanCustomizeHeaderWidgets;
|
||||
use CanCustomizeTabs;
|
||||
use EnvironmentWriterTrait;
|
||||
use InteractsWithForms;
|
||||
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'tabler-settings';
|
||||
protected static string|BackedEnum|null $navigationIcon = TablerIcon::Settings;
|
||||
|
||||
protected string $view = 'filament.pages.settings';
|
||||
|
||||
@@ -66,6 +69,8 @@ class Settings extends Page implements HasSchemas
|
||||
|
||||
protected CaptchaService $captchaService;
|
||||
|
||||
protected IconFactory $iconFactory;
|
||||
|
||||
/** @var array<mixed>|null */
|
||||
public ?array $data = [];
|
||||
|
||||
@@ -74,11 +79,12 @@ class Settings extends Page implements HasSchemas
|
||||
$this->form->fill();
|
||||
}
|
||||
|
||||
public function boot(OAuthService $oauthService, AvatarService $avatarService, CaptchaService $captchaService): void
|
||||
public function boot(OAuthService $oauthService, AvatarService $avatarService, CaptchaService $captchaService, IconFactory $iconFactory): void
|
||||
{
|
||||
$this->oauthService = $oauthService;
|
||||
$this->avatarService = $avatarService;
|
||||
$this->captchaService = $captchaService;
|
||||
$this->iconFactory = $iconFactory;
|
||||
}
|
||||
|
||||
public static function canAccess(): bool
|
||||
@@ -96,11 +102,7 @@ class Settings extends Page implements HasSchemas
|
||||
return trans('admin/setting.title');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<Component>
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
/** @return array<Component> */
|
||||
protected function getFormSchema(): array
|
||||
{
|
||||
return [
|
||||
@@ -108,34 +110,44 @@ class Settings extends Page implements HasSchemas
|
||||
->columns()
|
||||
->persistTabInQueryString()
|
||||
->disabled(fn () => !user()?->can('update settings'))
|
||||
->tabs([
|
||||
Tab::make('general')
|
||||
->label(trans('admin/setting.navigation.general'))
|
||||
->icon('tabler-home')
|
||||
->schema($this->generalSettings()),
|
||||
Tab::make('captcha')
|
||||
->label(trans('admin/setting.navigation.captcha'))
|
||||
->icon('tabler-shield')
|
||||
->schema($this->captchaSettings())
|
||||
->columns(1),
|
||||
Tab::make('mail')
|
||||
->label(trans('admin/setting.navigation.mail'))
|
||||
->icon('tabler-mail')
|
||||
->schema($this->mailSettings()),
|
||||
Tab::make('backup')
|
||||
->label(trans('admin/setting.navigation.backup'))
|
||||
->icon('tabler-box')
|
||||
->schema($this->backupSettings()),
|
||||
Tab::make('oauth')
|
||||
->label(trans('admin/setting.navigation.oauth'))
|
||||
->icon('tabler-brand-oauth')
|
||||
->schema($this->oauthSettings())
|
||||
->columns(1),
|
||||
Tab::make('misc')
|
||||
->label(trans('admin/setting.navigation.misc'))
|
||||
->icon('tabler-tool')
|
||||
->schema($this->miscSettings()),
|
||||
]),
|
||||
->tabs($this->getTabs()),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Tab[]
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function getDefaultTabs(): array
|
||||
{
|
||||
return [
|
||||
Tab::make('general')
|
||||
->label(trans('admin/setting.navigation.general'))
|
||||
->icon(TablerIcon::Home)
|
||||
->schema($this->generalSettings()),
|
||||
Tab::make('captcha')
|
||||
->label(trans('admin/setting.navigation.captcha'))
|
||||
->icon(TablerIcon::Shield)
|
||||
->schema($this->captchaSettings())
|
||||
->columns(1),
|
||||
Tab::make('mail')
|
||||
->label(trans('admin/setting.navigation.mail'))
|
||||
->icon(TablerIcon::Mail)
|
||||
->schema($this->mailSettings()),
|
||||
Tab::make('backup')
|
||||
->label(trans('admin/setting.navigation.backup'))
|
||||
->icon(TablerIcon::Box)
|
||||
->schema($this->backupSettings()),
|
||||
Tab::make('oauth')
|
||||
->label(trans('admin/setting.navigation.oauth'))
|
||||
->icon(TablerIcon::BrandOauth)
|
||||
->schema($this->oauthSettings())
|
||||
->columns(1),
|
||||
Tab::make('misc')
|
||||
->label(trans('admin/setting.navigation.misc'))
|
||||
->icon(TablerIcon::Tool)
|
||||
->schema($this->miscSettings()),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -154,12 +166,12 @@ class Settings extends Page implements HasSchemas
|
||||
->schema([
|
||||
TextInput::make('APP_LOGO')
|
||||
->label(trans('admin/setting.general.app_logo'))
|
||||
->hintIcon('tabler-question-mark', trans('admin/setting.general.app_logo_help'))
|
||||
->hintIcon(TablerIcon::QuestionMark, trans('admin/setting.general.app_logo_help'))
|
||||
->default(env('APP_LOGO'))
|
||||
->placeholder('/pelican.svg'),
|
||||
TextInput::make('APP_FAVICON')
|
||||
->label(trans('admin/setting.general.app_favicon'))
|
||||
->hintIcon('tabler-question-mark', trans('admin/setting.general.app_favicon_help'))
|
||||
->hintIcon(TablerIcon::QuestionMark, trans('admin/setting.general.app_favicon_help'))
|
||||
->required()
|
||||
->default(env('APP_FAVICON', '/pelican.ico'))
|
||||
->placeholder('/pelican.ico'),
|
||||
@@ -170,8 +182,8 @@ class Settings extends Page implements HasSchemas
|
||||
Toggle::make('APP_DEBUG')
|
||||
->label(trans('admin/setting.general.debug_mode'))
|
||||
->inline(false)
|
||||
->onIcon('tabler-check')
|
||||
->offIcon('tabler-x')
|
||||
->onIcon(TablerIcon::Check)
|
||||
->offIcon(TablerIcon::X)
|
||||
->onColor('success')
|
||||
->offColor('danger')
|
||||
->stateCast(new BooleanStateCast(false))
|
||||
@@ -188,8 +200,8 @@ class Settings extends Page implements HasSchemas
|
||||
Toggle::make('FILAMENT_UPLOADABLE_AVATARS')
|
||||
->label(trans('admin/setting.general.uploadable_avatars'))
|
||||
->inline(false)
|
||||
->onIcon('tabler-check')
|
||||
->offIcon('tabler-x')
|
||||
->onIcon(TablerIcon::Check)
|
||||
->offIcon(TablerIcon::X)
|
||||
->onColor('success')
|
||||
->offColor('danger')
|
||||
->stateCast(new BooleanStateCast(false))
|
||||
@@ -236,16 +248,16 @@ class Settings extends Page implements HasSchemas
|
||||
->placeholder(trans('admin/setting.general.trusted_proxies_help'))
|
||||
->default(env('TRUSTED_PROXIES', implode(',', Arr::wrap(config('trustedproxy.proxies')))))
|
||||
->hintActions([
|
||||
Action::make('clear')
|
||||
Action::make('hint_clear')
|
||||
->label(trans('admin/setting.general.clear'))
|
||||
->color('danger')
|
||||
->icon('tabler-trash')
|
||||
->icon(TablerIcon::Trash)
|
||||
->requiresConfirmation()
|
||||
->authorize(fn () => user()?->can('update settings'))
|
||||
->action(fn (Set $set) => $set('TRUSTED_PROXIES', [])),
|
||||
Action::make('cloudflare')
|
||||
Action::make('hint_cloudflare')
|
||||
->label(trans('admin/setting.general.set_to_cf'))
|
||||
->icon('tabler-brand-cloudflare')
|
||||
->icon(TablerIcon::BrandCloudflare)
|
||||
->authorize(fn () => user()?->can('update settings'))
|
||||
->action(function (Factory $client, Set $set) {
|
||||
$ips = collect();
|
||||
@@ -256,7 +268,7 @@ class Settings extends Page implements HasSchemas
|
||||
->connectTimeout(3)
|
||||
->get('https://api.cloudflare.com/client/v4/ips');
|
||||
|
||||
if ($response->getStatusCode() === 200) {
|
||||
if ($response->status() === 200) {
|
||||
$result = $response->json('result');
|
||||
foreach (['ipv4_cidrs', 'ipv6_cidrs'] as $value) {
|
||||
$ips->push(...data_get($result, $value));
|
||||
@@ -287,7 +299,7 @@ class Settings extends Page implements HasSchemas
|
||||
|
||||
$formFields[] = Section::make($schema->getName())
|
||||
->columns(5)
|
||||
->icon($schema->getIcon() ?? 'tabler-shield')
|
||||
->icon($schema->getIcon() ?? TablerIcon::Shield)
|
||||
->collapsed(fn () => !$schema->isEnabled())
|
||||
->collapsible()
|
||||
->schema([
|
||||
@@ -341,9 +353,9 @@ class Settings extends Page implements HasSchemas
|
||||
->live()
|
||||
->default(env('MAIL_MAILER', config('mail.default')))
|
||||
->hintAction(
|
||||
Action::make('test')
|
||||
Action::make('hint_test')
|
||||
->label(trans('admin/setting.mail.test_mail'))
|
||||
->icon('tabler-send')
|
||||
->icon(TablerIcon::Send)
|
||||
->hidden(fn (Get $get) => $get('MAIL_MAILER') === 'log')
|
||||
->authorize(fn () => user()?->can('update settings'))
|
||||
->action(function (Get $get) {
|
||||
@@ -477,16 +489,6 @@ class Settings extends Page implements HasSchemas
|
||||
private function backupSettings(): array
|
||||
{
|
||||
return [
|
||||
ToggleButtons::make('APP_BACKUP_DRIVER')
|
||||
->label(trans('admin/setting.backup.backup_driver'))
|
||||
->columnSpanFull()
|
||||
->inline()
|
||||
->options([
|
||||
Backup::ADAPTER_DAEMON => 'Wings',
|
||||
Backup::ADAPTER_AWS_S3 => 'S3',
|
||||
])
|
||||
->live()
|
||||
->default(env('APP_BACKUP_DRIVER', config('backups.default'))),
|
||||
Section::make(trans('admin/setting.backup.throttle'))
|
||||
->description(trans('admin/setting.backup.throttle_help'))
|
||||
->columns()
|
||||
@@ -506,41 +508,6 @@ class Settings extends Page implements HasSchemas
|
||||
->suffix('Seconds')
|
||||
->default(config('backups.throttles.period')),
|
||||
]),
|
||||
Section::make(trans('admin/setting.backup.s3.s3_title'))
|
||||
->columns()
|
||||
->visible(fn (Get $get) => $get('APP_BACKUP_DRIVER') === Backup::ADAPTER_AWS_S3)
|
||||
->schema([
|
||||
TextInput::make('AWS_DEFAULT_REGION')
|
||||
->label(trans('admin/setting.backup.s3.default_region'))
|
||||
->required()
|
||||
->default(config('backups.disks.s3.region')),
|
||||
TextInput::make('AWS_ACCESS_KEY_ID')
|
||||
->label(trans('admin/setting.backup.s3.access_key'))
|
||||
->required()
|
||||
->default(config('backups.disks.s3.key')),
|
||||
TextInput::make('AWS_SECRET_ACCESS_KEY')
|
||||
->label(trans('admin/setting.backup.s3.secret_key'))
|
||||
->required()
|
||||
->default(config('backups.disks.s3.secret')),
|
||||
TextInput::make('AWS_BACKUPS_BUCKET')
|
||||
->label(trans('admin/setting.backup.s3.bucket'))
|
||||
->required()
|
||||
->default(config('backups.disks.s3.bucket')),
|
||||
TextInput::make('AWS_ENDPOINT')
|
||||
->label(trans('admin/setting.backup.s3.endpoint'))
|
||||
->required()
|
||||
->default(config('backups.disks.s3.endpoint')),
|
||||
Toggle::make('AWS_USE_PATH_STYLE_ENDPOINT')
|
||||
->label(trans('admin/setting.backup.s3.use_path_style_endpoint'))
|
||||
->inline(false)
|
||||
->onIcon('tabler-check')
|
||||
->offIcon('tabler-x')
|
||||
->onColor('success')
|
||||
->offColor('danger')
|
||||
->live()
|
||||
->stateCast(new BooleanStateCast(false))
|
||||
->default(env('AWS_USE_PATH_STYLE_ENDPOINT', config('backups.disks.s3.use_path_style_endpoint'))),
|
||||
]),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -555,26 +522,34 @@ class Settings extends Page implements HasSchemas
|
||||
|
||||
$oauthSchemas = $this->oauthService->getAll();
|
||||
foreach ($oauthSchemas as $schema) {
|
||||
$id = Str::upper($schema->getId());
|
||||
$key = $schema->getConfigKey();
|
||||
|
||||
$icon = $schema->getIcon();
|
||||
if (is_string($icon)) {
|
||||
try {
|
||||
$this->iconFactory->svg($icon);
|
||||
} catch (SvgNotFound) {
|
||||
$icon = null;
|
||||
}
|
||||
}
|
||||
|
||||
$formFields[] = Section::make($schema->getName())
|
||||
->columns(5)
|
||||
->icon($schema->getIcon() ?? 'tabler-brand-oauth')
|
||||
->collapsed(fn () => !env($key, false))
|
||||
->icon($icon ?? TablerIcon::BrandOauth)
|
||||
->collapsed(fn () => !$schema->isEnabled())
|
||||
->collapsible()
|
||||
->schema([
|
||||
Hidden::make($key)
|
||||
->live()
|
||||
->default(env($key)),
|
||||
->default($schema->isEnabled()),
|
||||
Actions::make([
|
||||
Action::make("disable_oauth_$id")
|
||||
Action::make('disable_oauth_' . $schema->getId())
|
||||
->visible(fn (Get $get) => $get($key))
|
||||
->disabled(fn () => !user()?->can('update settings'))
|
||||
->label(trans('admin/setting.oauth.disable'))
|
||||
->color('danger')
|
||||
->action(fn (Set $set) => $set($key, false)),
|
||||
Action::make("enable_oauth_$id")
|
||||
Action::make('enable_oauth_' . $schema->getId())
|
||||
->visible(fn (Get $get) => !$get($key))
|
||||
->disabled(fn () => !user()?->can('update settings'))
|
||||
->label(trans('admin/setting.oauth.enable'))
|
||||
@@ -619,8 +594,8 @@ class Settings extends Page implements HasSchemas
|
||||
->schema([
|
||||
Toggle::make('PANEL_CLIENT_ALLOCATIONS_ENABLED')
|
||||
->label(trans('admin/setting.misc.auto_allocation.question'))
|
||||
->onIcon('tabler-check')
|
||||
->offIcon('tabler-x')
|
||||
->onIcon(TablerIcon::Check)
|
||||
->offIcon(TablerIcon::X)
|
||||
->onColor('success')
|
||||
->offColor('danger')
|
||||
->live()
|
||||
@@ -630,8 +605,8 @@ class Settings extends Page implements HasSchemas
|
||||
Toggle::make('PANEL_CLIENT_ALLOCATIONS_CREATE_NEW')
|
||||
->label(trans('admin/setting.misc.auto_allocation.create_new'))
|
||||
->helperText(trans('admin/setting.misc.auto_allocation.create_new_help'))
|
||||
->onIcon('tabler-check')
|
||||
->offIcon('tabler-x')
|
||||
->onIcon(TablerIcon::Check)
|
||||
->offIcon(TablerIcon::X)
|
||||
->onColor('success')
|
||||
->offColor('danger')
|
||||
->live()
|
||||
@@ -664,8 +639,8 @@ class Settings extends Page implements HasSchemas
|
||||
->schema([
|
||||
Toggle::make('PANEL_SEND_INSTALL_NOTIFICATION')
|
||||
->label(trans('admin/setting.misc.mail_notifications.server_installed'))
|
||||
->onIcon('tabler-check')
|
||||
->offIcon('tabler-x')
|
||||
->onIcon(TablerIcon::Check)
|
||||
->offIcon(TablerIcon::X)
|
||||
->onColor('success')
|
||||
->offColor('danger')
|
||||
->live()
|
||||
@@ -674,8 +649,8 @@ class Settings extends Page implements HasSchemas
|
||||
->default(env('PANEL_SEND_INSTALL_NOTIFICATION', config('panel.email.send_install_notification'))),
|
||||
Toggle::make('PANEL_SEND_REINSTALL_NOTIFICATION')
|
||||
->label(trans('admin/setting.misc.mail_notifications.server_reinstalled'))
|
||||
->onIcon('tabler-check')
|
||||
->offIcon('tabler-x')
|
||||
->onIcon(TablerIcon::Check)
|
||||
->offIcon(TablerIcon::X)
|
||||
->onColor('success')
|
||||
->offColor('danger')
|
||||
->live()
|
||||
@@ -723,8 +698,8 @@ class Settings extends Page implements HasSchemas
|
||||
Toggle::make('APP_ACTIVITY_HIDE_ADMIN')
|
||||
->label(trans('admin/setting.misc.activity_log.log_admin'))
|
||||
->inline(false)
|
||||
->onIcon('tabler-check')
|
||||
->offIcon('tabler-x')
|
||||
->onIcon(TablerIcon::Check)
|
||||
->offIcon(TablerIcon::X)
|
||||
->onColor('success')
|
||||
->offColor('danger')
|
||||
->live()
|
||||
@@ -760,8 +735,8 @@ class Settings extends Page implements HasSchemas
|
||||
->schema([
|
||||
Toggle::make('PANEL_EDITABLE_SERVER_DESCRIPTIONS')
|
||||
->label(trans('admin/setting.misc.server.edit_server_desc'))
|
||||
->onIcon('tabler-check')
|
||||
->offIcon('tabler-x')
|
||||
->onIcon(TablerIcon::Check)
|
||||
->offIcon(TablerIcon::X)
|
||||
->onColor('success')
|
||||
->offColor('danger')
|
||||
->live()
|
||||
@@ -823,7 +798,6 @@ class Settings extends Page implements HasSchemas
|
||||
|
||||
$this->writeToEnvironment($data);
|
||||
|
||||
Artisan::call('config:clear');
|
||||
Artisan::call('queue:restart');
|
||||
|
||||
$this->redirect($this->getUrl());
|
||||
@@ -846,9 +820,10 @@ class Settings extends Page implements HasSchemas
|
||||
{
|
||||
return [
|
||||
Action::make('save')
|
||||
->iconButton()->iconSize(IconSize::ExtraLarge)
|
||||
->icon('tabler-device-floppy')
|
||||
->hiddenLabel()
|
||||
->icon(TablerIcon::DeviceFloppy)
|
||||
->action('save')
|
||||
->tooltip(trans('filament-panels::resources/pages/edit-record.form.actions.save.label'))
|
||||
->authorize(fn () => user()?->can('update settings'))
|
||||
->keyBindings(['mod+s']),
|
||||
];
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Filament\Admin\Pages;
|
||||
|
||||
use App\Enums\TablerIcon;
|
||||
use App\Traits\ResolvesRecordDate;
|
||||
use Boquizo\FilamentLogViewer\Actions\BackAction;
|
||||
use Boquizo\FilamentLogViewer\Actions\DeleteAction;
|
||||
@@ -9,7 +10,6 @@ use Boquizo\FilamentLogViewer\Actions\DownloadAction;
|
||||
use Boquizo\FilamentLogViewer\Pages\ViewLog as BaseViewLog;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Support\Enums\IconSize;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
|
||||
class ViewLogs extends BaseViewLog
|
||||
@@ -20,20 +20,23 @@ class ViewLogs extends BaseViewLog
|
||||
{
|
||||
return [
|
||||
BackAction::make()
|
||||
->icon('tabler-arrow-left')->iconSize(IconSize::ExtraLarge)->iconButton(),
|
||||
->tooltip(trans('filament-log-viewer::log.table.actions.close.label'))
|
||||
->icon(TablerIcon::ArrowLeft)->iconButton(),
|
||||
DeleteAction::make(withTooltip: true)
|
||||
->iconSize(IconSize::ExtraLarge)->iconButton(),
|
||||
->icon(TablerIcon::Trash)->iconButton(),
|
||||
DownloadAction::make(withTooltip: true)
|
||||
->icon('tabler-file-download')->iconSize(IconSize::ExtraLarge)->iconButton(),
|
||||
->icon(TablerIcon::FileDownload)->iconButton(),
|
||||
Action::make('uploadLogs')
|
||||
->hiddenLabel()
|
||||
->icon('tabler-world-upload')->iconSize(IconSize::ExtraLarge)->iconButton()
|
||||
->icon(TablerIcon::WorldUpload)
|
||||
->requiresConfirmation()
|
||||
->tooltip(trans('admin/log.actions.upload_tooltip', ['url' => 'logs.pelican.dev']))
|
||||
->modalHeading(trans('admin/log.actions.upload_logs'))
|
||||
->modalDescription(fn () => trans('admin/log.actions.upload_logs_description', ['file' => $this->resolveRecordDate(), 'url' => 'https://logs.pelican.dev']))
|
||||
->action(function () {
|
||||
$logPath = storage_path('logs/' . $this->resolveRecordDate());
|
||||
$prefix = config('filament-log-viewer.pattern.prefix', 'laravel-');
|
||||
$extension = config('filament-log-viewer.pattern.extension', '.log');
|
||||
$logPath = storage_path('logs/' . $prefix . $this->resolveRecordDate() . $extension);
|
||||
|
||||
if (!file_exists($logPath)) {
|
||||
Notification::make()
|
||||
@@ -50,18 +53,12 @@ class ViewLogs extends BaseViewLog
|
||||
$uploadLines = $totalLines <= 1000 ? $lines : array_slice($lines, -1000);
|
||||
$content = implode("\n", $uploadLines);
|
||||
|
||||
$logUrl = 'https://logs.pelican.dev';
|
||||
try {
|
||||
$response = Http::timeout(10)->asMultipart()->post($logUrl, [
|
||||
[
|
||||
'name' => 'c',
|
||||
'contents' => $content,
|
||||
],
|
||||
[
|
||||
'name' => 'e',
|
||||
'contents' => '14d',
|
||||
],
|
||||
]);
|
||||
$response = Http::timeout(10)
|
||||
->asMultipart()
|
||||
->attach('c', $content)
|
||||
->attach('e', '14d')
|
||||
->post('https://logs.pelican.dev');
|
||||
|
||||
if ($response->failed()) {
|
||||
Notification::make()
|
||||
@@ -81,7 +78,7 @@ class ViewLogs extends BaseViewLog
|
||||
->body("{$url}")
|
||||
->success()
|
||||
->actions([
|
||||
Action::make('viewLogs')
|
||||
Action::make('exclude_viewLogs')
|
||||
->label(trans('admin/log.actions.view_logs'))
|
||||
->url($url)
|
||||
->openUrlInNewTab(true),
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Filament\Admin\Resources\ApiKeys;
|
||||
|
||||
use App\Enums\TablerIcon;
|
||||
use App\Filament\Admin\Resources\ApiKeys\Pages\CreateApiKey;
|
||||
use App\Filament\Admin\Resources\ApiKeys\Pages\ListApiKeys;
|
||||
use App\Filament\Admin\Resources\Users\Pages\EditUser;
|
||||
@@ -11,7 +12,9 @@ use App\Traits\Filament\CanCustomizePages;
|
||||
use App\Traits\Filament\CanCustomizeRelations;
|
||||
use App\Traits\Filament\CanModifyForm;
|
||||
use App\Traits\Filament\CanModifyTable;
|
||||
use BackedEnum;
|
||||
use Exception;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Forms\Components\TagsInput;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
@@ -19,8 +22,8 @@ use Filament\Forms\Components\ToggleButtons;
|
||||
use Filament\Resources\Pages\PageRegistration;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Components\Fieldset;
|
||||
use Filament\Schemas\Components\Section;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Support\Enums\IconSize;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
@@ -34,7 +37,7 @@ class ApiKeyResource extends Resource
|
||||
|
||||
protected static ?string $model = ApiKey::class;
|
||||
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'tabler-key';
|
||||
protected static string|BackedEnum|null $navigationIcon = TablerIcon::Key;
|
||||
|
||||
public static function getNavigationLabel(): string
|
||||
{
|
||||
@@ -77,7 +80,7 @@ class ApiKeyResource extends Resource
|
||||
->columns([
|
||||
TextColumn::make('key')
|
||||
->label(trans('admin/apikey.table.key'))
|
||||
->icon('tabler-clipboard-text')
|
||||
->icon(TablerIcon::ClipboardText)
|
||||
->state(fn (ApiKey $key) => $key->identifier . $key->token)
|
||||
->copyable(),
|
||||
TextColumn::make('memo')
|
||||
@@ -96,10 +99,12 @@ class ApiKeyResource extends Resource
|
||||
->url(fn (ApiKey $apiKey) => user()?->can('update', $apiKey->user) ? EditUser::getUrl(['record' => $apiKey->user]) : null),
|
||||
])
|
||||
->recordActions([
|
||||
DeleteAction::make()
|
||||
->iconButton()->iconSize(IconSize::ExtraLarge),
|
||||
DeleteAction::make(),
|
||||
])
|
||||
->emptyStateIcon('tabler-key')
|
||||
->toolbarActions([
|
||||
CreateAction::make(),
|
||||
])
|
||||
->emptyStateIcon(TablerIcon::Key)
|
||||
->emptyStateDescription('')
|
||||
->emptyStateHeading(trans('admin/apikey.empty'));
|
||||
}
|
||||
@@ -109,12 +114,44 @@ class ApiKeyResource extends Resource
|
||||
*/
|
||||
public static function defaultForm(Schema $schema): Schema
|
||||
{
|
||||
$permissionList = ApiKey::getPermissionList();
|
||||
|
||||
return $schema
|
||||
->components([
|
||||
Section::make(trans('admin/apikey.permissions.all'))
|
||||
->description(trans('admin/apikey.permissions.all_description'))
|
||||
->columnSpanFull()
|
||||
->schema([
|
||||
ToggleButtons::make('permissions_all')
|
||||
->hiddenLabel()
|
||||
->inline()
|
||||
->options([
|
||||
0 => trans('admin/apikey.permissions.none'),
|
||||
1 => trans('admin/apikey.permissions.read'),
|
||||
3 => trans('admin/apikey.permissions.read_write'),
|
||||
])
|
||||
->icons([
|
||||
0 => TablerIcon::BookOff,
|
||||
1 => TablerIcon::Book,
|
||||
3 => TablerIcon::Writing,
|
||||
])
|
||||
->colors([
|
||||
0 => 'success',
|
||||
1 => 'warning',
|
||||
3 => 'danger',
|
||||
])
|
||||
->live()
|
||||
->afterStateUpdated(function ($state, callable $set) use ($permissionList) {
|
||||
foreach ($permissionList as $resource) {
|
||||
$set('permissions_' . $resource, $state);
|
||||
}
|
||||
})
|
||||
->default(0),
|
||||
]),
|
||||
Fieldset::make('Permissions')
|
||||
->columnSpanFull()
|
||||
->schema(
|
||||
collect(ApiKey::getPermissionList())->map(fn ($resource) => ToggleButtons::make('permissions_' . $resource)
|
||||
collect($permissionList)->map(fn ($resource) => ToggleButtons::make('permissions_' . $resource)
|
||||
->label(str($resource)->replace('_', ' ')->title())->inline()
|
||||
->options([
|
||||
0 => trans('admin/apikey.permissions.none'),
|
||||
@@ -122,9 +159,9 @@ class ApiKeyResource extends Resource
|
||||
3 => trans('admin/apikey.permissions.read_write'),
|
||||
])
|
||||
->icons([
|
||||
0 => 'tabler-book-off',
|
||||
1 => 'tabler-book',
|
||||
3 => 'tabler-writing',
|
||||
0 => TablerIcon::BookOff,
|
||||
1 => TablerIcon::Book,
|
||||
3 => TablerIcon::Writing,
|
||||
])
|
||||
->colors([
|
||||
0 => 'success',
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Filament\Admin\Resources\ApiKeys\Pages;
|
||||
|
||||
use App\Enums\TablerIcon;
|
||||
use App\Filament\Admin\Resources\ApiKeys\ApiKeyResource;
|
||||
use App\Models\ApiKey;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
@@ -9,7 +10,6 @@ use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\ActionGroup;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
use Filament\Support\Enums\IconSize;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
@@ -26,9 +26,12 @@ class CreateApiKey extends CreateRecord
|
||||
protected function getDefaultHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
$this->getCreateFormAction()->formId('form')
|
||||
->iconButton()->iconSize(IconSize::ExtraLarge)
|
||||
->icon('tabler-file-plus'),
|
||||
Action::make('create')
|
||||
->hiddenLabel()
|
||||
->action('create')
|
||||
->keyBindings(['mod+s'])
|
||||
->tooltip(trans('filament-panels::resources/pages/create-record.form.actions.create.label'))
|
||||
->icon(TablerIcon::FilePlus),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -5,11 +5,7 @@ namespace App\Filament\Admin\Resources\ApiKeys\Pages;
|
||||
use App\Filament\Admin\Resources\ApiKeys\ApiKeyResource;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\ActionGroup;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
use Filament\Support\Enums\IconSize;
|
||||
|
||||
class ListApiKeys extends ListRecords
|
||||
{
|
||||
@@ -17,14 +13,4 @@ class ListApiKeys extends ListRecords
|
||||
use CanCustomizeHeaderWidgets;
|
||||
|
||||
protected static string $resource = ApiKeyResource::class;
|
||||
|
||||
/** @return array<Action|ActionGroup> */
|
||||
protected function getDefaultHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
CreateAction::make()
|
||||
->iconButton()->iconSize(IconSize::ExtraLarge)
|
||||
->icon('tabler-file-plus'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
162
app/Filament/Admin/Resources/BackupHosts/BackupHostResource.php
Normal file
162
app/Filament/Admin/Resources/BackupHosts/BackupHostResource.php
Normal file
@@ -0,0 +1,162 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources\BackupHosts;
|
||||
|
||||
use App\Enums\TablerIcon;
|
||||
use App\Extensions\BackupAdapter\BackupAdapterService;
|
||||
use App\Filament\Admin\Resources\BackupHosts\Pages\CreateBackupHost;
|
||||
use App\Filament\Admin\Resources\BackupHosts\Pages\EditBackupHost;
|
||||
use App\Filament\Admin\Resources\BackupHosts\Pages\ListBackupHosts;
|
||||
use App\Filament\Admin\Resources\BackupHosts\Pages\ViewBackupHost;
|
||||
use App\Filament\Admin\Resources\BackupHosts\RelationManagers\BackupsRelationManager;
|
||||
use App\Models\BackupHost;
|
||||
use App\Traits\Filament\CanCustomizePages;
|
||||
use App\Traits\Filament\CanCustomizeRelations;
|
||||
use App\Traits\Filament\CanModifyForm;
|
||||
use App\Traits\Filament\CanModifyTable;
|
||||
use BackedEnum;
|
||||
use Exception;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Actions\ViewAction;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Resources\Pages\PageRegistration;
|
||||
use Filament\Resources\RelationManagers\RelationManager;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Components\Section;
|
||||
use Filament\Schemas\Components\Utilities\Get;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
class BackupHostResource extends Resource
|
||||
{
|
||||
use CanCustomizePages;
|
||||
use CanCustomizeRelations;
|
||||
use CanModifyForm;
|
||||
use CanModifyTable;
|
||||
|
||||
protected static ?string $model = BackupHost::class;
|
||||
|
||||
protected static string|BackedEnum|null $navigationIcon = TablerIcon::FileZip;
|
||||
|
||||
protected static ?string $recordTitleAttribute = 'name';
|
||||
|
||||
public static function getNavigationBadge(): ?string
|
||||
{
|
||||
return (string) static::getEloquentQuery()->count() ?: null;
|
||||
}
|
||||
|
||||
public static function getNavigationLabel(): string
|
||||
{
|
||||
return static::getPluralModelLabel();
|
||||
}
|
||||
|
||||
public static function getModelLabel(): string
|
||||
{
|
||||
return trans_choice('admin/backuphost.model_label', 1);
|
||||
}
|
||||
|
||||
public static function getPluralModelLabel(): string
|
||||
{
|
||||
return trans_choice('admin/backuphost.model_label', 2);
|
||||
}
|
||||
|
||||
public static function getNavigationGroup(): ?string
|
||||
{
|
||||
return trans('admin/dashboard.advanced');
|
||||
}
|
||||
|
||||
/** @throws Exception */
|
||||
public static function defaultTable(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
TextColumn::make('name')
|
||||
->label(trans('admin/backuphost.name')),
|
||||
TextColumn::make('schema')
|
||||
->label(trans('admin/backuphost.schema'))
|
||||
->badge(),
|
||||
TextColumn::make('backups_count')
|
||||
->counts('backups')
|
||||
->label(trans('admin/backuphost.backups')),
|
||||
TextColumn::make('nodes.name')
|
||||
->badge()
|
||||
->placeholder(trans('admin/backuphost.all_nodes')),
|
||||
])
|
||||
->recordActions([
|
||||
ViewAction::make()
|
||||
->hidden(fn ($record) => static::getEditAuthorizationResponse($record)->allowed()),
|
||||
EditAction::make(),
|
||||
])
|
||||
->toolbarActions([
|
||||
CreateAction::make(),
|
||||
])
|
||||
->emptyStateIcon(TablerIcon::FileZip)
|
||||
->emptyStateDescription(trans('admin/backuphost.local_backups_only'))
|
||||
->emptyStateHeading(trans('admin/backuphost.no_backup_hosts'));
|
||||
}
|
||||
|
||||
/** @throws Exception */
|
||||
public static function defaultForm(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
TextInput::make('name')
|
||||
->label(trans('admin/backuphost.name'))
|
||||
->required(),
|
||||
Select::make('schema')
|
||||
->label(trans('admin/backuphost.schema'))
|
||||
->required()
|
||||
->selectablePlaceholder(false)
|
||||
->searchable()
|
||||
->options(fn (BackupAdapterService $service) => $service->getMappings())
|
||||
->live(onBlur: true),
|
||||
Select::make('node_ids')
|
||||
->label(trans('admin/backuphost.linked_nodes'))
|
||||
->multiple()
|
||||
->searchable()
|
||||
->preload()
|
||||
->relationship('nodes', 'name', fn (Builder $query) => $query->whereIn('nodes.id', user()?->accessibleNodes()->pluck('id'))),
|
||||
Section::make(trans('admin/backuphost.configuration'))
|
||||
->columnSpanFull()
|
||||
->columns()
|
||||
->schema(function (?BackupHost $backupHost, Get $get, BackupAdapterService $service) {
|
||||
$schema = $get('schema') ?? $backupHost?->schema;
|
||||
|
||||
if (!$schema) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$schema = $service->get($schema);
|
||||
|
||||
if ($schema) {
|
||||
return $schema->getConfigurationForm();
|
||||
}
|
||||
|
||||
return [];
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
/** @return class-string<RelationManager>[] */
|
||||
public static function getDefaultRelations(): array
|
||||
{
|
||||
return [
|
||||
BackupsRelationManager::class,
|
||||
];
|
||||
}
|
||||
|
||||
/** @return array<string, PageRegistration> */
|
||||
public static function getDefaultPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => ListBackupHosts::route('/'),
|
||||
'create' => CreateBackupHost::route('/create'),
|
||||
'view' => ViewBackupHost::route('/{record}'),
|
||||
'edit' => EditBackupHost::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources\BackupHosts\Pages;
|
||||
|
||||
use App\Filament\Admin\Resources\BackupHosts\BackupHostResource;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreateBackupHost extends CreateRecord
|
||||
{
|
||||
use CanCustomizeHeaderActions;
|
||||
use CanCustomizeHeaderWidgets;
|
||||
|
||||
protected static string $resource = BackupHostResource::class;
|
||||
|
||||
protected static bool $canCreateAnother = false;
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources\BackupHosts\Pages;
|
||||
|
||||
use App\Enums\TablerIcon;
|
||||
use App\Filament\Admin\Resources\BackupHosts\BackupHostResource;
|
||||
use App\Models\BackupHost;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\ActionGroup;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditBackupHost extends EditRecord
|
||||
{
|
||||
use CanCustomizeHeaderActions;
|
||||
use CanCustomizeHeaderWidgets;
|
||||
|
||||
protected static string $resource = BackupHostResource::class;
|
||||
|
||||
/** @return array<Action|ActionGroup> */
|
||||
protected function getDefaultHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
DeleteAction::make()
|
||||
->label(fn (BackupHost $backupHost) => $backupHost->backups()->count() > 0 ? trans('admin/backuphost.delete_help') : trans('filament-actions::delete.single.modal.actions.delete.label'))
|
||||
->disabled(fn (BackupHost $backupHost) => $backupHost->backups()->count() > 0)
|
||||
->hidden(fn () => BackupHost::count() === 1),
|
||||
Action::make('save')
|
||||
->hiddenLabel()
|
||||
->action('save')
|
||||
->keyBindings(['mod+s'])
|
||||
->tooltip(trans('filament-panels::resources/pages/edit-record.form.actions.save.label'))
|
||||
->icon(TablerIcon::DeviceFloppy),
|
||||
];
|
||||
}
|
||||
|
||||
protected function getFormActions(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources\BackupHosts\Pages;
|
||||
|
||||
use App\Filament\Admin\Resources\BackupHosts\BackupHostResource;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListBackupHosts extends ListRecords
|
||||
{
|
||||
use CanCustomizeHeaderActions;
|
||||
use CanCustomizeHeaderWidgets;
|
||||
|
||||
protected static string $resource = BackupHostResource::class;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources\BackupHosts\Pages;
|
||||
|
||||
use App\Filament\Admin\Resources\BackupHosts\BackupHostResource;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\ActionGroup;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Resources\Pages\ViewRecord;
|
||||
|
||||
class ViewBackupHost extends ViewRecord
|
||||
{
|
||||
use CanCustomizeHeaderActions;
|
||||
use CanCustomizeHeaderWidgets;
|
||||
|
||||
protected static string $resource = BackupHostResource::class;
|
||||
|
||||
/** @return array<Action|ActionGroup> */
|
||||
protected function getDefaultHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
EditAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources\BackupHosts\RelationManagers;
|
||||
|
||||
use App\Enums\TablerIcon;
|
||||
use App\Filament\Components\Tables\Columns\BytesColumn;
|
||||
use App\Filament\Components\Tables\Columns\DateTimeColumn;
|
||||
use Filament\Resources\RelationManagers\RelationManager;
|
||||
use Filament\Tables\Columns\IconColumn;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class BackupsRelationManager extends RelationManager
|
||||
{
|
||||
protected static string $relationship = 'backups';
|
||||
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->recordTitleAttribute('name')
|
||||
->heading(null)
|
||||
->columns([
|
||||
TextColumn::make('name')
|
||||
->label(trans('server/backup.actions.create.name'))
|
||||
->searchable(),
|
||||
BytesColumn::make('bytes')
|
||||
->label(trans('server/backup.size')),
|
||||
DateTimeColumn::make('created_at')
|
||||
->label(trans('server/backup.created_at'))
|
||||
->since()
|
||||
->sortable(),
|
||||
TextColumn::make('status')
|
||||
->label(trans('server/backup.status'))
|
||||
->badge(),
|
||||
IconColumn::make('is_locked')
|
||||
->label(trans('server/backup.is_locked'))
|
||||
->visibleFrom('md')
|
||||
->trueIcon(TablerIcon::Lock)
|
||||
->falseIcon(TablerIcon::LockOpen),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Filament\Admin\Resources\DatabaseHosts;
|
||||
|
||||
use App\Enums\TablerIcon;
|
||||
use App\Filament\Admin\Resources\DatabaseHosts\Pages\CreateDatabaseHost;
|
||||
use App\Filament\Admin\Resources\DatabaseHosts\Pages\EditDatabaseHost;
|
||||
use App\Filament\Admin\Resources\DatabaseHosts\Pages\ListDatabaseHosts;
|
||||
@@ -12,7 +13,10 @@ use App\Traits\Filament\CanCustomizePages;
|
||||
use App\Traits\Filament\CanCustomizeRelations;
|
||||
use App\Traits\Filament\CanModifyForm;
|
||||
use App\Traits\Filament\CanModifyTable;
|
||||
use BackedEnum;
|
||||
use Exception;
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Actions\ViewAction;
|
||||
@@ -37,7 +41,7 @@ class DatabaseHostResource extends Resource
|
||||
|
||||
protected static ?string $model = DatabaseHost::class;
|
||||
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'tabler-database';
|
||||
protected static string|BackedEnum|null $navigationIcon = TablerIcon::Database;
|
||||
|
||||
protected static ?string $recordTitleAttribute = 'name';
|
||||
|
||||
@@ -94,10 +98,13 @@ class DatabaseHostResource extends Resource
|
||||
->hidden(fn ($record) => static::getEditAuthorizationResponse($record)->allowed()),
|
||||
EditAction::make(),
|
||||
])
|
||||
->groupedBulkActions([
|
||||
DeleteBulkAction::make(),
|
||||
->toolbarActions([
|
||||
CreateAction::make(),
|
||||
BulkActionGroup::make([
|
||||
DeleteBulkAction::make('exclude_bulk_delete'),
|
||||
]),
|
||||
])
|
||||
->emptyStateIcon('tabler-database')
|
||||
->emptyStateIcon(TablerIcon::Database)
|
||||
->emptyStateDescription('')
|
||||
->emptyStateHeading(trans('admin/databasehost.no_database_hosts'));
|
||||
}
|
||||
|
||||
@@ -2,11 +2,13 @@
|
||||
|
||||
namespace App\Filament\Admin\Resources\DatabaseHosts\Pages;
|
||||
|
||||
use App\Enums\TablerIcon;
|
||||
use App\Filament\Admin\Resources\DatabaseHosts\DatabaseHostResource;
|
||||
use App\Services\Databases\Hosts\HostCreationService;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use Exception;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Forms\Components\Hidden;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
@@ -44,6 +46,17 @@ class CreateDatabaseHost extends CreateRecord
|
||||
$this->service = $service;
|
||||
}
|
||||
|
||||
protected function getCreateFormAction(): Action
|
||||
{
|
||||
$hasFormWrapper = $this->hasFormWrapper();
|
||||
|
||||
return Action::make('exclude_create')
|
||||
->label(trans('filament-panels::resources/pages/create-record.form.actions.create.label'))
|
||||
->submit($hasFormWrapper ? $this->getSubmitFormLivewireMethodName() : null)
|
||||
->action($hasFormWrapper ? null : $this->getSubmitFormLivewireMethodName())
|
||||
->keyBindings(['mod+s']);
|
||||
}
|
||||
|
||||
/** @return Step[]
|
||||
* @throws Exception
|
||||
*/
|
||||
@@ -175,7 +188,7 @@ class CreateDatabaseHost extends CreateRecord
|
||||
->title(trans('admin/databasehost.error'))
|
||||
->body($exception->getMessage())
|
||||
->color('danger')
|
||||
->icon('tabler-database')
|
||||
->icon(TablerIcon::Database)
|
||||
->danger()
|
||||
->send();
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Filament\Admin\Resources\DatabaseHosts\Pages;
|
||||
|
||||
use App\Enums\TablerIcon;
|
||||
use App\Filament\Admin\Resources\DatabaseHosts\DatabaseHostResource;
|
||||
use App\Models\DatabaseHost;
|
||||
use App\Services\Databases\Hosts\HostUpdateService;
|
||||
@@ -12,7 +13,6 @@ use Filament\Actions\ActionGroup;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
use Filament\Support\Enums\IconSize;
|
||||
use Filament\Support\Exceptions\Halt;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use PDOException;
|
||||
@@ -36,12 +36,14 @@ class EditDatabaseHost extends EditRecord
|
||||
{
|
||||
return [
|
||||
DeleteAction::make()
|
||||
->label(fn (DatabaseHost $databaseHost) => $databaseHost->databases()->count() > 0 ? trans('admin/databasehost.delete_help') : trans('filament-actions::delete.single.modal.actions.delete.label'))
|
||||
->disabled(fn (DatabaseHost $databaseHost) => $databaseHost->databases()->count() > 0)
|
||||
->iconButton()->iconSize(IconSize::ExtraLarge),
|
||||
$this->getSaveFormAction()->formId('form')
|
||||
->iconButton()->iconSize(IconSize::ExtraLarge)
|
||||
->icon('tabler-device-floppy'),
|
||||
->tooltip(fn (DatabaseHost $databaseHost) => $databaseHost->databases()->count() > 0 ? trans('admin/databasehost.delete_help') : trans('filament-actions::delete.single.modal.actions.delete.label'))
|
||||
->disabled(fn (DatabaseHost $databaseHost) => $databaseHost->databases()->count() > 0),
|
||||
Action::make('save')
|
||||
->hiddenLabel()
|
||||
->action('save')
|
||||
->keyBindings(['mod+s'])
|
||||
->tooltip(trans('filament-panels::resources/pages/edit-record.form.actions.save.label'))
|
||||
->icon(TablerIcon::DeviceFloppy),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -63,7 +65,7 @@ class EditDatabaseHost extends EditRecord
|
||||
->title(trans('admin/databasehost.error'))
|
||||
->body($exception->getMessage())
|
||||
->color('danger')
|
||||
->icon('tabler-database')
|
||||
->icon(TablerIcon::Database)
|
||||
->danger()
|
||||
->send();
|
||||
|
||||
|
||||
@@ -5,11 +5,7 @@ namespace App\Filament\Admin\Resources\DatabaseHosts\Pages;
|
||||
use App\Filament\Admin\Resources\DatabaseHosts\DatabaseHostResource;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\ActionGroup;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
use Filament\Support\Enums\IconSize;
|
||||
|
||||
class ListDatabaseHosts extends ListRecords
|
||||
{
|
||||
@@ -17,14 +13,4 @@ class ListDatabaseHosts extends ListRecords
|
||||
use CanCustomizeHeaderWidgets;
|
||||
|
||||
protected static string $resource = DatabaseHostResource::class;
|
||||
|
||||
/** @return array<Action|ActionGroup> */
|
||||
protected function getDefaultHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
CreateAction::make()
|
||||
->iconButton()->iconSize(IconSize::ExtraLarge)
|
||||
->icon('tabler-file-plus'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ use Filament\Actions\Action;
|
||||
use Filament\Actions\ActionGroup;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Resources\Pages\ViewRecord;
|
||||
use Filament\Support\Enums\IconSize;
|
||||
|
||||
class ViewDatabaseHost extends ViewRecord
|
||||
{
|
||||
@@ -22,8 +21,7 @@ class ViewDatabaseHost extends ViewRecord
|
||||
protected function getDefaultHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
EditAction::make()
|
||||
->iconButton()->iconSize(IconSize::ExtraLarge),
|
||||
EditAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ use Filament\Actions\ViewAction;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Resources\RelationManagers\RelationManager;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Support\Enums\IconSize;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
@@ -37,7 +36,7 @@ class DatabasesRelationManager extends RelationManager
|
||||
->formatStateUsing(fn (Database $record) => $record->remote === '%' ? trans('admin/databasehost.anywhere'). ' ( % )' : $record->remote),
|
||||
TextInput::make('max_connections')
|
||||
->label(trans('admin/databasehost.table.max_connections'))
|
||||
->formatStateUsing(fn (Database $record) => $record->max_connections === 0 ? trans('admin/databasehost.unlimited') : $record->max_connections),
|
||||
->formatStateUsing(fn (Database $record) => $record->max_connections ?: trans('admin/databasehost.unlimited')),
|
||||
TextInput::make('jdbc')
|
||||
->label(trans('admin/databasehost.table.connection_string'))
|
||||
->columnSpanFull()
|
||||
@@ -63,15 +62,14 @@ class DatabasesRelationManager extends RelationManager
|
||||
->url(fn (Database $database) => route('filament.admin.resources.servers.edit', ['record' => $database->server_id])),
|
||||
TextColumn::make('max_connections')
|
||||
->label(trans('admin/databasehost.table.max_connections'))
|
||||
->formatStateUsing(fn ($record) => $record->max_connections === 0 ? trans('admin/databasehost.unlimited') : $record->max_connections),
|
||||
->formatStateUsing(fn ($record) => $record->max_connections ?: trans('server/database.unlimited')),
|
||||
DateTimeColumn::make('created_at')
|
||||
->label(trans('admin/databasehost.table.created_at')),
|
||||
])
|
||||
->recordActions([
|
||||
ViewAction::make()
|
||||
->color('primary'),
|
||||
DeleteAction::make()
|
||||
->iconButton()->iconSize(IconSize::ExtraLarge),
|
||||
DeleteAction::make(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Filament\Admin\Resources\Eggs;
|
||||
|
||||
use App\Enums\CustomizationKey;
|
||||
use App\Enums\TablerIcon;
|
||||
use App\Filament\Admin\Resources\Eggs\Pages\CreateEgg;
|
||||
use App\Filament\Admin\Resources\Eggs\Pages\EditEgg;
|
||||
use App\Filament\Admin\Resources\Eggs\Pages\ListEggs;
|
||||
@@ -10,6 +11,7 @@ use App\Filament\Admin\Resources\Eggs\RelationManagers\ServersRelationManager;
|
||||
use App\Models\Egg;
|
||||
use App\Traits\Filament\CanCustomizePages;
|
||||
use App\Traits\Filament\CanCustomizeRelations;
|
||||
use BackedEnum;
|
||||
use Filament\Resources\Pages\PageRegistration;
|
||||
use Filament\Resources\RelationManagers\RelationManager;
|
||||
use Filament\Resources\Resource;
|
||||
@@ -21,7 +23,7 @@ class EggResource extends Resource
|
||||
|
||||
protected static ?string $model = Egg::class;
|
||||
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'tabler-eggs';
|
||||
protected static string|BackedEnum|null $navigationIcon = TablerIcon::Eggs;
|
||||
|
||||
protected static ?string $recordTitleAttribute = 'name';
|
||||
|
||||
|
||||
@@ -3,13 +3,14 @@
|
||||
namespace App\Filament\Admin\Resources\Eggs\Pages;
|
||||
|
||||
use App\Enums\EditorLanguages;
|
||||
use App\Enums\TablerIcon;
|
||||
use App\Filament\Admin\Resources\Eggs\EggResource;
|
||||
use App\Filament\Components\Forms\Fields\CopyFrom;
|
||||
use App\Filament\Components\Forms\Fields\MonacoEditor;
|
||||
use App\Models\EggVariable;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use Exception;
|
||||
use App\Traits\Filament\CanCustomizeTabs;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\ActionGroup;
|
||||
use Filament\Forms\Components\Checkbox;
|
||||
@@ -28,7 +29,6 @@ use Filament\Schemas\Components\Tabs\Tab;
|
||||
use Filament\Schemas\Components\Utilities\Get;
|
||||
use Filament\Schemas\Components\Utilities\Set;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Support\Enums\IconSize;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\Rules\Unique;
|
||||
@@ -37,6 +37,7 @@ class CreateEgg extends CreateRecord
|
||||
{
|
||||
use CanCustomizeHeaderActions;
|
||||
use CanCustomizeHeaderWidgets;
|
||||
use CanCustomizeTabs;
|
||||
|
||||
protected static string $resource = EggResource::class;
|
||||
|
||||
@@ -46,9 +47,12 @@ class CreateEgg extends CreateRecord
|
||||
protected function getDefaultHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
$this->getCreateFormAction()->formId('form')
|
||||
->iconButton()->iconSize(IconSize::ExtraLarge)
|
||||
->icon('tabler-file-plus'),
|
||||
Action::make('create')
|
||||
->hiddenLabel()
|
||||
->action('create')
|
||||
->keyBindings(['mod+s'])
|
||||
->tooltip(trans('filament-panels::resources/pages/create-record.form.actions.create.label'))
|
||||
->icon(TablerIcon::FilePlus),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -57,227 +61,232 @@ class CreateEgg extends CreateRecord
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
Tabs::make()->tabs([
|
||||
Tab::make('configuration')
|
||||
->label(trans('admin/egg.tabs.configuration'))
|
||||
->columns(['default' => 1, 'sm' => 1, 'md' => 2, 'lg' => 4])
|
||||
Tabs::make()
|
||||
->tabs($this->getTabs())
|
||||
->columnSpanFull()
|
||||
->persistTabInQueryString(),
|
||||
]);
|
||||
}
|
||||
|
||||
/** @return Tab[] */
|
||||
protected function getDefaultTabs(): array
|
||||
{
|
||||
return [
|
||||
Tab::make('configuration')
|
||||
->label(trans('admin/egg.tabs.configuration'))
|
||||
->columns(['default' => 1, 'sm' => 1, 'md' => 2, 'lg' => 4])
|
||||
->schema([
|
||||
TextInput::make('name')
|
||||
->label(trans('admin/egg.name'))
|
||||
->required()
|
||||
->maxLength(255)
|
||||
->columnSpan(['default' => 1, 'sm' => 1, 'md' => 2, 'lg' => 2])
|
||||
->helperText(trans('admin/egg.name_help')),
|
||||
TextInput::make('author')
|
||||
->label(trans('admin/egg.author'))
|
||||
->maxLength(255)
|
||||
->required()
|
||||
->email()
|
||||
->columnSpan(['default' => 1, 'sm' => 1, 'md' => 2, 'lg' => 2])
|
||||
->helperText(trans('admin/egg.author_help')),
|
||||
Textarea::make('description')
|
||||
->label(trans('admin/egg.description'))
|
||||
->rows(2)
|
||||
->columnSpanFull()
|
||||
->helperText(trans('admin/egg.description_help')),
|
||||
KeyValue::make('startup_commands')
|
||||
->label(trans('admin/egg.startup_commands'))
|
||||
->live()
|
||||
->columnSpanFull()
|
||||
->required()
|
||||
->addActionLabel(trans('admin/egg.add_startup'))
|
||||
->keyLabel(trans('admin/egg.startup_name'))
|
||||
->keyPlaceholder('Default')
|
||||
->valueLabel(trans('admin/egg.startup_command'))
|
||||
->valuePlaceholder('java -Xms128M -XX:MaxRAMPercentage=95.0 -jar {{SERVER_JARFILE}}')
|
||||
->helperText(trans('admin/egg.startup_help')),
|
||||
TagsInput::make('file_denylist')
|
||||
->label(trans('admin/egg.file_denylist'))
|
||||
->placeholder('denied-file.txt')
|
||||
->helperText(trans('admin/egg.file_denylist_help'))
|
||||
->columnSpan(['default' => 1, 'sm' => 1, 'md' => 2, 'lg' => 2]),
|
||||
TagsInput::make('features')
|
||||
->label(trans('admin/egg.features'))
|
||||
->columnSpan(['default' => 1, 'sm' => 1, 'md' => 1, 'lg' => 1]),
|
||||
Toggle::make('force_outgoing_ip')
|
||||
->label(trans('admin/egg.force_ip'))
|
||||
->hintIcon(TablerIcon::QuestionMark, trans('admin/egg.force_ip_help')),
|
||||
Hidden::make('script_is_privileged')
|
||||
->default(1),
|
||||
TagsInput::make('tags')
|
||||
->label(trans('admin/egg.tags'))
|
||||
->columnSpan(['default' => 1, 'sm' => 1, 'md' => 2, 'lg' => 2]),
|
||||
TextInput::make('update_url')
|
||||
->label(trans('admin/egg.update_url'))
|
||||
->hintIcon(TablerIcon::QuestionMark, trans('admin/egg.update_url_help'))
|
||||
->columnSpan(['default' => 1, 'sm' => 1, 'md' => 2, 'lg' => 2])
|
||||
->url(),
|
||||
KeyValue::make('docker_images')
|
||||
->label(trans('admin/egg.docker_images'))
|
||||
->live()
|
||||
->columnSpanFull()
|
||||
->required()
|
||||
->addActionLabel(trans('admin/egg.add_image'))
|
||||
->keyLabel(trans('admin/egg.docker_name'))
|
||||
->keyPlaceholder('Java 21')
|
||||
->valueLabel(trans('admin/egg.docker_uri'))
|
||||
->valuePlaceholder('ghcr.io/pelican-eggs/yolks:java_21')
|
||||
->helperText(trans('admin/egg.docker_help')),
|
||||
]),
|
||||
Tab::make('process_management')
|
||||
->label(trans('admin/egg.tabs.process_management'))
|
||||
->columns()
|
||||
->schema([
|
||||
CopyFrom::make('copy_process_from')
|
||||
->process(),
|
||||
TextInput::make('config_stop')
|
||||
->label(trans('admin/egg.stop_command'))
|
||||
->required()
|
||||
->maxLength(255)
|
||||
->helperText(trans('admin/egg.stop_command_help')),
|
||||
Textarea::make('config_startup')->rows(10)->json()
|
||||
->label(trans('admin/egg.start_config'))
|
||||
->default('{}')
|
||||
->helperText(trans('admin/egg.start_config_help')),
|
||||
Textarea::make('config_files')->rows(10)->json()
|
||||
->label(trans('admin/egg.config_files'))
|
||||
->default('{}')
|
||||
->helperText(trans('admin/egg.config_files_help')),
|
||||
Textarea::make('config_logs')->rows(10)->json()
|
||||
->label(trans('admin/egg.log_config'))
|
||||
->default('{}')
|
||||
->helperText(trans('admin/egg.log_config_help')),
|
||||
]),
|
||||
Tab::make('egg_variables')
|
||||
->label(trans('admin/egg.tabs.egg_variables'))
|
||||
->columnSpanFull()
|
||||
->schema([
|
||||
Repeater::make('variables')
|
||||
->hiddenLabel()
|
||||
->addActionLabel(trans('admin/egg.add_new_variable'))
|
||||
->grid()
|
||||
->relationship('variables')
|
||||
->orderColumn()
|
||||
->reorderAction(fn (Action $action) => $action->hiddenLabel()->tooltip(fn () => $action->getLabel()))
|
||||
->collapsible()->collapsed()
|
||||
->columnSpan(2)
|
||||
->defaultItems(0)
|
||||
->itemLabel(fn (array $state) => $state['name'])
|
||||
->mutateRelationshipDataBeforeCreateUsing(function (array $data): array {
|
||||
$data['default_value'] ??= '';
|
||||
$data['description'] ??= '';
|
||||
$data['rules'] ??= [];
|
||||
$data['user_viewable'] ??= '';
|
||||
$data['user_editable'] ??= '';
|
||||
|
||||
return $data;
|
||||
})
|
||||
->mutateRelationshipDataBeforeSaveUsing(function (array $data): array {
|
||||
$data['default_value'] ??= '';
|
||||
$data['description'] ??= '';
|
||||
$data['rules'] ??= [];
|
||||
$data['user_viewable'] ??= '';
|
||||
$data['user_editable'] ??= '';
|
||||
|
||||
return $data;
|
||||
})
|
||||
->schema([
|
||||
TextInput::make('name')
|
||||
->label(trans('admin/egg.name'))
|
||||
->required()
|
||||
->maxLength(255)
|
||||
->columnSpan(['default' => 1, 'sm' => 1, 'md' => 2, 'lg' => 2])
|
||||
->helperText(trans('admin/egg.name_help')),
|
||||
TextInput::make('author')
|
||||
->label(trans('admin/egg.author'))
|
||||
->maxLength(255)
|
||||
->required()
|
||||
->email()
|
||||
->columnSpan(['default' => 1, 'sm' => 1, 'md' => 2, 'lg' => 2])
|
||||
->helperText(trans('admin/egg.author_help')),
|
||||
Textarea::make('description')
|
||||
->label(trans('admin/egg.description'))
|
||||
->rows(2)
|
||||
->columnSpanFull()
|
||||
->helperText(trans('admin/egg.description_help')),
|
||||
KeyValue::make('startup_commands')
|
||||
->label(trans('admin/egg.startup_commands'))
|
||||
->live()
|
||||
->columnSpanFull()
|
||||
->required()
|
||||
->addActionLabel(trans('admin/egg.add_startup'))
|
||||
->keyLabel(trans('admin/egg.startup_name'))
|
||||
->keyPlaceholder('Default')
|
||||
->valueLabel(trans('admin/egg.startup_command'))
|
||||
->valuePlaceholder('java -Xms128M -XX:MaxRAMPercentage=95.0 -jar {{SERVER_JARFILE}}')
|
||||
->helperText(trans('admin/egg.startup_help')),
|
||||
TagsInput::make('file_denylist')
|
||||
->label(trans('admin/egg.file_denylist'))
|
||||
->placeholder('denied-file.txt')
|
||||
->helperText(trans('admin/egg.file_denylist_help'))
|
||||
->columnSpan(['default' => 1, 'sm' => 1, 'md' => 2, 'lg' => 2]),
|
||||
TagsInput::make('features')
|
||||
->label(trans('admin/egg.features'))
|
||||
->columnSpan(['default' => 1, 'sm' => 1, 'md' => 1, 'lg' => 1]),
|
||||
Toggle::make('force_outgoing_ip')
|
||||
->label(trans('admin/egg.force_ip'))
|
||||
->hintIcon('tabler-question-mark', trans('admin/egg.force_ip_help')),
|
||||
Hidden::make('script_is_privileged')
|
||||
->default(1),
|
||||
TagsInput::make('tags')
|
||||
->label(trans('admin/egg.tags'))
|
||||
->columnSpan(['default' => 1, 'sm' => 1, 'md' => 2, 'lg' => 2]),
|
||||
TextInput::make('update_url')
|
||||
->label(trans('admin/egg.update_url'))
|
||||
->hintIcon('tabler-question-mark', trans('admin/egg.update_url_help'))
|
||||
->columnSpan(['default' => 1, 'sm' => 1, 'md' => 2, 'lg' => 2])
|
||||
->url(),
|
||||
KeyValue::make('docker_images')
|
||||
->label(trans('admin/egg.docker_images'))
|
||||
->live()
|
||||
->columnSpanFull()
|
||||
->required()
|
||||
->addActionLabel(trans('admin/egg.add_image'))
|
||||
->keyLabel(trans('admin/egg.docker_name'))
|
||||
->keyPlaceholder('Java 21')
|
||||
->valueLabel(trans('admin/egg.docker_uri'))
|
||||
->valuePlaceholder('ghcr.io/pelican-eggs/yolks:java_21')
|
||||
->helperText(trans('admin/egg.docker_help')),
|
||||
]),
|
||||
|
||||
Tab::make('process_management')
|
||||
->label(trans('admin/egg.tabs.process_management'))
|
||||
->columns()
|
||||
->schema([
|
||||
CopyFrom::make('copy_process_from')
|
||||
->process(),
|
||||
TextInput::make('config_stop')
|
||||
->label(trans('admin/egg.stop_command'))
|
||||
->required()
|
||||
->debounce(750)
|
||||
->maxLength(255)
|
||||
->helperText(trans('admin/egg.stop_command_help')),
|
||||
Textarea::make('config_startup')->rows(10)->json()
|
||||
->label(trans('admin/egg.start_config'))
|
||||
->default('{}')
|
||||
->helperText(trans('admin/egg.start_config_help')),
|
||||
Textarea::make('config_files')->rows(10)->json()
|
||||
->label(trans('admin/egg.config_files'))
|
||||
->default('{}')
|
||||
->helperText(trans('admin/egg.config_files_help')),
|
||||
Textarea::make('config_logs')->rows(10)->json()
|
||||
->label(trans('admin/egg.log_config'))
|
||||
->default('{}')
|
||||
->helperText(trans('admin/egg.log_config_help')),
|
||||
]),
|
||||
Tab::make('egg_variables')
|
||||
->label(trans('admin/egg.tabs.egg_variables'))
|
||||
->columnSpanFull()
|
||||
->schema([
|
||||
Repeater::make('variables')
|
||||
->hiddenLabel()
|
||||
->addActionLabel(trans('admin/egg.add_new_variable'))
|
||||
->grid()
|
||||
->relationship('variables')
|
||||
->reorderable()->orderColumn()
|
||||
->collapsible()->collapsed()
|
||||
->columnSpan(2)
|
||||
->defaultItems(0)
|
||||
->itemLabel(fn (array $state) => $state['name'])
|
||||
->mutateRelationshipDataBeforeCreateUsing(function (array $data): array {
|
||||
$data['default_value'] ??= '';
|
||||
$data['description'] ??= '';
|
||||
$data['rules'] ??= [];
|
||||
$data['user_viewable'] ??= '';
|
||||
$data['user_editable'] ??= '';
|
||||
|
||||
return $data;
|
||||
})
|
||||
->mutateRelationshipDataBeforeSaveUsing(function (array $data): array {
|
||||
$data['default_value'] ??= '';
|
||||
$data['description'] ??= '';
|
||||
$data['rules'] ??= [];
|
||||
$data['user_viewable'] ??= '';
|
||||
$data['user_editable'] ??= '';
|
||||
|
||||
return $data;
|
||||
})
|
||||
->schema([
|
||||
TextInput::make('name')
|
||||
->label(trans('admin/egg.name'))
|
||||
->live()
|
||||
->debounce(750)
|
||||
->maxLength(255)
|
||||
->columnSpanFull()
|
||||
->afterStateUpdated(fn (Set $set, $state) => $set('env_variable', str($state)->trim()->snake()->upper()->toString()))
|
||||
->unique(modifyRuleUsing: fn (Unique $rule, Get $get) => $rule->where('egg_id', $get('../../id')))
|
||||
->validationMessages([
|
||||
'unique' => trans('admin/egg.error_unique'),
|
||||
])
|
||||
->required(),
|
||||
Textarea::make('description')->label(trans('admin/egg.description'))->columnSpanFull(),
|
||||
TextInput::make('env_variable')
|
||||
->label(trans('admin/egg.environment_variable'))
|
||||
->maxLength(255)
|
||||
->prefix('{{')
|
||||
->suffix('}}')
|
||||
->hintIcon('tabler-code', fn ($state) => "{{{$state}}}")
|
||||
->unique(modifyRuleUsing: fn (Unique $rule, Get $get) => $rule->where('egg_id', $get('../../id')))
|
||||
->rules(EggVariable::getRulesForField('env_variable'))
|
||||
->validationMessages([
|
||||
'unique' => trans('admin/egg.error_unique'),
|
||||
'required' => trans('admin/egg.error_required'),
|
||||
'*' => trans('admin/egg.error_reserved'),
|
||||
])
|
||||
->required(),
|
||||
TextInput::make('default_value')->label(trans('admin/egg.default_value')),
|
||||
Fieldset::make(trans('admin/egg.user_permissions'))
|
||||
->schema([
|
||||
Checkbox::make('user_viewable')->label(trans('admin/egg.viewable')),
|
||||
Checkbox::make('user_editable')->label(trans('admin/egg.editable')),
|
||||
]),
|
||||
TagsInput::make('rules')
|
||||
->label(trans('admin/egg.rules'))
|
||||
->columnSpanFull()
|
||||
->reorderable()
|
||||
->suggestions([
|
||||
'required',
|
||||
'nullable',
|
||||
'string',
|
||||
'integer',
|
||||
'numeric',
|
||||
'boolean',
|
||||
'alpha',
|
||||
'alpha_dash',
|
||||
'alpha_num',
|
||||
'url',
|
||||
'email',
|
||||
'regex:',
|
||||
'min:',
|
||||
'max:',
|
||||
'between:',
|
||||
'between:1024,65535',
|
||||
'in:',
|
||||
'in:true,false',
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
Tab::make('install_script')
|
||||
->label(trans('admin/egg.tabs.install_script'))
|
||||
->columns(3)
|
||||
->schema([
|
||||
CopyFrom::make('copy_script_from')
|
||||
->script(),
|
||||
TextInput::make('script_container')
|
||||
->label(trans('admin/egg.script_container'))
|
||||
->required()
|
||||
->maxLength(255)
|
||||
->default('ghcr.io/pelican-eggs/installers:debian'),
|
||||
Select::make('script_entry')
|
||||
->label(trans('admin/egg.script_entry'))
|
||||
->selectablePlaceholder(false)
|
||||
->default('bash')
|
||||
->options([
|
||||
'bash' => 'bash',
|
||||
'ash' => 'ash',
|
||||
'/bin/bash' => '/bin/bash',
|
||||
->columnSpanFull()
|
||||
->afterStateUpdated(fn (Set $set, $state) => $set('env_variable', str($state)->trim()->snake()->upper()->toString()))
|
||||
->unique(modifyRuleUsing: fn (Unique $rule, Get $get) => $rule->where('egg_id', $get('../../id')))
|
||||
->validationMessages([
|
||||
'unique' => trans('admin/egg.error_unique'),
|
||||
])
|
||||
->required(),
|
||||
MonacoEditor::make('script_install')
|
||||
->label(trans('admin/egg.script_install'))
|
||||
->language(EditorLanguages::shell)
|
||||
Textarea::make('description')->label(trans('admin/egg.description'))->columnSpanFull(),
|
||||
TextInput::make('env_variable')
|
||||
->label(trans('admin/egg.environment_variable'))
|
||||
->maxLength(255)
|
||||
->prefix('{{')
|
||||
->suffix('}}')
|
||||
->hintIcon(TablerIcon::Code, fn ($state) => "{{{$state}}}")
|
||||
->unique(modifyRuleUsing: fn (Unique $rule, Get $get) => $rule->where('egg_id', $get('../../id')))
|
||||
->rules(EggVariable::getRulesForField('env_variable'))
|
||||
->validationMessages([
|
||||
'unique' => trans('admin/egg.error_unique'),
|
||||
'required' => trans('admin/egg.error_required'),
|
||||
'*' => trans('admin/egg.error_reserved'),
|
||||
])
|
||||
->required(),
|
||||
TextInput::make('default_value')->label(trans('admin/egg.default_value')),
|
||||
Fieldset::make(trans('admin/egg.user_permissions'))
|
||||
->schema([
|
||||
Checkbox::make('user_viewable')->label(trans('admin/egg.viewable')),
|
||||
Checkbox::make('user_editable')->label(trans('admin/egg.editable')),
|
||||
]),
|
||||
TagsInput::make('rules')
|
||||
->label(trans('admin/egg.rules'))
|
||||
->columnSpanFull()
|
||||
->lazy(),
|
||||
->reorderable()
|
||||
->suggestions([
|
||||
'required',
|
||||
'nullable',
|
||||
'string',
|
||||
'integer',
|
||||
'numeric',
|
||||
'boolean',
|
||||
'alpha',
|
||||
'alpha_dash',
|
||||
'alpha_num',
|
||||
'url',
|
||||
'email',
|
||||
'regex:',
|
||||
'min:',
|
||||
'max:',
|
||||
'between:',
|
||||
'between:1024,65535',
|
||||
'in:',
|
||||
'in:true,false',
|
||||
]),
|
||||
]),
|
||||
|
||||
])->columnSpanFull()->persistTabInQueryString(),
|
||||
]);
|
||||
]),
|
||||
Tab::make('install_script')
|
||||
->label(trans('admin/egg.tabs.install_script'))
|
||||
->columns(3)
|
||||
->schema([
|
||||
CopyFrom::make('copy_script_from')
|
||||
->script(),
|
||||
TextInput::make('script_container')
|
||||
->label(trans('admin/egg.script_container'))
|
||||
->required()
|
||||
->maxLength(255)
|
||||
->default('ghcr.io/pelican-eggs/installers:debian'),
|
||||
Select::make('script_entry')
|
||||
->label(trans('admin/egg.script_entry'))
|
||||
->selectablePlaceholder(false)
|
||||
->default('bash')
|
||||
->options([
|
||||
'bash' => 'bash',
|
||||
'ash' => 'ash',
|
||||
'/bin/bash' => '/bin/bash',
|
||||
])
|
||||
->required(),
|
||||
MonacoEditor::make('script_install')
|
||||
->label(trans('admin/egg.script_install'))
|
||||
->language(EditorLanguages::shell)
|
||||
->columnSpanFull()
|
||||
->lazy(),
|
||||
]),
|
||||
];
|
||||
}
|
||||
|
||||
protected function handleRecordCreation(array $data): Model
|
||||
|
||||
@@ -3,21 +3,23 @@
|
||||
namespace App\Filament\Admin\Resources\Eggs\Pages;
|
||||
|
||||
use App\Enums\EditorLanguages;
|
||||
use App\Enums\TablerIcon;
|
||||
use App\Filament\Admin\Resources\Eggs\EggResource;
|
||||
use App\Filament\Components\Actions\DeleteIcon;
|
||||
use App\Filament\Components\Actions\ExportEggAction;
|
||||
use App\Filament\Components\Actions\ImportEggAction;
|
||||
use App\Filament\Components\Actions\UploadIcon;
|
||||
use App\Filament\Components\Forms\Fields\CopyFrom;
|
||||
use App\Filament\Components\Forms\Fields\MonacoEditor;
|
||||
use App\Models\Egg;
|
||||
use App\Models\EggVariable;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use Exception;
|
||||
use App\Traits\Filament\CanCustomizeTabs;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\ActionGroup;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Forms\Components\Checkbox;
|
||||
use Filament\Forms\Components\FileUpload;
|
||||
use Filament\Forms\Components\Hidden;
|
||||
use Filament\Forms\Components\KeyValue;
|
||||
use Filament\Forms\Components\Repeater;
|
||||
@@ -26,11 +28,8 @@ 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;
|
||||
@@ -38,400 +37,260 @@ 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\Support\Facades\Storage;
|
||||
use Illuminate\Validation\Rules\Unique;
|
||||
use Livewire\Features\SupportFileUploads\TemporaryUploadedFile;
|
||||
|
||||
class EditEgg extends EditRecord
|
||||
{
|
||||
use CanCustomizeHeaderActions;
|
||||
use CanCustomizeHeaderWidgets;
|
||||
use CanCustomizeTabs;
|
||||
|
||||
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')
|
||||
Tabs::make()
|
||||
->tabs($this->getTabs())
|
||||
->columnSpanFull()
|
||||
->persistTabInQueryString(),
|
||||
]);
|
||||
}
|
||||
|
||||
/** @return Tab[] */
|
||||
protected function getDefaultTabs(): array
|
||||
{
|
||||
return [
|
||||
Tab::make('configuration')
|
||||
->label(trans('admin/egg.tabs.configuration'))
|
||||
->columns(['default' => 2, 'sm' => 2, 'md' => 4, 'lg' => 6])
|
||||
->icon(TablerIcon::Egg)
|
||||
->schema([
|
||||
Grid::make(2)
|
||||
->columnStart(1)
|
||||
->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('imageUrl'),
|
||||
Hidden::make('imageExtension'),
|
||||
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);
|
||||
$set('imageUrl', null);
|
||||
$set('imageExtension', null);
|
||||
Image::make('', 'icon')
|
||||
->hidden(fn ($record) => !$record->icon)
|
||||
->url(fn ($record) => $record->icon)
|
||||
->imageSize(150)
|
||||
->columnSpanFull()
|
||||
->alignJustify(),
|
||||
UploadIcon::make(),
|
||||
DeleteIcon::make()
|
||||
->iconStoragePath(Egg::getIconStoragePath()),
|
||||
]),
|
||||
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(TablerIcon::QuestionMark, 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(TablerIcon::QuestionMark, 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(TablerIcon::ServerCog)
|
||||
->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'))
|
||||
->dehydrateStateUsing(fn ($state) => blank($state) ? '{}' : $state)
|
||||
->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(TablerIcon::Variable)
|
||||
->schema([
|
||||
Repeater::make('variables')
|
||||
->hiddenLabel()
|
||||
->grid()
|
||||
->relationship('variables')
|
||||
->orderColumn()
|
||||
->reorderAction(fn (Action $action) => $action->hiddenLabel()->tooltip(fn () => $action->getLabel()))
|
||||
->collapsible()->collapsed()
|
||||
->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;
|
||||
}
|
||||
return $data;
|
||||
})
|
||||
->mutateRelationshipDataBeforeSaveUsing(function (array $data): array {
|
||||
$data['default_value'] ??= '';
|
||||
$data['description'] ??= '';
|
||||
$data['rules'] ??= [];
|
||||
$data['user_viewable'] ??= '';
|
||||
$data['user_editable'] ??= '';
|
||||
|
||||
try {
|
||||
if (!filter_var($state, FILTER_VALIDATE_URL)) {
|
||||
throw new Exception(trans('admin/egg.import.invalid_url'));
|
||||
}
|
||||
|
||||
$extension = strtolower(pathinfo(parse_url($state, PHP_URL_PATH), PATHINFO_EXTENSION));
|
||||
|
||||
if (!array_key_exists($extension, Egg::IMAGE_FORMATS)) {
|
||||
throw new Exception(trans('admin/egg.import.unsupported_format', ['format' => implode(', ', array_keys(Egg::IMAGE_FORMATS))]));
|
||||
}
|
||||
|
||||
$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'));
|
||||
}
|
||||
|
||||
$set('imageUrl', $state);
|
||||
$set('imageExtension', $extension);
|
||||
$set('image_url_error', null);
|
||||
|
||||
} catch (Exception $e) {
|
||||
$set('image_url_error', $e->getMessage());
|
||||
$set('imageUrl', null);
|
||||
$set('imageExtension', 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(256)
|
||||
->maxFiles(1)
|
||||
->columnSpanFull()
|
||||
->alignCenter()
|
||||
->imageEditor()
|
||||
->image()
|
||||
->disk('public')
|
||||
->directory(Egg::ICON_STORAGE_PATH)
|
||||
->acceptedFileTypes([
|
||||
'image/png',
|
||||
'image/jpeg',
|
||||
'image/webp',
|
||||
'image/svg+xml',
|
||||
])
|
||||
->getUploadedFileNameForStorageUsing(function (TemporaryUploadedFile $file, $record) {
|
||||
return $record->uuid . '.' . $file->getClientOriginalExtension();
|
||||
}),
|
||||
]),
|
||||
]),
|
||||
])
|
||||
->action(function (array $data, $record): void {
|
||||
if (!empty($data['imageUrl']) && !empty($data['imageExtension'])) {
|
||||
$this->saveImageFromUrl($data['imageUrl'], $data['imageExtension'], $record);
|
||||
|
||||
Notification::make()
|
||||
->title(trans('admin/egg.import.image_updated'))
|
||||
->success()
|
||||
->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!empty($data['image'])) {
|
||||
Notification::make()
|
||||
->title(trans('admin/egg.import.image_updated'))
|
||||
->success()
|
||||
->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (empty($data['imageUrl']) && empty($data['image'])) {
|
||||
Notification::make()
|
||||
->title(trans('admin/egg.import.no_image'))
|
||||
->warning()
|
||||
->send();
|
||||
}
|
||||
}),
|
||||
Action::make('delete_image')
|
||||
->visible(fn ($record) => $record->image)
|
||||
->hiddenLabel()
|
||||
->icon('tabler-trash')
|
||||
->iconButton()
|
||||
->iconSize(IconSize::Large)
|
||||
->color('danger')
|
||||
->action(function ($record) {
|
||||
foreach (array_keys(Egg::IMAGE_FORMATS) as $ext) {
|
||||
$path = Egg::ICON_STORAGE_PATH . "/$record->uuid.$ext";
|
||||
if (Storage::disk('public')->exists($path)) {
|
||||
Storage::disk('public')->delete($path);
|
||||
}
|
||||
}
|
||||
|
||||
Notification::make()
|
||||
->title(trans('admin/egg.import.image_deleted'))
|
||||
->success()
|
||||
->send();
|
||||
|
||||
$record->refresh();
|
||||
}),
|
||||
]),
|
||||
]),
|
||||
return $data;
|
||||
})
|
||||
->schema([
|
||||
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'))
|
||||
->debounce(750)
|
||||
->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',
|
||||
->columnSpanFull()
|
||||
->afterStateUpdated(fn (Set $set, $state) => $set('env_variable', str($state)->trim()->snake()->upper()->toString()))
|
||||
->unique(modifyRuleUsing: fn (Unique $rule, Get $get) => $rule->where('egg_id', $get('../../id')))
|
||||
->validationMessages([
|
||||
'unique' => trans('admin/egg.error_unique'),
|
||||
])
|
||||
->required(),
|
||||
MonacoEditor::make('script_install')
|
||||
->hiddenLabel()
|
||||
->language(EditorLanguages::shell)
|
||||
->columnSpanFull(),
|
||||
Textarea::make('description')->label(trans('admin/egg.description'))->columnSpanFull(),
|
||||
TextInput::make('env_variable')
|
||||
->label(trans('admin/egg.environment_variable'))
|
||||
->maxLength(255)
|
||||
->prefix('{{')
|
||||
->suffix('}}')
|
||||
->hintIcon(TablerIcon::Code, fn ($state) => "{{{$state}}}")
|
||||
->unique(modifyRuleUsing: fn (Unique $rule, Get $get) => $rule->where('egg_id', $get('../../id')))
|
||||
->rules(EggVariable::getRulesForField('env_variable'))
|
||||
->validationMessages([
|
||||
'unique' => trans('admin/egg.error_unique'),
|
||||
'required' => trans('admin/egg.error_required'),
|
||||
'*' => trans('admin/egg.error_reserved'),
|
||||
])
|
||||
->required(),
|
||||
TextInput::make('default_value')->label(trans('admin/egg.default_value')),
|
||||
Fieldset::make(trans('admin/egg.user_permissions'))
|
||||
->schema([
|
||||
Checkbox::make('user_viewable')->label(trans('admin/egg.viewable')),
|
||||
Checkbox::make('user_editable')->label(trans('admin/egg.editable')),
|
||||
]),
|
||||
TagsInput::make('rules')
|
||||
->label(trans('admin/egg.rules'))
|
||||
->columnSpanFull()
|
||||
->reorderable()
|
||||
->suggestions([
|
||||
'required',
|
||||
'nullable',
|
||||
'string',
|
||||
'integer',
|
||||
'numeric',
|
||||
'boolean',
|
||||
'alpha',
|
||||
'alpha_dash',
|
||||
'alpha_num',
|
||||
'url',
|
||||
'email',
|
||||
'regex:',
|
||||
'min:',
|
||||
'max:',
|
||||
'between:',
|
||||
'between:1024,65535',
|
||||
'in:',
|
||||
'in:true,false',
|
||||
]),
|
||||
]),
|
||||
])->columnSpanFull()->persistTabInQueryString(),
|
||||
]);
|
||||
]),
|
||||
Tab::make('install_script')
|
||||
->label(trans('admin/egg.tabs.install_script'))
|
||||
->columns(3)
|
||||
->icon(TablerIcon::FileDownload)
|
||||
->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(),
|
||||
MonacoEditor::make('script_install')
|
||||
->hiddenLabel()
|
||||
->language(EditorLanguages::shell)
|
||||
->columnSpanFull(),
|
||||
]),
|
||||
];
|
||||
}
|
||||
|
||||
/** @return array<Action|ActionGroup> */
|
||||
@@ -440,14 +299,16 @@ class EditEgg extends EditRecord
|
||||
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'))
|
||||
->iconButton()->iconSize(IconSize::ExtraLarge),
|
||||
->tooltip(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')
|
||||
->iconButton()->iconSize(IconSize::ExtraLarge)
|
||||
->icon('tabler-device-floppy'),
|
||||
Action::make('save')
|
||||
->hiddenLabel()
|
||||
->action('save')
|
||||
->keyBindings(['mod+s'])
|
||||
->tooltip(trans('filament-panels::resources/pages/edit-record.form.actions.save.label'))
|
||||
->icon(TablerIcon::DeviceFloppy),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -456,37 +317,6 @@ class EditEgg extends EditRecord
|
||||
$this->fillForm();
|
||||
}
|
||||
|
||||
/**
|
||||
* Save an image from URL download to a file.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
private function saveImageFromUrl(string $imageUrl, string $extension, Egg $egg): void
|
||||
{
|
||||
$context = stream_context_create([
|
||||
'http' => ['timeout' => 3],
|
||||
'https' => [
|
||||
'timeout' => 3,
|
||||
'verify_peer' => true,
|
||||
'verify_peer_name' => true,
|
||||
],
|
||||
]);
|
||||
|
||||
$data = @file_get_contents($imageUrl, false, $context, 0, 1048576); // 1024KB
|
||||
|
||||
if (empty($data)) {
|
||||
throw new Exception(trans('admin/egg.import.invalid_url'));
|
||||
}
|
||||
|
||||
$normalizedExtension = match ($extension) {
|
||||
'svg+xml' => 'svg',
|
||||
'jpeg' => 'jpg',
|
||||
default => $extension,
|
||||
};
|
||||
|
||||
Storage::disk('public')->put(Egg::ICON_STORAGE_PATH . "/$egg->uuid.$normalizedExtension", $data);
|
||||
}
|
||||
|
||||
protected function getFormActions(): array
|
||||
{
|
||||
return [];
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Filament\Admin\Resources\Eggs\Pages;
|
||||
|
||||
use App\Enums\TablerIcon;
|
||||
use App\Filament\Admin\Resources\Eggs\EggResource;
|
||||
use App\Filament\Components\Actions\ExportEggAction;
|
||||
use App\Filament\Components\Actions\ImportEggAction;
|
||||
@@ -12,17 +13,17 @@ 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\BulkActionGroup;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Actions\ReplicateAction;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
use Filament\Support\Enums\IconSize;
|
||||
use Filament\Tables\Columns\ImageColumn;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class ListEggs extends ListRecords
|
||||
@@ -37,6 +38,8 @@ class ListEggs extends ListRecords
|
||||
*/
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
$defaultEggIcon = 'data:image/svg+xml;base64,' . base64_encode(file_get_contents(public_path('pelican.svg')));
|
||||
|
||||
return $table
|
||||
->searchable(true)
|
||||
->defaultPaginationPageOption(25)
|
||||
@@ -44,13 +47,11 @@ class ListEggs extends ListRecords
|
||||
TextColumn::make('id')
|
||||
->label('Id')
|
||||
->hidden(),
|
||||
ImageColumn::make('image')
|
||||
ImageColumn::make('icon')
|
||||
->label('')
|
||||
->alignCenter()
|
||||
->circular()
|
||||
->getStateUsing(fn ($record) => $record->image
|
||||
? $record->image
|
||||
: 'data:image/svg+xml;base64,' . base64_encode(file_get_contents(public_path('pelican.svg')))),
|
||||
->getStateUsing(fn (Egg $record) => $record->icon ?: $defaultEggIcon),
|
||||
TextColumn::make('name')
|
||||
->label(trans('admin/egg.name'))
|
||||
->description(fn ($record): ?string => (strlen($record->description) > 120) ? substr($record->description, 0, 120).'...' : $record->description)
|
||||
@@ -63,19 +64,13 @@ class ListEggs extends ListRecords
|
||||
])
|
||||
->recordActions([
|
||||
EditAction::make()
|
||||
->iconButton()
|
||||
->tooltip(trans('filament-actions::edit.single.label'))
|
||||
->iconSize(IconSize::Large),
|
||||
->tooltip(trans('filament-actions::edit.single.label')),
|
||||
ExportEggAction::make()
|
||||
->tooltip(trans('filament-actions::export.modal.actions.export.label'))
|
||||
->iconSize(IconSize::Large),
|
||||
->tooltip(trans('filament-actions::export.modal.actions.export.label')),
|
||||
UpdateEggAction::make()
|
||||
->tooltip(trans_choice('admin/egg.update', 1))
|
||||
->iconSize(IconSize::Large),
|
||||
->tooltip(trans_choice('admin/egg.update', 1)),
|
||||
ReplicateAction::make()
|
||||
->iconButton()
|
||||
->tooltip(trans('filament-actions::replicate.single.label'))
|
||||
->iconSize(IconSize::Large)
|
||||
->modal(false)
|
||||
->excludeAttributes(['author', 'uuid', 'update_url', 'servers_count', 'created_at', 'updated_at'])
|
||||
->beforeReplicaSaved(function (Egg $replica) {
|
||||
@@ -86,19 +81,54 @@ class ListEggs extends ListRecords
|
||||
->after(fn (Egg $record, Egg $replica) => $record->variables->each(fn ($variable) => $variable->replicate()->fill(['egg_id' => $replica->id])->save()))
|
||||
->successRedirectUrl(fn (Egg $replica) => EditEgg::getUrl(['record' => $replica])),
|
||||
])
|
||||
->groupedBulkActions([
|
||||
DeleteBulkAction::make()
|
||||
->before(fn (&$records) => $records = $records->filter(function ($egg) {
|
||||
/** @var Egg $egg */
|
||||
return $egg->servers_count <= 0;
|
||||
})),
|
||||
UpdateEggBulkAction::make()
|
||||
->before(fn (&$records) => $records = $records->filter(function ($egg) {
|
||||
/** @var Egg $egg */
|
||||
return cache()->get("eggs.$egg->uuid.update", false);
|
||||
})),
|
||||
->toolbarActions([
|
||||
ImportEggAction::make()
|
||||
->multiple(),
|
||||
CreateAction::make(),
|
||||
BulkActionGroup::make([
|
||||
DeleteBulkAction::make('exclude_bulk_delete')
|
||||
->before(function (Collection &$records) {
|
||||
$eggsWithServers = $records->filter(fn (Egg $egg) => $egg->servers_count > 0);
|
||||
|
||||
if ($eggsWithServers->isNotEmpty()) {
|
||||
$eggNames = $eggsWithServers->map(fn (Egg $egg) => sprintf('%s (%d server%s)', $egg->name, $egg->servers_count, $egg->servers_count > 1 ? 's' : ''))
|
||||
->join(', ');
|
||||
Notification::make()
|
||||
->danger()
|
||||
->title(trans('admin/egg.cannot_delete', ['count' => $eggsWithServers->count()]))
|
||||
->body(trans('admin/egg.eggs_have_servers', ['eggs' => $eggNames]))
|
||||
->send();
|
||||
}
|
||||
|
||||
$records = $records->filter(fn (Egg $egg) => $egg->servers_count <= 0);
|
||||
|
||||
if ($records->isEmpty()) {
|
||||
$this->halt();
|
||||
}
|
||||
}),
|
||||
UpdateEggBulkAction::make('exclude_bulk_update')
|
||||
->before(function (Collection &$records) {
|
||||
$eggsWithoutUpdateUrl = $records->filter(fn (Egg $egg) => $egg->update_url === null);
|
||||
|
||||
if ($eggsWithoutUpdateUrl->isNotEmpty()) {
|
||||
$eggNames = $eggsWithoutUpdateUrl->pluck('name')->join(', ');
|
||||
|
||||
Notification::make()
|
||||
->warning()
|
||||
->title(trans('admin/egg.cannot_update', ['count' => $eggsWithoutUpdateUrl->count()]))
|
||||
->body(trans('admin/egg.no_update_url', ['eggs' => $eggNames]))
|
||||
->send();
|
||||
}
|
||||
|
||||
$records = $records->filter(fn (Egg $egg) => $egg->update_url !== null);
|
||||
|
||||
if ($records->isEmpty()) {
|
||||
$this->halt();
|
||||
}
|
||||
}),
|
||||
]),
|
||||
])
|
||||
->emptyStateIcon('tabler-eggs')
|
||||
->emptyStateIcon(TablerIcon::Eggs)
|
||||
->emptyStateDescription('')
|
||||
->emptyStateHeading(trans('admin/egg.no_eggs'))
|
||||
->filters([
|
||||
@@ -106,18 +136,4 @@ class ListEggs extends ListRecords
|
||||
->model(Egg::class),
|
||||
]);
|
||||
}
|
||||
|
||||
/** @return array<Action|ActionGroup>
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function getDefaultHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
ImportEggAction::make()
|
||||
->multiple(),
|
||||
CreateAction::make()
|
||||
->icon('tabler-file-plus')
|
||||
->iconButton()->iconSize(IconSize::ExtraLarge),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Filament\Admin\Resources\Mounts;
|
||||
|
||||
use App\Enums\TablerIcon;
|
||||
use App\Filament\Admin\Resources\Mounts\Pages\CreateMount;
|
||||
use App\Filament\Admin\Resources\Mounts\Pages\EditMount;
|
||||
use App\Filament\Admin\Resources\Mounts\Pages\ListMounts;
|
||||
@@ -11,7 +12,10 @@ use App\Traits\Filament\CanCustomizePages;
|
||||
use App\Traits\Filament\CanCustomizeRelations;
|
||||
use App\Traits\Filament\CanModifyForm;
|
||||
use App\Traits\Filament\CanModifyTable;
|
||||
use BackedEnum;
|
||||
use Exception;
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Actions\ViewAction;
|
||||
@@ -21,7 +25,6 @@ use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\ToggleButtons;
|
||||
use Filament\Resources\Pages\PageRegistration;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Components\Group;
|
||||
use Filament\Schemas\Components\Section;
|
||||
use Filament\Schemas\Components\StateCasts\BooleanStateCast;
|
||||
use Filament\Schemas\Schema;
|
||||
@@ -38,7 +41,7 @@ class MountResource extends Resource
|
||||
|
||||
protected static ?string $model = Mount::class;
|
||||
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'tabler-layers-linked';
|
||||
protected static string|BackedEnum|null $navigationIcon = TablerIcon::LayersLinked;
|
||||
|
||||
protected static ?string $recordTitleAttribute = 'name';
|
||||
|
||||
@@ -89,19 +92,28 @@ class MountResource extends Resource
|
||||
TextColumn::make('read_only')
|
||||
->label(trans('admin/mount.table.read_only'))
|
||||
->badge()
|
||||
->icon(fn ($state) => $state ? 'tabler-writing-off' : 'tabler-writing')
|
||||
->icon(fn ($state) => $state ? TablerIcon::WritingOff : TablerIcon::Writing)
|
||||
->color(fn ($state) => $state ? 'success' : 'warning')
|
||||
->formatStateUsing(fn ($state) => $state ? trans('admin/mount.toggles.read_only') : trans('admin/mount.toggles.writable')),
|
||||
TextColumn::make('user_mountable')
|
||||
->label(trans('admin/mount.table.user_mountable'))
|
||||
->badge()
|
||||
->icon(fn ($state) => $state ? TablerIcon::User : TablerIcon::UserOff)
|
||||
->color(fn ($state) => $state ? 'success' : 'warning')
|
||||
->formatStateUsing(fn ($state) => $state ? trans('admin/mount.toggles.user_mountable') : trans('admin/mount.toggles.not_user_mountable')),
|
||||
])
|
||||
->recordActions([
|
||||
ViewAction::make()
|
||||
->hidden(fn ($record) => static::getEditAuthorizationResponse($record)->allowed()),
|
||||
EditAction::make(),
|
||||
])
|
||||
->groupedBulkActions([
|
||||
DeleteBulkAction::make(),
|
||||
->toolbarActions([
|
||||
CreateAction::make(),
|
||||
BulkActionGroup::make([
|
||||
DeleteBulkAction::make('exclude_bulk_delete'),
|
||||
]),
|
||||
])
|
||||
->emptyStateIcon('tabler-layers-linked')
|
||||
->emptyStateIcon(TablerIcon::LayersLinked)
|
||||
->emptyStateDescription('')
|
||||
->emptyStateHeading(trans('admin/mount.no_mounts'));
|
||||
}
|
||||
@@ -118,7 +130,8 @@ class MountResource extends Resource
|
||||
->label(trans('admin/mount.name'))
|
||||
->required()
|
||||
->helperText(trans('admin/mount.name_help'))
|
||||
->maxLength(64),
|
||||
->maxLength(64)
|
||||
->columnSpanFull(),
|
||||
ToggleButtons::make('read_only')
|
||||
->label(trans('admin/mount.read_only'))
|
||||
->helperText(trans('admin/mount.read_only_help'))
|
||||
@@ -128,8 +141,8 @@ class MountResource extends Resource
|
||||
true => trans('admin/mount.toggles.read_only'),
|
||||
])
|
||||
->icons([
|
||||
false => 'tabler-writing',
|
||||
true => 'tabler-writing-off',
|
||||
false => TablerIcon::Writing,
|
||||
true => TablerIcon::WritingOff,
|
||||
])
|
||||
->colors([
|
||||
false => 'warning',
|
||||
@@ -137,6 +150,24 @@ class MountResource extends Resource
|
||||
])
|
||||
->inline()
|
||||
->default(false),
|
||||
ToggleButtons::make('user_mountable')
|
||||
->label(trans('admin/mount.user_mountable'))
|
||||
->helperText(trans('admin/mount.user_mountable_help'))
|
||||
->stateCast(new BooleanStateCast(false, true))
|
||||
->options([
|
||||
false => trans('admin/mount.toggles.not_user_mountable'),
|
||||
true => trans('admin/mount.toggles.user_mountable'),
|
||||
])
|
||||
->icons([
|
||||
false => TablerIcon::UserOff,
|
||||
true => TablerIcon::User,
|
||||
])
|
||||
->colors([
|
||||
false => 'warning',
|
||||
true => 'success',
|
||||
])
|
||||
->inline()
|
||||
->default(true),
|
||||
TextInput::make('source')
|
||||
->label(trans('admin/mount.source'))
|
||||
->required()
|
||||
@@ -151,30 +182,32 @@ class MountResource extends Resource
|
||||
->label(trans('admin/mount.description'))
|
||||
->helperText(trans('admin/mount.description_help'))
|
||||
->columnSpanFull(),
|
||||
])->columnSpan(1)->columns([
|
||||
'default' => 1,
|
||||
'lg' => 2,
|
||||
]),
|
||||
Group::make()->schema([
|
||||
Section::make()->schema([
|
||||
Select::make('eggs')->multiple()
|
||||
->label(trans('admin/mount.eggs'))
|
||||
// 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', user()?->accessibleNodes()->pluck('id')))
|
||||
->searchable(['name', 'fqdn'])
|
||||
->preload(),
|
||||
])
|
||||
->columnSpan([
|
||||
'default' => 1,
|
||||
'lg' => 2,
|
||||
])
|
||||
->columns([
|
||||
'default' => 1,
|
||||
'xl' => 2,
|
||||
]),
|
||||
])->columns([
|
||||
'default' => 1,
|
||||
'lg' => 2,
|
||||
Section::make()->schema([
|
||||
Select::make('eggs')
|
||||
->multiple()
|
||||
->label(trans('admin/mount.eggs'))
|
||||
// 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', user()?->accessibleNodes()->pluck('id')))
|
||||
->searchable(['name', 'fqdn'])
|
||||
->preload(),
|
||||
]),
|
||||
])->columns([
|
||||
'default' => 1,
|
||||
'lg' => 2,
|
||||
'lg' => 3,
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
|
||||
namespace App\Filament\Admin\Resources\Mounts\Pages;
|
||||
|
||||
use App\Enums\TablerIcon;
|
||||
use App\Filament\Admin\Resources\Mounts\MountResource;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\ActionGroup;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
use Filament\Support\Enums\IconSize;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
@@ -25,9 +25,12 @@ class CreateMount extends CreateRecord
|
||||
protected function getDefaultHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
$this->getCreateFormAction()->formId('form')
|
||||
->iconButton()->iconSize(IconSize::ExtraLarge)
|
||||
->icon('tabler-file-plus'),
|
||||
Action::make('create')
|
||||
->hiddenLabel()
|
||||
->action('create')
|
||||
->keyBindings(['mod+s'])
|
||||
->tooltip(trans('filament-panels::resources/pages/create-record.form.actions.create.label'))
|
||||
->icon(TablerIcon::FilePlus),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -39,7 +42,6 @@ class CreateMount extends CreateRecord
|
||||
protected function handleRecordCreation(array $data): Model
|
||||
{
|
||||
$data['uuid'] ??= Str::uuid()->toString();
|
||||
$data['user_mountable'] = 1;
|
||||
|
||||
return parent::handleRecordCreation($data);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Filament\Admin\Resources\Mounts\Pages;
|
||||
|
||||
use App\Enums\TablerIcon;
|
||||
use App\Filament\Admin\Resources\Mounts\MountResource;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
@@ -9,7 +10,6 @@ use Filament\Actions\Action;
|
||||
use Filament\Actions\ActionGroup;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
use Filament\Support\Enums\IconSize;
|
||||
|
||||
class EditMount extends EditRecord
|
||||
{
|
||||
@@ -22,11 +22,13 @@ class EditMount extends EditRecord
|
||||
protected function getDefaultHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
DeleteAction::make()
|
||||
->iconButton()->iconSize(IconSize::ExtraLarge),
|
||||
$this->getSaveFormAction()->formId('form')
|
||||
->iconButton()->iconSize(IconSize::ExtraLarge)
|
||||
->icon('tabler-device-floppy'),
|
||||
DeleteAction::make(),
|
||||
Action::make('save')
|
||||
->hiddenLabel()
|
||||
->action('save')
|
||||
->keyBindings(['mod+s'])
|
||||
->tooltip(trans('filament-panels::resources/pages/edit-record.form.actions.save.label'))
|
||||
->icon(TablerIcon::DeviceFloppy),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -5,11 +5,7 @@ namespace App\Filament\Admin\Resources\Mounts\Pages;
|
||||
use App\Filament\Admin\Resources\Mounts\MountResource;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\ActionGroup;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
use Filament\Support\Enums\IconSize;
|
||||
|
||||
class ListMounts extends ListRecords
|
||||
{
|
||||
@@ -17,14 +13,4 @@ class ListMounts extends ListRecords
|
||||
use CanCustomizeHeaderWidgets;
|
||||
|
||||
protected static string $resource = MountResource::class;
|
||||
|
||||
/** @return array<Action|ActionGroup> */
|
||||
protected function getDefaultHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
CreateAction::make()
|
||||
->iconButton()->iconSize(IconSize::ExtraLarge)
|
||||
->icon('tabler-file-plus'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ use Filament\Actions\Action;
|
||||
use Filament\Actions\ActionGroup;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Resources\Pages\ViewRecord;
|
||||
use Filament\Support\Enums\IconSize;
|
||||
|
||||
class ViewMount extends ViewRecord
|
||||
{
|
||||
@@ -22,9 +21,7 @@ class ViewMount extends ViewRecord
|
||||
protected function getDefaultHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
EditAction::make()
|
||||
->iconSize(IconSize::ExtraLarge)
|
||||
->iconButton(),
|
||||
EditAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Filament\Admin\Resources\Nodes;
|
||||
|
||||
use App\Enums\CustomizationKey;
|
||||
use App\Enums\TablerIcon;
|
||||
use App\Filament\Admin\Resources\Nodes\Pages\CreateNode;
|
||||
use App\Filament\Admin\Resources\Nodes\Pages\EditNode;
|
||||
use App\Filament\Admin\Resources\Nodes\Pages\ListNodes;
|
||||
@@ -11,6 +12,7 @@ use App\Filament\Admin\Resources\Nodes\RelationManagers\ServersRelationManager;
|
||||
use App\Models\Node;
|
||||
use App\Traits\Filament\CanCustomizePages;
|
||||
use App\Traits\Filament\CanCustomizeRelations;
|
||||
use BackedEnum;
|
||||
use Filament\Resources\Pages\PageRegistration;
|
||||
use Filament\Resources\RelationManagers\RelationManager;
|
||||
use Filament\Resources\Resource;
|
||||
@@ -23,7 +25,7 @@ class NodeResource extends Resource
|
||||
|
||||
protected static ?string $model = Node::class;
|
||||
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'tabler-server-2';
|
||||
protected static string|BackedEnum|null $navigationIcon = TablerIcon::Server2;
|
||||
|
||||
protected static ?string $recordTitleAttribute = 'name';
|
||||
|
||||
|
||||
@@ -2,11 +2,12 @@
|
||||
|
||||
namespace App\Filament\Admin\Resources\Nodes\Pages;
|
||||
|
||||
use App\Enums\TablerIcon;
|
||||
use App\Filament\Admin\Resources\Nodes\NodeResource;
|
||||
use App\Models\Node;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use Exception;
|
||||
use App\Traits\Filament\CanCustomizeSteps;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Forms\Components\Hidden;
|
||||
use Filament\Forms\Components\TagsInput;
|
||||
@@ -27,394 +28,406 @@ class CreateNode extends CreateRecord
|
||||
{
|
||||
use CanCustomizeHeaderActions;
|
||||
use CanCustomizeHeaderWidgets;
|
||||
use CanCustomizeSteps;
|
||||
|
||||
protected static string $resource = NodeResource::class;
|
||||
|
||||
protected static bool $canCreateAnother = false;
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
Wizard::make([
|
||||
Step::make('basic')
|
||||
->label(trans('admin/node.tabs.basic_settings'))
|
||||
->icon('tabler-server')
|
||||
->columnSpanFull()
|
||||
->columns([
|
||||
'default' => 2,
|
||||
'sm' => 3,
|
||||
'md' => 3,
|
||||
'lg' => 4,
|
||||
])
|
||||
->schema([
|
||||
TextInput::make('fqdn')
|
||||
->columnSpan(2)
|
||||
->required()
|
||||
->autofocus()
|
||||
->live(debounce: 1500)
|
||||
->rules(Node::getRulesForField('fqdn'))
|
||||
->prohibited(fn ($state) => is_ip($state) && request()->isSecure())
|
||||
->label(fn ($state) => is_ip($state) ? trans('admin/node.ip_address') : trans('admin/node.domain'))
|
||||
->placeholder(fn ($state) => is_ip($state) ? '192.168.1.1' : 'node.example.com')
|
||||
->helperText(function ($state) {
|
||||
if (is_ip($state)) {
|
||||
if (request()->isSecure()) {
|
||||
return trans('admin/node.fqdn_help');
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
return trans('admin/node.error');
|
||||
})
|
||||
->hintColor('danger')
|
||||
->hint(function ($state) {
|
||||
if (is_ip($state) && request()->isSecure()) {
|
||||
return trans('admin/node.ssl_ip');
|
||||
}
|
||||
|
||||
return '';
|
||||
})
|
||||
->afterStateUpdated(function (Set $set, ?string $state) {
|
||||
$set('dns', null);
|
||||
$set('ip', null);
|
||||
|
||||
[$subdomain] = str($state)->explode('.', 2);
|
||||
if (!is_numeric($subdomain)) {
|
||||
$set('name', $subdomain);
|
||||
}
|
||||
|
||||
if (!$state || is_ip($state)) {
|
||||
$set('dns', null);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$ip = get_ip_from_hostname($state);
|
||||
if ($ip) {
|
||||
$set('dns', true);
|
||||
|
||||
$set('ip', $ip);
|
||||
} else {
|
||||
$set('dns', false);
|
||||
}
|
||||
})
|
||||
->maxLength(255),
|
||||
|
||||
TextInput::make('ip')
|
||||
->disabled()
|
||||
->hidden(),
|
||||
|
||||
ToggleButtons::make('dns')
|
||||
->label(trans('admin/node.dns'))
|
||||
->helperText(trans('admin/node.dns_help'))
|
||||
->disabled()
|
||||
->inline()
|
||||
->default(null)
|
||||
->hint(fn (Get $get) => $get('ip'))
|
||||
->hintColor('success')
|
||||
->options([
|
||||
true => trans('admin/node.valid'),
|
||||
false => trans('admin/node.invalid'),
|
||||
])
|
||||
->colors([
|
||||
true => 'success',
|
||||
false => 'danger',
|
||||
])
|
||||
->columnSpan([
|
||||
'default' => 1,
|
||||
'sm' => 1,
|
||||
'md' => 1,
|
||||
'lg' => 1,
|
||||
]),
|
||||
|
||||
TextInput::make('daemon_connect')
|
||||
->columnSpan(1)
|
||||
->label(fn (Get $get) => $get('connection') === 'https_proxy' ? trans('admin/node.connect_port') : trans('admin/node.port'))
|
||||
->helperText(fn (Get $get) => $get('connection') === 'https_proxy' ? trans('admin/node.connect_port_help') : trans('admin/node.port_help'))
|
||||
->minValue(1)
|
||||
->maxValue(65535)
|
||||
->default(8080)
|
||||
->required()
|
||||
->integer(),
|
||||
|
||||
TextInput::make('name')
|
||||
->label(trans('admin/node.display_name'))
|
||||
->columnSpan([
|
||||
'default' => 1,
|
||||
'sm' => 1,
|
||||
'md' => 1,
|
||||
'lg' => 2,
|
||||
])
|
||||
->required()
|
||||
->maxLength(100),
|
||||
|
||||
Hidden::make('scheme')
|
||||
->default(fn () => request()->isSecure() ? 'https' : 'http'),
|
||||
|
||||
Hidden::make('behind_proxy')
|
||||
->default(false),
|
||||
|
||||
ToggleButtons::make('connection')
|
||||
->label(trans('admin/node.ssl'))
|
||||
->columnSpan(1)
|
||||
->inline()
|
||||
->helperText(function (Get $get) {
|
||||
if (request()->isSecure()) {
|
||||
return new HtmlString(trans('admin/node.panel_on_ssl'));
|
||||
}
|
||||
|
||||
if (is_ip($get('fqdn'))) {
|
||||
return trans('admin/node.ssl_help');
|
||||
}
|
||||
|
||||
return '';
|
||||
})
|
||||
->disableOptionWhen(fn (string $value) => $value === 'http' && request()->isSecure())
|
||||
->options([
|
||||
'http' => 'HTTP',
|
||||
'https' => 'HTTPS (SSL)',
|
||||
'https_proxy' => 'HTTPS with (reverse) proxy',
|
||||
])
|
||||
->colors([
|
||||
'http' => 'warning',
|
||||
'https' => 'success',
|
||||
'https_proxy' => 'success',
|
||||
])
|
||||
->icons([
|
||||
'http' => 'tabler-lock-open-off',
|
||||
'https' => 'tabler-lock',
|
||||
'https_proxy' => 'tabler-shield-lock',
|
||||
])
|
||||
->default(fn () => request()->isSecure() ? 'https' : 'http')
|
||||
->live()
|
||||
->dehydrated(false)
|
||||
->afterStateUpdated(function ($state, Set $set) {
|
||||
$set('scheme', $state === 'http' ? 'http' : 'https');
|
||||
$set('behind_proxy', $state === 'https_proxy');
|
||||
|
||||
$set('daemon_connect', $state === 'https_proxy' ? 443 : 8080);
|
||||
$set('daemon_listen', 8080);
|
||||
}),
|
||||
|
||||
TextInput::make('daemon_listen')
|
||||
->columnSpan(1)
|
||||
->label(trans('admin/node.listen_port'))
|
||||
->helperText(trans('admin/node.listen_port_help'))
|
||||
->minValue(1)
|
||||
->maxValue(65535)
|
||||
->default(8080)
|
||||
->required()
|
||||
->integer()
|
||||
->visible(fn (Get $get) => $get('connection') === 'https_proxy'),
|
||||
]),
|
||||
Step::make('advanced')
|
||||
->label(trans('admin/node.tabs.advanced_settings'))
|
||||
->icon('tabler-server-cog')
|
||||
->columnSpanFull()
|
||||
->columns([
|
||||
'default' => 2,
|
||||
'sm' => 3,
|
||||
'md' => 3,
|
||||
'lg' => 4,
|
||||
])
|
||||
->schema([
|
||||
ToggleButtons::make('maintenance_mode')
|
||||
->label(trans('admin/node.maintenance_mode'))->inline()
|
||||
->columnSpan(1)
|
||||
->default(false)
|
||||
->hintIcon('tabler-question-mark', trans('admin/node.maintenance_mode_help'))
|
||||
->options([
|
||||
true => trans('admin/node.enabled'),
|
||||
false => trans('admin/node.disabled'),
|
||||
])
|
||||
->colors([
|
||||
true => 'danger',
|
||||
false => 'success',
|
||||
]),
|
||||
ToggleButtons::make('public')
|
||||
->default(true)
|
||||
->columnSpan(1)
|
||||
->label(trans('admin/node.use_for_deploy'))->inline()
|
||||
->options([
|
||||
true => trans('admin/node.yes'),
|
||||
false => trans('admin/node.no'),
|
||||
])
|
||||
->colors([
|
||||
true => 'success',
|
||||
false => 'danger',
|
||||
]),
|
||||
TagsInput::make('tags')
|
||||
->label(trans('admin/node.tags'))
|
||||
->columnSpan(2),
|
||||
TextInput::make('upload_size')
|
||||
->label(trans('admin/node.upload_limit'))
|
||||
->helperText(trans('admin/node.upload_limit_help.0'))
|
||||
->hintIcon('tabler-question-mark', trans('admin/node.upload_limit_help.1'))
|
||||
->columnSpan(1)
|
||||
->numeric()->required()
|
||||
->default(256)
|
||||
->minValue(1)
|
||||
->maxValue(1024)
|
||||
->suffix(config('panel.use_binary_prefix') ? 'MiB' : 'MB'),
|
||||
TextInput::make('daemon_sftp')
|
||||
->columnSpan(1)
|
||||
->label(trans('admin/node.sftp_port'))
|
||||
->minValue(1)
|
||||
->maxValue(65535)
|
||||
->default(2022)
|
||||
->required()
|
||||
->integer(),
|
||||
TextInput::make('daemon_sftp_alias')
|
||||
->columnSpan(2)
|
||||
->label(trans('admin/node.sftp_alias'))
|
||||
->helperText(trans('admin/node.sftp_alias_help')),
|
||||
Grid::make()
|
||||
->columns(6)
|
||||
->columnSpanFull()
|
||||
->schema([
|
||||
ToggleButtons::make('unlimited_mem')
|
||||
->dehydrated()
|
||||
->label(trans('admin/node.memory'))->inlineLabel()->inline()
|
||||
->afterStateUpdated(fn (Set $set) => $set('memory', 0))
|
||||
->afterStateUpdated(fn (Set $set) => $set('memory_overallocate', 0))
|
||||
->formatStateUsing(fn (Get $get) => $get('memory') == 0)
|
||||
->live()
|
||||
->options([
|
||||
true => trans('admin/node.unlimited'),
|
||||
false => trans('admin/node.limited'),
|
||||
])
|
||||
->colors([
|
||||
true => 'primary',
|
||||
false => 'warning',
|
||||
])
|
||||
->columnSpan(2),
|
||||
TextInput::make('memory')
|
||||
->dehydratedWhenHidden()
|
||||
->hidden(fn (Get $get) => $get('unlimited_mem'))
|
||||
->label(trans('admin/node.memory_limit'))->inlineLabel()
|
||||
->suffix(config('panel.use_binary_prefix') ? 'MiB' : 'MB')
|
||||
->columnSpan(2)
|
||||
->numeric()
|
||||
->minValue(0)
|
||||
->default(0)
|
||||
->required(),
|
||||
TextInput::make('memory_overallocate')
|
||||
->dehydratedWhenHidden()
|
||||
->label(trans('admin/node.overallocate'))->inlineLabel()
|
||||
->hidden(fn (Get $get) => $get('unlimited_mem'))
|
||||
->columnSpan(2)
|
||||
->numeric()
|
||||
->minValue(-1)
|
||||
->maxValue(100)
|
||||
->default(0)
|
||||
->suffix('%')
|
||||
->required(),
|
||||
]),
|
||||
Grid::make()
|
||||
->columns(6)
|
||||
->columnSpanFull()
|
||||
->schema([
|
||||
ToggleButtons::make('unlimited_disk')
|
||||
->dehydrated()
|
||||
->label(trans('admin/node.disk'))->inlineLabel()->inline()
|
||||
->live()
|
||||
->afterStateUpdated(fn (Set $set) => $set('disk', 0))
|
||||
->afterStateUpdated(fn (Set $set) => $set('disk_overallocate', 0))
|
||||
->formatStateUsing(fn (Get $get) => $get('disk') == 0)
|
||||
->options([
|
||||
true => trans('admin/node.unlimited'),
|
||||
false => trans('admin/node.limited'),
|
||||
])
|
||||
->colors([
|
||||
true => 'primary',
|
||||
false => 'warning',
|
||||
])
|
||||
->columnSpan(2),
|
||||
TextInput::make('disk')
|
||||
->dehydratedWhenHidden()
|
||||
->hidden(fn (Get $get) => $get('unlimited_disk'))
|
||||
->label(trans('admin/node.disk_limit'))->inlineLabel()
|
||||
->suffix(config('panel.use_binary_prefix') ? 'MiB' : 'MB')
|
||||
->columnSpan(2)
|
||||
->numeric()
|
||||
->minValue(0)
|
||||
->default(0)
|
||||
->required(),
|
||||
TextInput::make('disk_overallocate')
|
||||
->dehydratedWhenHidden()
|
||||
->hidden(fn (Get $get) => $get('unlimited_disk'))
|
||||
->label(trans('admin/node.overallocate'))->inlineLabel()
|
||||
->columnSpan(2)
|
||||
->numeric()
|
||||
->minValue(-1)
|
||||
->maxValue(100)
|
||||
->default(0)
|
||||
->suffix('%')
|
||||
->required(),
|
||||
]),
|
||||
Grid::make()
|
||||
->columns(6)
|
||||
->columnSpanFull()
|
||||
->schema([
|
||||
ToggleButtons::make('unlimited_cpu')
|
||||
->dehydrated()
|
||||
->label(trans('admin/node.cpu'))->inlineLabel()->inline()
|
||||
->live()
|
||||
->afterStateUpdated(fn (Set $set) => $set('cpu', 0))
|
||||
->afterStateUpdated(fn (Set $set) => $set('cpu_overallocate', 0))
|
||||
->formatStateUsing(fn (Get $get) => $get('cpu') == 0)
|
||||
->options([
|
||||
true => trans('admin/node.unlimited'),
|
||||
false => trans('admin/node.limited'),
|
||||
])
|
||||
->colors([
|
||||
true => 'primary',
|
||||
false => 'warning',
|
||||
])
|
||||
->columnSpan(2),
|
||||
TextInput::make('cpu')
|
||||
->dehydratedWhenHidden()
|
||||
->hidden(fn (Get $get) => $get('unlimited_cpu'))
|
||||
->label(trans('admin/node.cpu_limit'))->inlineLabel()
|
||||
->suffix('%')
|
||||
->columnSpan(2)
|
||||
->numeric()
|
||||
->default(0)
|
||||
->minValue(0)
|
||||
->required(),
|
||||
TextInput::make('cpu_overallocate')
|
||||
->dehydratedWhenHidden()
|
||||
->hidden(fn (Get $get) => $get('unlimited_cpu'))
|
||||
->label(trans('admin/node.overallocate'))->inlineLabel()
|
||||
->columnSpan(2)
|
||||
->numeric()
|
||||
->default(0)
|
||||
->minValue(-1)
|
||||
->maxValue(100)
|
||||
->suffix('%')
|
||||
->required(),
|
||||
]),
|
||||
]),
|
||||
])->columnSpanFull()
|
||||
->nextAction(fn (Action $action) => $action->label(trans('admin/node.next_step'))->iconButton()->iconSize(IconSize::ExtraLarge)->icon('tabler-arrow-right'))
|
||||
->previousAction(fn (Action $action) => $action->iconButton()->iconSize(IconSize::ExtraLarge)->icon('tabler-arrow-left'))
|
||||
Wizard::make($this->getSteps())
|
||||
->columnSpanFull()
|
||||
->nextAction(fn (Action $action) => $action->tooltip(fn () => $action->getLabel())->iconButton()->iconSize(IconSize::ExtraLarge)->icon(TablerIcon::ArrowRight))
|
||||
->previousAction(fn (Action $action) => $action->tooltip(fn () => $action->getLabel())->iconButton()->iconSize(IconSize::ExtraLarge)->icon(TablerIcon::ArrowLeft))
|
||||
->submitAction(new HtmlString(Blade::render(<<<'BLADE'
|
||||
<x-filament::icon-button
|
||||
type="submit"
|
||||
iconSize="xl"
|
||||
icon="tabler-file-plus"
|
||||
>
|
||||
{{ trans('admin/node.create') }}
|
||||
</x-filament::icon-button>
|
||||
BLADE))),
|
||||
<x-filament::icon-button
|
||||
type="submit"
|
||||
iconSize="xl"
|
||||
icon="tabler-plus"
|
||||
tooltip="{{ trans('admin/node.create') }}"
|
||||
>
|
||||
{{ trans('admin/node.create') }}
|
||||
</x-filament::icon-button>
|
||||
BLADE))),
|
||||
]);
|
||||
}
|
||||
|
||||
/** @return Step[] */
|
||||
protected function getDefaultSteps(): array
|
||||
{
|
||||
return [
|
||||
Step::make('basic')
|
||||
->label(trans('admin/node.tabs.basic_settings'))
|
||||
->icon(TablerIcon::Server)
|
||||
->columnSpanFull()
|
||||
->columns([
|
||||
'default' => 2,
|
||||
'sm' => 3,
|
||||
'md' => 3,
|
||||
'lg' => 4,
|
||||
])
|
||||
->schema([
|
||||
TextInput::make('fqdn')
|
||||
->columnSpan(2)
|
||||
->required()
|
||||
->autofocus()
|
||||
->live(debounce: 1500)
|
||||
->rules(Node::getRulesForField('fqdn'))
|
||||
->prohibited(fn ($state) => is_ip($state) && request()->isSecure())
|
||||
->label(fn ($state) => is_ip($state) ? trans('admin/node.ip_address') : trans('admin/node.domain'))
|
||||
->placeholder(fn ($state) => is_ip($state) ? '192.168.1.1' : 'node.example.com')
|
||||
->helperText(function ($state) {
|
||||
if (is_ip($state)) {
|
||||
if (request()->isSecure()) {
|
||||
return trans('admin/node.fqdn_help');
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
return trans('admin/node.error');
|
||||
})
|
||||
->hintColor('danger')
|
||||
->hint(function ($state) {
|
||||
if (is_ip($state) && request()->isSecure()) {
|
||||
return trans('admin/node.ssl_ip');
|
||||
}
|
||||
|
||||
return '';
|
||||
})
|
||||
->afterStateUpdated(function (Set $set, ?string $state) {
|
||||
$set('dns', null);
|
||||
$set('ip', null);
|
||||
|
||||
[$subdomain] = str($state)->explode('.', 2);
|
||||
if (!is_numeric($subdomain)) {
|
||||
$set('name', $subdomain);
|
||||
}
|
||||
|
||||
if (!$state || is_ip($state)) {
|
||||
$set('dns', null);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$ip = get_ip_from_hostname($state);
|
||||
if ($ip) {
|
||||
$set('dns', true);
|
||||
|
||||
$set('ip', $ip);
|
||||
} else {
|
||||
$set('dns', false);
|
||||
}
|
||||
})
|
||||
->maxLength(255),
|
||||
|
||||
TextInput::make('ip')
|
||||
->disabled()
|
||||
->hidden(),
|
||||
|
||||
ToggleButtons::make('dns')
|
||||
->label(trans('admin/node.dns'))
|
||||
->helperText(trans('admin/node.dns_help'))
|
||||
->disabled()
|
||||
->inline()
|
||||
->default(null)
|
||||
->hint(fn (Get $get) => $get('ip'))
|
||||
->hintColor('success')
|
||||
->options([
|
||||
true => trans('admin/node.valid'),
|
||||
false => trans('admin/node.invalid'),
|
||||
])
|
||||
->colors([
|
||||
true => 'success',
|
||||
false => 'danger',
|
||||
])
|
||||
->columnSpan([
|
||||
'default' => 1,
|
||||
'sm' => 1,
|
||||
'md' => 1,
|
||||
'lg' => 1,
|
||||
]),
|
||||
|
||||
TextInput::make('daemon_connect')
|
||||
->columnSpan(1)
|
||||
->label(fn (Get $get) => $get('connection') === 'https_proxy' ? trans('admin/node.connect_port') : trans('admin/node.port'))
|
||||
->helperText(fn (Get $get) => $get('connection') === 'https_proxy' ? trans('admin/node.connect_port_help') : trans('admin/node.port_help'))
|
||||
->minValue(1)
|
||||
->maxValue(65535)
|
||||
->default(8080)
|
||||
->required()
|
||||
->integer(),
|
||||
|
||||
TextInput::make('name')
|
||||
->label(trans('admin/node.display_name'))
|
||||
->columnSpan([
|
||||
'default' => 1,
|
||||
'sm' => 1,
|
||||
'md' => 1,
|
||||
'lg' => 2,
|
||||
])
|
||||
->required()
|
||||
->maxLength(100),
|
||||
|
||||
Hidden::make('scheme')
|
||||
->default(fn () => request()->isSecure() ? 'https' : 'http'),
|
||||
|
||||
Hidden::make('behind_proxy')
|
||||
->default(false),
|
||||
|
||||
ToggleButtons::make('connection')
|
||||
->label(trans('admin/node.ssl'))
|
||||
->columnSpan(1)
|
||||
->inline()
|
||||
->helperText(function (Get $get) {
|
||||
if (request()->isSecure()) {
|
||||
return new HtmlString(trans('admin/node.panel_on_ssl'));
|
||||
}
|
||||
|
||||
if (is_ip($get('fqdn'))) {
|
||||
return trans('admin/node.ssl_help');
|
||||
}
|
||||
|
||||
return '';
|
||||
})
|
||||
->disableOptionWhen(fn (string $value) => $value === 'http' && request()->isSecure())
|
||||
->options([
|
||||
'http' => 'HTTP',
|
||||
'https' => 'HTTPS (SSL)',
|
||||
'https_proxy' => 'HTTPS with (reverse) proxy',
|
||||
])
|
||||
->colors([
|
||||
'http' => 'warning',
|
||||
'https' => 'success',
|
||||
'https_proxy' => 'success',
|
||||
])
|
||||
->icons([
|
||||
'http' => TablerIcon::LockOpenOff,
|
||||
'https' => TablerIcon::Lock,
|
||||
'https_proxy' => TablerIcon::ShieldLock,
|
||||
])
|
||||
->default(fn () => request()->isSecure() ? 'https' : 'http')
|
||||
->live()
|
||||
->dehydrated(false)
|
||||
->afterStateUpdated(function ($state, Set $set) {
|
||||
$set('scheme', $state === 'http' ? 'http' : 'https');
|
||||
$set('behind_proxy', $state === 'https_proxy');
|
||||
|
||||
$set('daemon_connect', $state === 'https_proxy' ? 443 : 8080);
|
||||
$set('daemon_listen', 8080);
|
||||
}),
|
||||
|
||||
TextInput::make('daemon_listen')
|
||||
->columnSpan(1)
|
||||
->label(trans('admin/node.listen_port'))
|
||||
->helperText(trans('admin/node.listen_port_help'))
|
||||
->minValue(1)
|
||||
->maxValue(65535)
|
||||
->default(8080)
|
||||
->required()
|
||||
->integer()
|
||||
->visible(fn (Get $get) => $get('connection') === 'https_proxy'),
|
||||
]),
|
||||
Step::make('advanced')
|
||||
->label(trans('admin/node.tabs.advanced_settings'))
|
||||
->icon(TablerIcon::ServerCog)
|
||||
->columnSpanFull()
|
||||
->columns([
|
||||
'default' => 2,
|
||||
'sm' => 3,
|
||||
'md' => 3,
|
||||
'lg' => 4,
|
||||
])
|
||||
->schema([
|
||||
ToggleButtons::make('maintenance_mode')
|
||||
->label(trans('admin/node.maintenance_mode'))->inline()
|
||||
->columnSpan(1)
|
||||
->default(false)
|
||||
->hintIcon(TablerIcon::QuestionMark, trans('admin/node.maintenance_mode_help'))
|
||||
->options([
|
||||
true => trans('admin/node.enabled'),
|
||||
false => trans('admin/node.disabled'),
|
||||
])
|
||||
->colors([
|
||||
true => 'danger',
|
||||
false => 'success',
|
||||
]),
|
||||
ToggleButtons::make('public')
|
||||
->default(true)
|
||||
->columnSpan(1)
|
||||
->label(trans('admin/node.use_for_deploy'))->inline()
|
||||
->options([
|
||||
true => trans('admin/node.yes'),
|
||||
false => trans('admin/node.no'),
|
||||
])
|
||||
->colors([
|
||||
true => 'success',
|
||||
false => 'danger',
|
||||
]),
|
||||
TagsInput::make('tags')
|
||||
->label(trans('admin/node.tags'))
|
||||
->columnSpan(2),
|
||||
TextInput::make('upload_size')
|
||||
->label(trans('admin/node.upload_limit'))
|
||||
->hintIcon(TablerIcon::QuestionMark, trans('admin/node.upload_limit_help'))
|
||||
->columnSpan(1)
|
||||
->numeric()->required()
|
||||
->default(256)
|
||||
->minValue(1)
|
||||
->suffix(config('panel.use_binary_prefix') ? 'MiB' : 'MB'),
|
||||
TextInput::make('daemon_base')
|
||||
->label(trans('admin/node.daemon_base'))
|
||||
->placeholder('/var/lib/pelican/volumes')
|
||||
->hintIcon(TablerIcon::QuestionMark, trans('admin/node.daemon_base_help'))
|
||||
->columnSpan(1)
|
||||
->required()
|
||||
->default('/var/lib/pelican/volumes')
|
||||
->rule('regex:/^([\/][\d\w.\-\/]+)$/'),
|
||||
TextInput::make('daemon_sftp')
|
||||
->columnSpan(1)
|
||||
->label(trans('admin/node.sftp_port'))
|
||||
->minValue(1)
|
||||
->maxValue(65535)
|
||||
->default(2022)
|
||||
->required()
|
||||
->integer(),
|
||||
TextInput::make('daemon_sftp_alias')
|
||||
->columnSpan(1)
|
||||
->label(trans('admin/node.sftp_alias'))
|
||||
->helperText(trans('admin/node.sftp_alias_help')),
|
||||
Grid::make()
|
||||
->columns(6)
|
||||
->columnSpanFull()
|
||||
->schema([
|
||||
ToggleButtons::make('unlimited_mem')
|
||||
->dehydrated()
|
||||
->label(trans('admin/node.memory'))->inlineLabel()->inline()
|
||||
->afterStateUpdated(fn (Set $set) => $set('memory', 0))
|
||||
->afterStateUpdated(fn (Set $set) => $set('memory_overallocate', 0))
|
||||
->formatStateUsing(fn (Get $get) => $get('memory') == 0)
|
||||
->live()
|
||||
->options([
|
||||
true => trans('admin/node.unlimited'),
|
||||
false => trans('admin/node.limited'),
|
||||
])
|
||||
->colors([
|
||||
true => 'primary',
|
||||
false => 'warning',
|
||||
])
|
||||
->columnSpan(2),
|
||||
TextInput::make('memory')
|
||||
->dehydratedWhenHidden()
|
||||
->hidden(fn (Get $get) => $get('unlimited_mem'))
|
||||
->label(trans('admin/node.memory_limit'))->inlineLabel()
|
||||
->suffix(config('panel.use_binary_prefix') ? 'MiB' : 'MB')
|
||||
->columnSpan(2)
|
||||
->numeric()
|
||||
->minValue(0)
|
||||
->default(0)
|
||||
->required(),
|
||||
TextInput::make('memory_overallocate')
|
||||
->dehydratedWhenHidden()
|
||||
->label(trans('admin/node.overallocate'))->inlineLabel()
|
||||
->hidden(fn (Get $get) => $get('unlimited_mem'))
|
||||
->columnSpan(2)
|
||||
->numeric()
|
||||
->minValue(-1)
|
||||
->maxValue(100)
|
||||
->default(0)
|
||||
->suffix('%')
|
||||
->required(),
|
||||
]),
|
||||
Grid::make()
|
||||
->columns(6)
|
||||
->columnSpanFull()
|
||||
->schema([
|
||||
ToggleButtons::make('unlimited_disk')
|
||||
->dehydrated()
|
||||
->label(trans('admin/node.disk'))->inlineLabel()->inline()
|
||||
->live()
|
||||
->afterStateUpdated(fn (Set $set) => $set('disk', 0))
|
||||
->afterStateUpdated(fn (Set $set) => $set('disk_overallocate', 0))
|
||||
->formatStateUsing(fn (Get $get) => $get('disk') == 0)
|
||||
->options([
|
||||
true => trans('admin/node.unlimited'),
|
||||
false => trans('admin/node.limited'),
|
||||
])
|
||||
->colors([
|
||||
true => 'primary',
|
||||
false => 'warning',
|
||||
])
|
||||
->columnSpan(2),
|
||||
TextInput::make('disk')
|
||||
->dehydratedWhenHidden()
|
||||
->hidden(fn (Get $get) => $get('unlimited_disk'))
|
||||
->label(trans('admin/node.disk_limit'))->inlineLabel()
|
||||
->suffix(config('panel.use_binary_prefix') ? 'MiB' : 'MB')
|
||||
->columnSpan(2)
|
||||
->numeric()
|
||||
->minValue(0)
|
||||
->default(0)
|
||||
->required(),
|
||||
TextInput::make('disk_overallocate')
|
||||
->dehydratedWhenHidden()
|
||||
->hidden(fn (Get $get) => $get('unlimited_disk'))
|
||||
->label(trans('admin/node.overallocate'))->inlineLabel()
|
||||
->columnSpan(2)
|
||||
->numeric()
|
||||
->minValue(-1)
|
||||
->maxValue(100)
|
||||
->default(0)
|
||||
->suffix('%')
|
||||
->required(),
|
||||
]),
|
||||
Grid::make()
|
||||
->columns(6)
|
||||
->columnSpanFull()
|
||||
->schema([
|
||||
ToggleButtons::make('unlimited_cpu')
|
||||
->dehydrated()
|
||||
->label(trans('admin/node.cpu'))->inlineLabel()->inline()
|
||||
->live()
|
||||
->afterStateUpdated(fn (Set $set) => $set('cpu', 0))
|
||||
->afterStateUpdated(fn (Set $set) => $set('cpu_overallocate', 0))
|
||||
->formatStateUsing(fn (Get $get) => $get('cpu') == 0)
|
||||
->options([
|
||||
true => trans('admin/node.unlimited'),
|
||||
false => trans('admin/node.limited'),
|
||||
])
|
||||
->colors([
|
||||
true => 'primary',
|
||||
false => 'warning',
|
||||
])
|
||||
->columnSpan(2),
|
||||
TextInput::make('cpu')
|
||||
->dehydratedWhenHidden()
|
||||
->hidden(fn (Get $get) => $get('unlimited_cpu'))
|
||||
->label(trans('admin/node.cpu_limit'))->inlineLabel()
|
||||
->suffix('%')
|
||||
->columnSpan(2)
|
||||
->numeric()
|
||||
->default(0)
|
||||
->minValue(0)
|
||||
->required(),
|
||||
TextInput::make('cpu_overallocate')
|
||||
->dehydratedWhenHidden()
|
||||
->hidden(fn (Get $get) => $get('unlimited_cpu'))
|
||||
->label(trans('admin/node.overallocate'))->inlineLabel()
|
||||
->columnSpan(2)
|
||||
->numeric()
|
||||
->default(0)
|
||||
->minValue(-1)
|
||||
->maxValue(100)
|
||||
->suffix('%')
|
||||
->required(),
|
||||
]),
|
||||
]),
|
||||
];
|
||||
}
|
||||
|
||||
protected function getRedirectUrlParameters(): array
|
||||
{
|
||||
return [
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,18 +2,17 @@
|
||||
|
||||
namespace App\Filament\Admin\Resources\Nodes\Pages;
|
||||
|
||||
use App\Enums\TablerIcon;
|
||||
use App\Filament\Admin\Resources\Nodes\NodeResource;
|
||||
use App\Filament\Components\Tables\Columns\NodeClientHealthColumn;
|
||||
use App\Filament\Components\Tables\Columns\NodeHealthColumn;
|
||||
use App\Filament\Components\Tables\Filters\TagsFilter;
|
||||
use App\Models\Node;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\ActionGroup;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
use Filament\Support\Enums\IconSize;
|
||||
use Filament\Tables\Columns\IconColumn;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
@@ -36,6 +35,7 @@ class ListNodes extends ListRecords
|
||||
->searchable()
|
||||
->hidden(),
|
||||
NodeHealthColumn::make('health'),
|
||||
NodeClientHealthColumn::make('reachable'),
|
||||
TextColumn::make('name')
|
||||
->label(trans('admin/node.table.name'))
|
||||
->sortable()
|
||||
@@ -48,14 +48,14 @@ class ListNodes extends ListRecords
|
||||
IconColumn::make('scheme')
|
||||
->visibleFrom('xl')
|
||||
->label('SSL')
|
||||
->trueIcon('tabler-lock')
|
||||
->falseIcon('tabler-lock-open-off')
|
||||
->trueIcon(TablerIcon::Lock)
|
||||
->falseIcon(TablerIcon::LockOpenOff)
|
||||
->state(fn (Node $node) => $node->scheme === 'https'),
|
||||
IconColumn::make('public')
|
||||
->label(trans('admin/node.table.public'))
|
||||
->visibleFrom('lg')
|
||||
->trueIcon('tabler-eye-check')
|
||||
->falseIcon('tabler-eye-cancel'),
|
||||
->trueIcon(TablerIcon::EyeCheck)
|
||||
->falseIcon(TablerIcon::EyeCancel),
|
||||
TextColumn::make('servers_count')
|
||||
->visibleFrom('sm')
|
||||
->counts('servers')
|
||||
@@ -65,7 +65,10 @@ class ListNodes extends ListRecords
|
||||
->recordActions([
|
||||
EditAction::make(),
|
||||
])
|
||||
->emptyStateIcon('tabler-server-2')
|
||||
->toolbarActions([
|
||||
CreateAction::make(),
|
||||
])
|
||||
->emptyStateIcon(TablerIcon::Server2)
|
||||
->emptyStateDescription('')
|
||||
->emptyStateHeading(trans('admin/node.no_nodes'))
|
||||
->filters([
|
||||
@@ -73,14 +76,4 @@ class ListNodes extends ListRecords
|
||||
->model(Node::class),
|
||||
]);
|
||||
}
|
||||
|
||||
/** @return array<Action|ActionGroup> */
|
||||
protected function getDefaultHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
CreateAction::make()
|
||||
->iconButton()->iconSize(IconSize::ExtraLarge)
|
||||
->icon('tabler-file-plus'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,11 +2,13 @@
|
||||
|
||||
namespace App\Filament\Admin\Resources\Nodes\RelationManagers;
|
||||
|
||||
use App\Enums\TablerIcon;
|
||||
use App\Filament\Admin\Resources\Servers\Pages\CreateServer;
|
||||
use App\Filament\Components\Actions\UpdateNodeAllocations;
|
||||
use App\Models\Allocation;
|
||||
use App\Models\Node;
|
||||
use App\Services\Allocations\AssignmentService;
|
||||
use BackedEnum;
|
||||
use Exception;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
@@ -16,7 +18,6 @@ use Filament\Forms\Components\TextInput;
|
||||
use Filament\Resources\RelationManagers\RelationManager;
|
||||
use Filament\Schemas\Components\Utilities\Get;
|
||||
use Filament\Schemas\Components\Utilities\Set;
|
||||
use Filament\Support\Enums\IconSize;
|
||||
use Filament\Tables\Columns\SelectColumn;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Columns\TextInputColumn;
|
||||
@@ -29,7 +30,7 @@ class AllocationsRelationManager extends RelationManager
|
||||
{
|
||||
protected static string $relationship = 'allocations';
|
||||
|
||||
protected static string|\BackedEnum|null $icon = 'tabler-plug-connected';
|
||||
protected static string|BackedEnum|null $icon = TablerIcon::PlugConnected;
|
||||
|
||||
public function setTitle(): string
|
||||
{
|
||||
@@ -57,7 +58,7 @@ class AllocationsRelationManager extends RelationManager
|
||||
->label(trans('admin/node.ports')),
|
||||
TextColumn::make('server.name')
|
||||
->label(trans('admin/node.table.servers'))
|
||||
->icon('tabler-brand-docker')
|
||||
->icon(TablerIcon::BrandDocker)
|
||||
->visibleFrom('md')
|
||||
->searchable()
|
||||
->url(fn (Allocation $allocation): string => $allocation->server ? route('filament.admin.resources.servers.edit', ['record' => $allocation->server]) : ''),
|
||||
@@ -86,12 +87,14 @@ class AllocationsRelationManager extends RelationManager
|
||||
DeleteBulkAction::make()
|
||||
->authorize(fn () => user()?->can('update', $this->getOwnerRecord())),
|
||||
Action::make('create new allocation')
|
||||
->label(trans('admin/node.create_allocation'))
|
||||
->icon('tabler-world-plus')
|
||||
->iconButton()->iconSize(IconSize::ExtraLarge)
|
||||
->tooltip(trans('admin/node.create_allocation'))
|
||||
->icon(TablerIcon::WorldPlus)
|
||||
->schema(fn () => [
|
||||
Select::make('allocation_ip')
|
||||
->options(fn () => collect($this->getOwnerRecord()->ipAddresses())->mapWithKeys(fn (string $ip) => [$ip => $ip]))
|
||||
->options(fn (Get $get) => collect($this->getOwnerRecord()->ipAddresses())
|
||||
->when($get('allocation_ip'), fn ($ips, $current) => $ips->push($current))
|
||||
->unique()
|
||||
->mapWithKeys(fn (string $ip) => [$ip => $ip]))
|
||||
->label(trans('admin/node.ip_address'))
|
||||
->inlineLabel()
|
||||
->ip()
|
||||
@@ -99,14 +102,26 @@ class AllocationsRelationManager extends RelationManager
|
||||
->afterStateUpdated(fn (Set $set) => $set('allocation_ports', []))
|
||||
->live()
|
||||
->hintAction(
|
||||
Action::make('refresh')
|
||||
->iconButton()
|
||||
->icon('tabler-refresh')
|
||||
Action::make('hint_refresh')
|
||||
->hiddenLabel()
|
||||
->icon(TablerIcon::Refresh)
|
||||
->tooltip(trans('admin/node.refresh'))
|
||||
->action(function () {
|
||||
cache()->forget("nodes.{$this->getOwnerRecord()->id}.ips");
|
||||
})
|
||||
)
|
||||
->suffixAction(
|
||||
Action::make('custom_ip')
|
||||
->icon(TablerIcon::Keyboard)
|
||||
->tooltip(trans('admin/node.custom_ip'))
|
||||
->schema([
|
||||
TextInput::make('custom_ip')
|
||||
->label(trans('admin/node.ip_address'))
|
||||
->ip()
|
||||
->required(),
|
||||
])
|
||||
->action(fn (array $data, Set $set) => $set('allocation_ip', $data['custom_ip']))
|
||||
)
|
||||
->required(),
|
||||
TextInput::make('allocation_alias')
|
||||
->label(trans('admin/node.table.alias'))
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
namespace App\Filament\Admin\Resources\Nodes\RelationManagers;
|
||||
|
||||
use App\Enums\ServerResourceType;
|
||||
use App\Enums\TablerIcon;
|
||||
use App\Models\Server;
|
||||
use BackedEnum;
|
||||
use Filament\Resources\RelationManagers\RelationManager;
|
||||
use Filament\Tables\Columns\SelectColumn;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
@@ -13,7 +15,7 @@ class ServersRelationManager extends RelationManager
|
||||
{
|
||||
protected static string $relationship = 'servers';
|
||||
|
||||
protected static string|\BackedEnum|null $icon = 'tabler-brand-docker';
|
||||
protected static string|BackedEnum|null $icon = TablerIcon::BrandDocker;
|
||||
|
||||
public function setTitle(): string
|
||||
{
|
||||
|
||||
@@ -3,9 +3,14 @@
|
||||
namespace App\Filament\Admin\Resources\Plugins;
|
||||
|
||||
use App\Enums\PluginStatus;
|
||||
use App\Enums\TablerIcon;
|
||||
use App\Filament\Admin\Resources\Plugins\Pages\ListPlugins;
|
||||
use App\Jobs\Plugin\InstallPlugin;
|
||||
use App\Jobs\Plugin\UninstallPlugin;
|
||||
use App\Jobs\Plugin\UpdatePlugin;
|
||||
use App\Models\Plugin;
|
||||
use App\Services\Helpers\PluginService;
|
||||
use BackedEnum;
|
||||
use Exception;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\ActionGroup;
|
||||
@@ -14,7 +19,6 @@ use Filament\Forms\Components\TextInput;
|
||||
use Filament\Infolists\Components\TextEntry;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Support\Enums\IconSize;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
@@ -23,7 +27,7 @@ class PluginResource extends Resource
|
||||
{
|
||||
protected static ?string $model = Plugin::class;
|
||||
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'tabler-packages';
|
||||
protected static string|BackedEnum|null $navigationIcon = TablerIcon::Packages;
|
||||
|
||||
protected static ?string $recordTitleAttribute = 'name';
|
||||
|
||||
@@ -59,7 +63,7 @@ class PluginResource extends Resource
|
||||
TextColumn::make('name')
|
||||
->label(trans('admin/plugin.name'))
|
||||
->description(fn (Plugin $plugin) => (strlen($plugin->description) > 80) ? substr($plugin->description, 0, 80).'...' : $plugin->description)
|
||||
->icon(fn (Plugin $plugin) => $plugin->isUpdateAvailable() ? 'tabler-versions-off' : 'tabler-versions')
|
||||
->icon(fn (Plugin $plugin) => $plugin->isUpdateAvailable() ? TablerIcon::VersionsOff : TablerIcon::Versions)
|
||||
->iconColor(fn (Plugin $plugin) => $plugin->isUpdateAvailable() ? 'danger' : 'success')
|
||||
->tooltip(fn (Plugin $plugin) => $plugin->isUpdateAvailable() ? trans('admin/plugin.update_available') : null)
|
||||
->sortable()
|
||||
@@ -82,15 +86,15 @@ class PluginResource extends Resource
|
||||
->sortable(),
|
||||
])
|
||||
->recordActions([
|
||||
Action::make('view')
|
||||
Action::make('exclude_view')
|
||||
->label(trans('filament-actions::view.single.label'))
|
||||
->icon(fn (Plugin $plugin) => $plugin->getReadme() ? 'tabler-eye' : 'tabler-eye-share')
|
||||
->icon(fn (Plugin $plugin) => $plugin->getReadme() ? TablerIcon::Eye : TablerIcon::EyeShare)
|
||||
->color('gray')
|
||||
->visible(fn (Plugin $plugin) => $plugin->getReadme() || $plugin->url)
|
||||
->url(fn (Plugin $plugin) => !$plugin->getReadme() ? $plugin->url : null, true)
|
||||
->slideOver(true)
|
||||
->modalHeading('Readme')
|
||||
->modalSubmitAction(fn (Plugin $plugin) => Action::make('visit_website')
|
||||
->modalSubmitAction(fn (Plugin $plugin) => Action::make('exclude_visit_website')
|
||||
->label(trans('admin/plugin.visit_website'))
|
||||
->visible(!is_null($plugin->url))
|
||||
->url($plugin->url, true)
|
||||
@@ -102,52 +106,66 @@ class PluginResource extends Resource
|
||||
->markdown()
|
||||
->state(fn (Plugin $plugin) => $plugin->getReadme()),
|
||||
] : null),
|
||||
Action::make('settings')
|
||||
Action::make('exclude_settings')
|
||||
->label(trans('admin/plugin.settings'))
|
||||
->authorize(fn (Plugin $plugin) => user()?->can('update', $plugin))
|
||||
->icon('tabler-settings')
|
||||
->icon(TablerIcon::Settings)
|
||||
->color('primary')
|
||||
->visible(fn (Plugin $plugin) => $plugin->status === PluginStatus::Enabled && $plugin->hasSettings())
|
||||
->schema(fn (Plugin $plugin) => $plugin->getSettingsForm())
|
||||
->action(fn (array $data, Plugin $plugin) => $plugin->saveSettings($data))
|
||||
->slideOver(),
|
||||
ActionGroup::make([
|
||||
Action::make('install')
|
||||
Action::make('exclude_install')
|
||||
->label(trans('admin/plugin.install'))
|
||||
->authorize(fn (Plugin $plugin) => user()?->can('update', $plugin))
|
||||
->icon('tabler-terminal')
|
||||
->icon(TablerIcon::Terminal)
|
||||
->color('success')
|
||||
->hidden(fn (Plugin $plugin) => $plugin->status !== PluginStatus::NotInstalled)
|
||||
->action(function (Plugin $plugin, $livewire, PluginService $pluginService) {
|
||||
$pluginService->installPlugin($plugin, !$plugin->isTheme() || !$pluginService->hasThemePluginEnabled());
|
||||
->action(function (Plugin $plugin) {
|
||||
try {
|
||||
InstallPlugin::dispatch(user(), $plugin);
|
||||
|
||||
redirect(ListPlugins::getUrl(['tab' => $livewire->activeTab]));
|
||||
|
||||
Notification::make()
|
||||
->success()
|
||||
->title(trans('admin/plugin.notifications.installed'))
|
||||
->send();
|
||||
Notification::make()
|
||||
->success()
|
||||
->title(trans('admin/plugin.notifications.install_started'))
|
||||
->body(trans('admin/plugin.notifications.background_info'))
|
||||
->send();
|
||||
} catch (Exception $exception) {
|
||||
Notification::make()
|
||||
->danger()
|
||||
->title(trans('admin/plugin.notifications.install_error'))
|
||||
->body($exception->getMessage())
|
||||
->send();
|
||||
}
|
||||
}),
|
||||
Action::make('update')
|
||||
Action::make('exclude_update')
|
||||
->label(trans('admin/plugin.update'))
|
||||
->authorize(fn (Plugin $plugin) => user()?->can('update', $plugin))
|
||||
->icon('tabler-download')
|
||||
->icon(TablerIcon::Download)
|
||||
->color('success')
|
||||
->visible(fn (Plugin $plugin) => $plugin->status !== PluginStatus::NotInstalled && $plugin->isUpdateAvailable())
|
||||
->action(function (Plugin $plugin, $livewire, PluginService $pluginService) {
|
||||
$pluginService->updatePlugin($plugin);
|
||||
->action(function (Plugin $plugin) {
|
||||
try {
|
||||
UpdatePlugin::dispatch(user(), $plugin);
|
||||
|
||||
redirect(ListPlugins::getUrl(['tab' => $livewire->activeTab]));
|
||||
|
||||
Notification::make()
|
||||
->success()
|
||||
->title(trans('admin/plugin.notifications.updated'))
|
||||
->send();
|
||||
Notification::make()
|
||||
->success()
|
||||
->title(trans('admin/plugin.notifications.update_started'))
|
||||
->body(trans('admin/plugin.notifications.background_info'))
|
||||
->send();
|
||||
} catch (Exception $exception) {
|
||||
Notification::make()
|
||||
->danger()
|
||||
->title(trans('admin/plugin.notifications.update_error'))
|
||||
->body($exception->getMessage())
|
||||
->send();
|
||||
}
|
||||
}),
|
||||
Action::make('enable')
|
||||
Action::make('exclude_enable')
|
||||
->label(trans('admin/plugin.enable'))
|
||||
->authorize(fn (Plugin $plugin) => user()?->can('update', $plugin))
|
||||
->icon('tabler-check')
|
||||
->icon(TablerIcon::Check)
|
||||
->color('success')
|
||||
->visible(fn (Plugin $plugin) => $plugin->canEnable())
|
||||
->requiresConfirmation(fn (Plugin $plugin, PluginService $pluginService) => $plugin->isTheme() && $pluginService->hasThemePluginEnabled())
|
||||
@@ -160,13 +178,13 @@ class PluginResource extends Resource
|
||||
|
||||
Notification::make()
|
||||
->success()
|
||||
->title(trans('admin/plugin.notifications.updated'))
|
||||
->title(trans('admin/plugin.notifications.enabled'))
|
||||
->send();
|
||||
}),
|
||||
Action::make('disable')
|
||||
Action::make('exclude_disable')
|
||||
->label(trans('admin/plugin.disable'))
|
||||
->authorize(fn (Plugin $plugin) => user()?->can('update', $plugin))
|
||||
->icon('tabler-x')
|
||||
->icon(TablerIcon::X)
|
||||
->color('warning')
|
||||
->visible(fn (Plugin $plugin) => $plugin->canDisable())
|
||||
->action(function (Plugin $plugin, $livewire, PluginService $pluginService) {
|
||||
@@ -179,13 +197,13 @@ class PluginResource extends Resource
|
||||
->title(trans('admin/plugin.notifications.disabled'))
|
||||
->send();
|
||||
}),
|
||||
Action::make('delete')
|
||||
Action::make('exclude_delete')
|
||||
->label(trans('filament-actions::delete.single.label'))
|
||||
->authorize(fn (Plugin $plugin) => user()?->can('delete', $plugin))
|
||||
->icon('tabler-trash')
|
||||
->icon(TablerIcon::Trash)
|
||||
->color('danger')
|
||||
->requiresConfirmation()
|
||||
->visible(fn (Plugin $plugin) => $plugin->status === PluginStatus::NotInstalled)
|
||||
->visible(fn (Plugin $plugin) => $plugin->status === PluginStatus::NotInstalled || $plugin->status === PluginStatus::Errored)
|
||||
->action(function (Plugin $plugin, $livewire, PluginService $pluginService) {
|
||||
$pluginService->deletePlugin($plugin);
|
||||
|
||||
@@ -196,32 +214,38 @@ class PluginResource extends Resource
|
||||
->title(trans('admin/plugin.notifications.deleted'))
|
||||
->send();
|
||||
}),
|
||||
Action::make('uninstall')
|
||||
Action::make('exclude_uninstall')
|
||||
->label(trans('admin/plugin.uninstall'))
|
||||
->authorize(fn (Plugin $plugin) => user()?->can('update', $plugin))
|
||||
->icon('tabler-terminal')
|
||||
->icon(TablerIcon::Terminal)
|
||||
->color('danger')
|
||||
->requiresConfirmation()
|
||||
->hidden(fn (Plugin $plugin) => $plugin->status === PluginStatus::NotInstalled)
|
||||
->action(function (Plugin $plugin, $livewire, PluginService $pluginService) {
|
||||
$pluginService->uninstallPlugin($plugin);
|
||||
->hidden(fn (Plugin $plugin) => $plugin->status === PluginStatus::NotInstalled || $plugin->status === PluginStatus::Errored)
|
||||
->action(function (Plugin $plugin) {
|
||||
try {
|
||||
UninstallPlugin::dispatch(user(), $plugin);
|
||||
|
||||
redirect(ListPlugins::getUrl(['tab' => $livewire->activeTab]));
|
||||
|
||||
Notification::make()
|
||||
->success()
|
||||
->title(trans('admin/plugin.notifications.uninstalled'))
|
||||
->send();
|
||||
Notification::make()
|
||||
->success()
|
||||
->title(trans('admin/plugin.notifications.uninstall_started'))
|
||||
->body(trans('admin/plugin.notifications.background_info'))
|
||||
->send();
|
||||
} catch (Exception $exception) {
|
||||
Notification::make()
|
||||
->danger()
|
||||
->title(trans('admin/plugin.notifications.uninstall_error'))
|
||||
->body($exception->getMessage())
|
||||
->send();
|
||||
}
|
||||
}),
|
||||
]),
|
||||
])
|
||||
->headerActions([
|
||||
Action::make('import_from_file')
|
||||
->label(trans('admin/plugin.import_from_file'))
|
||||
->hiddenLabel()
|
||||
->tooltip(trans('admin/plugin.import_from_file'))
|
||||
->authorize(fn () => user()?->can('create', Plugin::class))
|
||||
->icon('tabler-file-download')
|
||||
->iconButton()
|
||||
->iconSize(IconSize::ExtraLarge)
|
||||
->icon(TablerIcon::FileDownload)
|
||||
->schema([
|
||||
// TODO: switch to new file upload
|
||||
FileUpload::make('file')
|
||||
@@ -261,11 +285,10 @@ class PluginResource extends Resource
|
||||
}
|
||||
}),
|
||||
Action::make('import_from_url')
|
||||
->label(trans('admin/plugin.import_from_url'))
|
||||
->hiddenLabel()
|
||||
->tooltip(trans('admin/plugin.import_from_url'))
|
||||
->authorize(fn () => user()?->can('create', Plugin::class))
|
||||
->icon('tabler-world-download')
|
||||
->iconButton()
|
||||
->iconSize(IconSize::ExtraLarge)
|
||||
->icon(TablerIcon::WorldDownload)
|
||||
->schema([
|
||||
TextInput::make('url')
|
||||
->required()
|
||||
@@ -299,7 +322,7 @@ class PluginResource extends Resource
|
||||
}
|
||||
}),
|
||||
])
|
||||
->emptyStateIcon('tabler-packages')
|
||||
->emptyStateIcon(TablerIcon::Packages)
|
||||
->emptyStateDescription('')
|
||||
->emptyStateHeading(trans('admin/plugin.no_plugins'));
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Filament\Admin\Resources\Roles\Pages;
|
||||
|
||||
use App\Enums\TablerIcon;
|
||||
use App\Filament\Admin\Resources\Roles\RoleResource;
|
||||
use App\Models\Role;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
@@ -9,7 +10,6 @@ use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\ActionGroup;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
use Filament\Support\Enums\IconSize;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Collection;
|
||||
use Spatie\Permission\Models\Permission;
|
||||
@@ -32,9 +32,12 @@ class CreateRole extends CreateRecord
|
||||
protected function getDefaultHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
$this->getCreateFormAction()->formId('form')
|
||||
->iconButton()->iconSize(IconSize::ExtraLarge)
|
||||
->icon('tabler-plus'),
|
||||
Action::make('create')
|
||||
->hiddenLabel()
|
||||
->action('create')
|
||||
->keyBindings(['mod+s'])
|
||||
->tooltip(trans('filament-panels::resources/pages/create-record.form.actions.create.label'))
|
||||
->icon(TablerIcon::FilePlus),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Filament\Admin\Resources\Roles\Pages;
|
||||
|
||||
use App\Enums\TablerIcon;
|
||||
use App\Filament\Admin\Resources\Roles\RoleResource;
|
||||
use App\Models\Role;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
@@ -10,7 +11,6 @@ use Filament\Actions\Action;
|
||||
use Filament\Actions\ActionGroup;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
use Filament\Support\Enums\IconSize;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Collection;
|
||||
use Spatie\Permission\Models\Permission;
|
||||
@@ -58,12 +58,14 @@ class EditRole extends EditRecord
|
||||
{
|
||||
return [
|
||||
DeleteAction::make()
|
||||
->label(fn (Role $role) => $role->isRootAdmin() ? trans('admin/role.root_admin_delete') : ($role->users_count >= 1 ? trans('admin/role.in_use') : trans('filament-actions::delete.single.label')))
|
||||
->disabled(fn (Role $role) => $role->isRootAdmin() || $role->users_count >= 1)
|
||||
->iconButton()->iconSize(IconSize::ExtraLarge),
|
||||
$this->getSaveFormAction()->formId('form')
|
||||
->iconButton()->iconSize(IconSize::ExtraLarge)
|
||||
->icon('tabler-device-floppy'),
|
||||
->tooltip(fn (Role $role) => $role->isRootAdmin() ? trans('admin/role.root_admin_delete') : ($role->users_count >= 1 ? trans('admin/role.in_use') : trans('filament-actions::delete.single.label')))
|
||||
->disabled(fn (Role $role) => $role->isRootAdmin() || $role->users_count >= 1),
|
||||
Action::make('save')
|
||||
->hiddenLabel()
|
||||
->action('save')
|
||||
->keyBindings(['mod+s'])
|
||||
->tooltip(trans('filament-panels::resources/pages/edit-record.form.actions.save.label'))
|
||||
->icon(TablerIcon::DeviceFloppy),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -5,11 +5,7 @@ namespace App\Filament\Admin\Resources\Roles\Pages;
|
||||
use App\Filament\Admin\Resources\Roles\RoleResource;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\ActionGroup;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
use Filament\Support\Enums\IconSize;
|
||||
|
||||
class ListRoles extends ListRecords
|
||||
{
|
||||
@@ -17,14 +13,4 @@ class ListRoles extends ListRecords
|
||||
use CanCustomizeHeaderWidgets;
|
||||
|
||||
protected static string $resource = RoleResource::class;
|
||||
|
||||
/** @return array<Action|ActionGroup> */
|
||||
protected function getDefaultHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
CreateAction::make()
|
||||
->icon('tabler-file-plus')
|
||||
->iconButton()->iconSize(IconSize::ExtraLarge),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ use Filament\Actions\Action;
|
||||
use Filament\Actions\ActionGroup;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Resources\Pages\ViewRecord;
|
||||
use Filament\Support\Enums\IconSize;
|
||||
|
||||
class ViewRole extends ViewRecord
|
||||
{
|
||||
@@ -22,8 +21,7 @@ class ViewRole extends ViewRecord
|
||||
protected function getDefaultHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
EditAction::make()
|
||||
->iconButton()->iconSize(IconSize::ExtraLarge),
|
||||
EditAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Filament\Admin\Resources\Roles;
|
||||
|
||||
use App\Enums\CustomizationKey;
|
||||
use App\Enums\TablerIcon;
|
||||
use App\Filament\Admin\Resources\Roles\Pages\CreateRole;
|
||||
use App\Filament\Admin\Resources\Roles\Pages\EditRole;
|
||||
use App\Filament\Admin\Resources\Roles\Pages\ListRoles;
|
||||
@@ -15,6 +16,8 @@ use App\Traits\Filament\CanModifyTable;
|
||||
use BackedEnum;
|
||||
use Exception;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Actions\ViewAction;
|
||||
@@ -43,7 +46,7 @@ class RoleResource extends Resource
|
||||
|
||||
protected static ?string $model = Role::class;
|
||||
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'tabler-users-group';
|
||||
protected static string|BackedEnum|null $navigationIcon = TablerIcon::UsersGroup;
|
||||
|
||||
protected static ?string $recordTitleAttribute = 'name';
|
||||
|
||||
@@ -100,10 +103,13 @@ class RoleResource extends Resource
|
||||
->hidden(fn ($record) => static::getEditAuthorizationResponse($record)->allowed()),
|
||||
EditAction::make(),
|
||||
])
|
||||
->checkIfRecordIsSelectableUsing(fn (Role $role) => !$role->isRootAdmin() && $role->users_count <= 0)
|
||||
->groupedBulkActions([
|
||||
DeleteBulkAction::make(),
|
||||
]);
|
||||
->toolbarActions([
|
||||
CreateAction::make(),
|
||||
BulkActionGroup::make([
|
||||
DeleteBulkAction::make('exclude_bulk_delete'),
|
||||
]),
|
||||
])
|
||||
->checkIfRecordIsSelectableUsing(fn (Role $role) => !$role->isRootAdmin() && $role->users_count <= 0);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user