mirror of
https://github.com/pelican-dev/panel.git
synced 2026-05-04 18:00:48 +03:00
Compare commits
153 Commits
v1.0.0-bet
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2bbfb0eef9 | ||
|
|
2a64ea8536 | ||
|
|
4fdbbff74b | ||
|
|
2ed891633c | ||
|
|
58dcbeac0b | ||
|
|
91c5ddb2bd | ||
|
|
562be98b20 | ||
|
|
fd3b8a7ab3 | ||
|
|
1817383bf5 | ||
|
|
d39a0c4464 | ||
|
|
2e48095379 | ||
|
|
06c662988a | ||
|
|
98d7158dfc | ||
|
|
e01d9f2cf3 | ||
|
|
b693d0e728 | ||
|
|
612041e1f8 | ||
|
|
64bcdb514b | ||
|
|
c1105db702 | ||
|
|
761492d09f | ||
|
|
e35ce1e79d | ||
|
|
42c127c004 | ||
|
|
593f209142 | ||
|
|
9bf5b2cf0a | ||
|
|
f76e864a30 | ||
|
|
01cfa31ee1 | ||
|
|
dead664e4d | ||
|
|
1bbbcd0e25 | ||
|
|
677d2f742c | ||
|
|
650fb16d2d | ||
|
|
58814ea782 | ||
|
|
0918ed308b | ||
|
|
85d5f2ec3f | ||
|
|
810f237547 | ||
|
|
8f191890a1 | ||
|
|
160e0e54f5 | ||
|
|
fdcfbb00ca | ||
|
|
44f6cf8928 | ||
|
|
cf2a26bbf0 | ||
|
|
adb6678eee | ||
|
|
9539e21b39 | ||
|
|
33660f635f | ||
|
|
8c475ed95f | ||
|
|
d43cb1d180 | ||
|
|
fe55dbd200 | ||
|
|
0bb4503c2b | ||
|
|
3c1168beb5 | ||
|
|
1a092aedc8 | ||
|
|
8c99a8030f | ||
|
|
6e53b1cd7d | ||
|
|
4042e0416b | ||
|
|
cc8973cf00 | ||
|
|
8ebe75b947 | ||
|
|
f8144407d1 | ||
|
|
e431ccb66a | ||
|
|
9291bb4477 | ||
|
|
e8c80ae420 | ||
|
|
f1be003276 | ||
|
|
e532a9a180 | ||
|
|
41fdd7bc8e | ||
|
|
9b01203b7c | ||
|
|
ab4eadec32 | ||
|
|
789c4c7284 | ||
|
|
b1a39f1724 | ||
|
|
6a548c09a0 | ||
|
|
55bda569cc | ||
|
|
adf1249086 | ||
|
|
dbf77bf146 | ||
|
|
a34bf9fd49 | ||
|
|
7a9deba0e1 | ||
|
|
159bfe2210 | ||
|
|
a821db8aae | ||
|
|
1556f8efb8 | ||
|
|
57c2aa6f21 | ||
|
|
36de4c3786 | ||
|
|
26312e3897 | ||
|
|
a477c89025 | ||
|
|
93e81c26a9 | ||
|
|
23e91e8df3 | ||
|
|
833294bfaf | ||
|
|
abaeeff86d | ||
|
|
dd77555c42 | ||
|
|
297ecb544d | ||
|
|
e14bb7d030 | ||
|
|
c770937880 | ||
|
|
426643eaa6 | ||
|
|
3ca0f64e6e | ||
|
|
8e8ce3b50f | ||
|
|
b1e9cadc10 | ||
|
|
7bf1f18c2d | ||
|
|
6fe7d29960 | ||
|
|
15172b1d86 | ||
|
|
9f744d39a2 | ||
|
|
b79511568e | ||
|
|
adeb1b4217 | ||
|
|
d064bf9734 | ||
|
|
107286d618 | ||
|
|
a3203f7dda | ||
|
|
e9abd56f7a | ||
|
|
675ab057b0 | ||
|
|
943d9d3ef5 | ||
|
|
c06a525be2 | ||
|
|
2ff5fdf831 | ||
|
|
0e810f3110 | ||
|
|
eadbe6e8fd | ||
|
|
53aa49b11a | ||
|
|
6ae4f007c8 | ||
|
|
6b9d683f06 | ||
|
|
3b24e22316 | ||
|
|
bd012f52a9 | ||
|
|
af202d9827 | ||
|
|
6ebeb40ba0 | ||
|
|
333eeda065 | ||
|
|
fcfafadec7 | ||
|
|
76b6118fd1 | ||
|
|
3141fe61b4 | ||
|
|
bed9dbeb2b | ||
|
|
976cb00c0d | ||
|
|
e3534bbb29 | ||
|
|
5740c93032 | ||
|
|
d72e075977 | ||
|
|
9af608f808 | ||
|
|
ac36e7a4b5 | ||
|
|
b1c64e2ef1 | ||
|
|
da2e930d4d | ||
|
|
460a5dfaf8 | ||
|
|
576f04be58 | ||
|
|
43fb030133 | ||
|
|
ae054f6e9b | ||
|
|
fef91791c3 | ||
|
|
1d5ace3a6d | ||
|
|
242a75bf3d | ||
|
|
2ab4c81e2a | ||
|
|
5a47948a93 | ||
|
|
9d1e7f510f | ||
|
|
be55e75109 | ||
|
|
8b5f33ee71 | ||
|
|
014e866d0e | ||
|
|
4a1ecb1adc | ||
|
|
e2529ab436 | ||
|
|
cd3f3a97ac | ||
|
|
2f5790b121 | ||
|
|
59f0fe1959 | ||
|
|
fdd9faaaa3 | ||
|
|
9449d78144 | ||
|
|
a391d21043 | ||
|
|
b13fcfd644 | ||
|
|
760aaf9bfb | ||
|
|
1ab4ddb07c | ||
|
|
f278041bc0 | ||
|
|
cdc928a15b | ||
|
|
3939c409c1 | ||
|
|
091ca5447a | ||
|
|
57c4172c74 |
49
.github/workflows/ci.yaml
vendored
49
.github/workflows/ci.yaml
vendored
@@ -26,7 +26,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: true
|
fail-fast: true
|
||||||
matrix:
|
matrix:
|
||||||
php: [8.2, 8.3, 8.4]
|
php: [8.3, 8.4, 8.5]
|
||||||
env:
|
env:
|
||||||
DB_CONNECTION: sqlite
|
DB_CONNECTION: sqlite
|
||||||
DB_DATABASE: testing.sqlite
|
DB_DATABASE: testing.sqlite
|
||||||
@@ -61,14 +61,17 @@ jobs:
|
|||||||
- name: Create SQLite file
|
- name: Create SQLite file
|
||||||
run: touch database/testing.sqlite
|
run: touch database/testing.sqlite
|
||||||
|
|
||||||
|
- name: Run Migrations
|
||||||
|
run: php artisan migrate --force --seed
|
||||||
|
|
||||||
- name: Unit tests
|
- name: Unit tests
|
||||||
run: vendor/bin/pest tests/Unit
|
run: vendor/bin/pest tests/Unit --parallel
|
||||||
env:
|
env:
|
||||||
DB_HOST: UNIT_NO_DB
|
DB_HOST: UNIT_NO_DB
|
||||||
SKIP_MIGRATIONS: true
|
SKIP_MIGRATIONS: true
|
||||||
|
|
||||||
- name: Integration tests
|
- name: Integration tests
|
||||||
run: vendor/bin/pest tests/Integration
|
run: vendor/bin/pest tests/Integration --parallel
|
||||||
|
|
||||||
mysql:
|
mysql:
|
||||||
name: MySQL
|
name: MySQL
|
||||||
@@ -76,8 +79,8 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: true
|
fail-fast: true
|
||||||
matrix:
|
matrix:
|
||||||
php: [8.2, 8.3, 8.4]
|
php: [8.5]
|
||||||
database: ["mysql:8"]
|
database: ["mysql:8.4", "mysql:9.6"]
|
||||||
services:
|
services:
|
||||||
database:
|
database:
|
||||||
image: ${{ matrix.database }}
|
image: ${{ matrix.database }}
|
||||||
@@ -120,14 +123,20 @@ jobs:
|
|||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: composer install --no-interaction --no-suggest --no-progress --no-scripts
|
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
|
- name: Unit tests
|
||||||
run: vendor/bin/pest tests/Unit
|
run: vendor/bin/pest tests/Unit --parallel
|
||||||
env:
|
env:
|
||||||
DB_HOST: UNIT_NO_DB
|
DB_HOST: UNIT_NO_DB
|
||||||
SKIP_MIGRATIONS: true
|
SKIP_MIGRATIONS: true
|
||||||
|
|
||||||
- name: Integration tests
|
- name: Integration tests
|
||||||
run: vendor/bin/pest tests/Integration
|
run: vendor/bin/pest tests/Integration --parallel
|
||||||
env:
|
env:
|
||||||
DB_PORT: ${{ job.services.database.ports[3306] }}
|
DB_PORT: ${{ job.services.database.ports[3306] }}
|
||||||
DB_USERNAME: root
|
DB_USERNAME: root
|
||||||
@@ -138,8 +147,8 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: true
|
fail-fast: true
|
||||||
matrix:
|
matrix:
|
||||||
php: [8.2, 8.3, 8.4]
|
php: [8.5]
|
||||||
database: ["mariadb:10.6", "mariadb:10.11", "mariadb:11.4"]
|
database: ["mariadb:10.11", "mariadb:11.4"]
|
||||||
services:
|
services:
|
||||||
database:
|
database:
|
||||||
image: ${{ matrix.database }}
|
image: ${{ matrix.database }}
|
||||||
@@ -182,14 +191,20 @@ jobs:
|
|||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: composer install --no-interaction --no-suggest --no-progress --no-scripts
|
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
|
- name: Unit tests
|
||||||
run: vendor/bin/pest tests/Unit
|
run: vendor/bin/pest tests/Unit --parallel
|
||||||
env:
|
env:
|
||||||
DB_HOST: UNIT_NO_DB
|
DB_HOST: UNIT_NO_DB
|
||||||
SKIP_MIGRATIONS: true
|
SKIP_MIGRATIONS: true
|
||||||
|
|
||||||
- name: Integration tests
|
- name: Integration tests
|
||||||
run: vendor/bin/pest tests/Integration
|
run: vendor/bin/pest tests/Integration --parallel
|
||||||
env:
|
env:
|
||||||
DB_PORT: ${{ job.services.database.ports[3306] }}
|
DB_PORT: ${{ job.services.database.ports[3306] }}
|
||||||
DB_USERNAME: root
|
DB_USERNAME: root
|
||||||
@@ -200,8 +215,8 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: true
|
fail-fast: true
|
||||||
matrix:
|
matrix:
|
||||||
php: [8.2, 8.3, 8.4]
|
php: [8.5]
|
||||||
database: ["postgres:14"]
|
database: ["postgres:17", "postgres:18"]
|
||||||
services:
|
services:
|
||||||
database:
|
database:
|
||||||
image: ${{ matrix.database }}
|
image: ${{ matrix.database }}
|
||||||
@@ -238,6 +253,7 @@ jobs:
|
|||||||
path: ${{ steps.composer-cache.outputs.dir }}
|
path: ${{ steps.composer-cache.outputs.dir }}
|
||||||
key: ${{ runner.os }}-composer-${{ matrix.php }}-${{ hashFiles('**/composer.lock') }}
|
key: ${{ runner.os }}-composer-${{ matrix.php }}-${{ hashFiles('**/composer.lock') }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
|
${{ runner.os }}-composer-${{ matrix.php }}-
|
||||||
|
|
||||||
- name: Setup PHP
|
- name: Setup PHP
|
||||||
uses: shivammathur/setup-php@v2
|
uses: shivammathur/setup-php@v2
|
||||||
@@ -250,11 +266,14 @@ jobs:
|
|||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: composer install --no-interaction --no-suggest --no-progress --no-scripts
|
run: composer install --no-interaction --no-suggest --no-progress --no-scripts
|
||||||
|
|
||||||
|
- name: Run Migrations
|
||||||
|
run: php artisan migrate --force --seed
|
||||||
|
|
||||||
- name: Unit tests
|
- name: Unit tests
|
||||||
run: vendor/bin/pest tests/Unit
|
run: vendor/bin/pest tests/Unit --parallel
|
||||||
env:
|
env:
|
||||||
DB_HOST: UNIT_NO_DB
|
DB_HOST: UNIT_NO_DB
|
||||||
SKIP_MIGRATIONS: true
|
SKIP_MIGRATIONS: true
|
||||||
|
|
||||||
- name: Integration tests
|
- 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:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- '**'
|
- "**"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
pint:
|
pint:
|
||||||
@@ -16,7 +16,7 @@ jobs:
|
|||||||
- name: Setup PHP
|
- name: Setup PHP
|
||||||
uses: shivammathur/setup-php@v2
|
uses: shivammathur/setup-php@v2
|
||||||
with:
|
with:
|
||||||
php-version: "8.3"
|
php-version: "8.4"
|
||||||
extensions: bcmath, curl, gd, mbstring, mysql, openssl, pdo, tokenizer, xml, zip
|
extensions: bcmath, curl, gd, mbstring, mysql, openssl, pdo, tokenizer, xml, zip
|
||||||
tools: composer:v2
|
tools: composer:v2
|
||||||
coverage: none
|
coverage: none
|
||||||
@@ -35,7 +35,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: true
|
fail-fast: true
|
||||||
matrix:
|
matrix:
|
||||||
php: [ 8.2, 8.3, 8.4 ]
|
php: [8.2, 8.3, 8.4, 8.5]
|
||||||
steps:
|
steps:
|
||||||
- name: Code Checkout
|
- name: Code Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|||||||
52
Dockerfile
52
Dockerfile
@@ -2,7 +2,7 @@
|
|||||||
# Pelican Production Dockerfile
|
# Pelican Production Dockerfile
|
||||||
|
|
||||||
##
|
##
|
||||||
# If you want to build this locally you want to run `docker build -f Dockerfile.dev`
|
# If you want to build this locally you want to run `docker build -f Dockerfile.dev .`
|
||||||
##
|
##
|
||||||
|
|
||||||
# ================================
|
# ================================
|
||||||
@@ -38,7 +38,7 @@ RUN yarn config set network-timeout 300000 \
|
|||||||
FROM --platform=$TARGETOS/$TARGETARCH composer AS composerbuild
|
FROM --platform=$TARGETOS/$TARGETARCH composer AS composerbuild
|
||||||
|
|
||||||
# Copy full code to optimize autoload
|
# Copy full code to optimize autoload
|
||||||
COPY --exclude=Caddyfile --exclude=docker/ . ./
|
COPY --exclude=docker/ . ./
|
||||||
|
|
||||||
RUN composer dump-autoload --optimize
|
RUN composer dump-autoload --optimize
|
||||||
|
|
||||||
@@ -50,7 +50,7 @@ FROM --platform=$TARGETOS/$TARGETARCH yarn AS yarnbuild
|
|||||||
WORKDIR /build
|
WORKDIR /build
|
||||||
|
|
||||||
# Copy full code
|
# Copy full code
|
||||||
COPY --exclude=Caddyfile --exclude=docker/ . ./
|
COPY --exclude=docker/ . ./
|
||||||
COPY --from=composer /build .
|
COPY --from=composer /build .
|
||||||
|
|
||||||
RUN yarn run build
|
RUN yarn run build
|
||||||
@@ -62,37 +62,35 @@ FROM --platform=$TARGETOS/$TARGETARCH localhost:5000/base-php:$TARGETARCH AS fin
|
|||||||
|
|
||||||
WORKDIR /var/www/html
|
WORKDIR /var/www/html
|
||||||
|
|
||||||
# Install additional required libraries
|
|
||||||
RUN apk add --no-cache \
|
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 composer binary for runtime plugin dependency management
|
||||||
COPY --chown=root:www-data --chmod=640 --from=yarnbuild /build/public ./public
|
COPY --from=composer /usr/local/bin/composer /usr/local/bin/composer
|
||||||
|
COPY --chown=root:www-data --chmod=770 --from=composerbuild /build .
|
||||||
# Set permissions
|
COPY --chown=root:www-data --chmod=770 --from=yarnbuild /build/public ./public
|
||||||
# First ensure all files are owned by root and restrict www-data to read access
|
|
||||||
RUN chown root:www-data ./ \
|
|
||||||
&& chmod 750 ./ \
|
|
||||||
# Files should not have execute set, but directories need it
|
|
||||||
&& find ./ -type d -exec chmod 750 {} \; \
|
|
||||||
# Create necessary directories
|
|
||||||
&& mkdir -p /pelican-data/storage /var/www/html/storage/app/public /var/run/supervisord /etc/supercronic \
|
|
||||||
# Symlinks for env, database, and avatars
|
|
||||||
&& ln -s /pelican-data/.env ./.env \
|
|
||||||
&& ln -s /pelican-data/database/database.sqlite ./database/database.sqlite \
|
|
||||||
&& ln -sf /var/www/html/storage/app/public /var/www/html/public/storage \
|
|
||||||
&& ln -s /pelican-data/storage/avatars /var/www/html/storage/app/public/avatars \
|
|
||||||
&& ln -s /pelican-data/storage/fonts /var/www/html/storage/app/public/fonts \
|
|
||||||
# Allow www-data write permissions where necessary
|
|
||||||
&& chown -R www-data:www-data /pelican-data ./storage ./bootstrap/cache /var/run/supervisord /var/www/html/public/storage \
|
|
||||||
&& chmod -R u+rwX,g+rwX,o-rwx /pelican-data ./storage ./bootstrap/cache /var/run/supervisord \
|
|
||||||
&& 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
|
# Configure Supervisor
|
||||||
COPY docker/supervisord.conf /etc/supervisord.conf
|
COPY docker/supervisord.conf /etc/supervisord.conf
|
||||||
COPY docker/Caddyfile /etc/caddy/Caddyfile
|
COPY docker/Caddyfile /etc/caddy/Caddyfile
|
||||||
# Add Laravel scheduler to crontab
|
# 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/entrypoint.sh /entrypoint.sh
|
||||||
COPY docker/healthcheck.sh /healthcheck.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/
|
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
|
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/
|
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
|
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
|
FROM --platform=$TARGETOS/$TARGETARCH composer AS composerbuild
|
||||||
|
|
||||||
# Copy full code to optimize autoload
|
# Copy full code to optimize autoload
|
||||||
COPY --exclude=Caddyfile --exclude=docker/ . ./
|
COPY --exclude=docker/ . ./
|
||||||
|
|
||||||
RUN composer dump-autoload --optimize
|
RUN composer dump-autoload --optimize
|
||||||
|
|
||||||
@@ -54,7 +54,7 @@ FROM --platform=$TARGETOS/$TARGETARCH yarn AS yarnbuild
|
|||||||
WORKDIR /build
|
WORKDIR /build
|
||||||
|
|
||||||
# Copy full code
|
# Copy full code
|
||||||
COPY --exclude=Caddyfile --exclude=docker/ . ./
|
COPY --exclude=docker/ . ./
|
||||||
COPY --from=composer /build .
|
COPY --from=composer /build .
|
||||||
|
|
||||||
RUN yarn run build
|
RUN yarn run build
|
||||||
@@ -68,35 +68,35 @@ WORKDIR /var/www/html
|
|||||||
|
|
||||||
# Install additional required libraries
|
# Install additional required libraries
|
||||||
RUN apk add --no-cache \
|
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 composer binary for runtime plugin dependency management
|
||||||
COPY --chown=root:www-data --chmod=640 --from=yarnbuild /build/public ./public
|
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
|
# Create and remove directories
|
||||||
# First ensure all files are owned by root and restrict www-data to read access
|
RUN mkdir -p /pelican-data/storage /pelican-data/plugins /var/run/supervisord \
|
||||||
RUN chown root:www-data ./ \
|
&& rm -rf /var/www/html/plugins \
|
||||||
&& chmod 750 ./ \
|
# Symlinks for env, database, storage, and plugins
|
||||||
# Files should not have execute set, but directories need it
|
&& ln -s /pelican-data/.env /var/www/html/.env \
|
||||||
&& find ./ -type d -exec chmod 750 {} \; \
|
&& ln -s /pelican-data/database/database.sqlite ./database/database.sqlite \
|
||||||
# Create necessary directories
|
&& ln -s /pelican-data/storage /var/www/html/public/storage \
|
||||||
&& mkdir -p /pelican-data/storage /var/www/html/storage/app/public /var/run/supervisord /etc/supercronic \
|
&& ln -s /pelican-data/storage /var/www/html/storage/app/public \
|
||||||
# Symlinks for env, database, and avatars
|
&& ln -s /pelican-data/plugins /var/www/html \
|
||||||
&& ln -s /pelican-data/.env ./.env \
|
# Allow www-data write permissions where necessary
|
||||||
&& ln -s /pelican-data/database/database.sqlite ./database/database.sqlite \
|
&& chown -R www-data: /pelican-data .env ./storage ./plugins ./bootstrap/cache /var/run/supervisord /var/www/html/public/storage \
|
||||||
&& ln -sf /var/www/html/storage/app/public /var/www/html/public/storage \
|
&& chmod -R 770 /pelican-data ./storage ./bootstrap/cache /var/run/supervisord \
|
||||||
&& ln -s /pelican-data/storage/avatars /var/www/html/storage/app/public/avatars \
|
&& chown -R www-data: /usr/local/etc/php/ /usr/local/etc/php-fpm.d/ /var/www/html/composer.json /var/www/html/composer.lock
|
||||||
&& ln -s /pelican-data/storage/fonts /var/www/html/storage/app/public/fonts \
|
|
||||||
# Allow www-data write permissions where necessary
|
|
||||||
&& chown -R www-data:www-data /pelican-data ./storage ./bootstrap/cache /var/run/supervisord /var/www/html/public/storage \
|
|
||||||
&& chmod -R u+rwX,g+rwX,o-rwx /pelican-data ./storage ./bootstrap/cache /var/run/supervisord \
|
|
||||||
&& chown -R www-data: /usr/local/etc/php/
|
|
||||||
|
|
||||||
# Configure Supervisor
|
# Configure Supervisor
|
||||||
COPY docker/supervisord.conf /etc/supervisord.conf
|
COPY docker/supervisord.conf /etc/supervisord.conf
|
||||||
COPY docker/Caddyfile /etc/caddy/Caddyfile
|
COPY docker/Caddyfile /etc/caddy/Caddyfile
|
||||||
# Add Laravel scheduler to crontab
|
# 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/entrypoint.sh /entrypoint.sh
|
||||||
COPY docker/healthcheck.sh /healthcheck.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 Exception;
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
use Illuminate\Support\Facades\Http;
|
use Illuminate\Support\Facades\Http;
|
||||||
use JsonException;
|
|
||||||
use Symfony\Component\Yaml\Yaml;
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
|
||||||
class CheckEggUpdatesCommand extends Command
|
class CheckEggUpdatesCommand extends Command
|
||||||
@@ -22,14 +21,12 @@ class CheckEggUpdatesCommand extends Command
|
|||||||
try {
|
try {
|
||||||
$this->check($egg, $exporterService);
|
$this->check($egg, $exporterService);
|
||||||
} catch (Exception $exception) {
|
} catch (Exception $exception) {
|
||||||
$this->error("{$egg->name}: Error ({$exception->getMessage()})");
|
$this->error("$egg->name: Error ({$exception->getMessage()})");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** @throws Exception */
|
||||||
* @throws JsonException
|
|
||||||
*/
|
|
||||||
private function check(Egg $egg, EggExporterService $exporterService): void
|
private function check(Egg $egg, EggExporterService $exporterService): void
|
||||||
{
|
{
|
||||||
if (is_null($egg->update_url)) {
|
if (is_null($egg->update_url)) {
|
||||||
@@ -45,7 +42,13 @@ class CheckEggUpdatesCommand extends Command
|
|||||||
? Yaml::parse($exporterService->handle($egg->id, EggFormat::YAML))
|
? Yaml::parse($exporterService->handle($egg->id, EggFormat::YAML))
|
||||||
: json_decode($exporterService->handle($egg->id, EggFormat::JSON), true);
|
: 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);
|
$remote = $isYaml ? Yaml::parse($remote) : json_decode($remote, true);
|
||||||
|
|
||||||
unset($local['exported_at'], $remote['exported_at']);
|
unset($local['exported_at'], $remote['exported_at']);
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ class UpdateEggIndexCommand extends Command
|
|||||||
public function handle(): int
|
public function handle(): int
|
||||||
{
|
{
|
||||||
try {
|
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) {
|
} catch (Exception $exception) {
|
||||||
$this->error($exception->getMessage());
|
$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');
|
||||||
|
}
|
||||||
|
}
|
||||||
25
app/Console/Commands/Plugin/ComposerPluginsCommand.php
Normal file
25
app/Console/Commands/Plugin/ComposerPluginsCommand.php
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands\Plugin;
|
||||||
|
|
||||||
|
use App\Services\Helpers\PluginService;
|
||||||
|
use Exception;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
|
||||||
|
class ComposerPluginsCommand extends Command
|
||||||
|
{
|
||||||
|
protected $signature = 'p:plugin:composer';
|
||||||
|
|
||||||
|
protected $description = 'Makes sure the needed composer packages for all installed plugins are available.';
|
||||||
|
|
||||||
|
public function handle(PluginService $pluginService): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$pluginService->manageComposerPackages();
|
||||||
|
} catch (Exception $exception) {
|
||||||
|
report($exception);
|
||||||
|
|
||||||
|
$this->error($exception->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
37
app/Console/Commands/Plugin/DisablePluginCommand.php
Normal file
37
app/Console/Commands/Plugin/DisablePluginCommand.php
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands\Plugin;
|
||||||
|
|
||||||
|
use App\Models\Plugin;
|
||||||
|
use App\Services\Helpers\PluginService;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
|
||||||
|
class DisablePluginCommand extends Command
|
||||||
|
{
|
||||||
|
protected $signature = 'p:plugin:disable {id?}';
|
||||||
|
|
||||||
|
protected $description = 'Disables a plugin';
|
||||||
|
|
||||||
|
public function handle(PluginService $pluginService): void
|
||||||
|
{
|
||||||
|
$id = $this->argument('id') ?? $this->choice('Plugin', Plugin::pluck('name', 'id')->toArray());
|
||||||
|
|
||||||
|
$plugin = Plugin::find(str($id)->lower()->toString());
|
||||||
|
|
||||||
|
if (!$plugin) {
|
||||||
|
$this->error('Plugin does not exist!');
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$plugin->canDisable()) {
|
||||||
|
$this->error("Plugin can't be disabled!");
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$pluginService->disablePlugin($plugin);
|
||||||
|
|
||||||
|
$this->info('Plugin disabled.');
|
||||||
|
}
|
||||||
|
}
|
||||||
43
app/Console/Commands/Plugin/InstallPluginCommand.php
Normal file
43
app/Console/Commands/Plugin/InstallPluginCommand.php
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands\Plugin;
|
||||||
|
|
||||||
|
use App\Enums\PluginStatus;
|
||||||
|
use App\Models\Plugin;
|
||||||
|
use App\Services\Helpers\PluginService;
|
||||||
|
use Exception;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
|
||||||
|
class InstallPluginCommand extends Command
|
||||||
|
{
|
||||||
|
protected $signature = 'p:plugin:install {id?}';
|
||||||
|
|
||||||
|
protected $description = 'Installs a plugin';
|
||||||
|
|
||||||
|
public function handle(PluginService $pluginService): void
|
||||||
|
{
|
||||||
|
$id = $this->argument('id') ?? $this->choice('Plugin', Plugin::pluck('name', 'id')->toArray());
|
||||||
|
|
||||||
|
$plugin = Plugin::find(str($id)->lower()->toString());
|
||||||
|
|
||||||
|
if (!$plugin) {
|
||||||
|
$this->error('Plugin does not exist!');
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($plugin->status !== PluginStatus::NotInstalled) {
|
||||||
|
$this->error('Plugin is already installed!');
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$pluginService->installPlugin($plugin);
|
||||||
|
|
||||||
|
$this->info('Plugin installed and enabled.');
|
||||||
|
} catch (Exception $exception) {
|
||||||
|
$this->error('Could not install plugin: ' . $exception->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
28
app/Console/Commands/Plugin/ListPluginsCommand.php
Normal file
28
app/Console/Commands/Plugin/ListPluginsCommand.php
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands\Plugin;
|
||||||
|
|
||||||
|
use App\Models\Plugin;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
|
||||||
|
class ListPluginsCommand extends Command
|
||||||
|
{
|
||||||
|
protected $signature = 'p:plugin:list';
|
||||||
|
|
||||||
|
protected $description = 'List all installed plugins';
|
||||||
|
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
$plugins = Plugin::query()->get(['name', 'author', 'status', 'version', 'panels', 'category']);
|
||||||
|
|
||||||
|
if (count($plugins) < 1) {
|
||||||
|
$this->warn('No plugins installed');
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->table(['Name', 'Author', 'Status', 'Version', 'Panels', 'Category'], $plugins->toArray());
|
||||||
|
|
||||||
|
$this->output->newLine();
|
||||||
|
}
|
||||||
|
}
|
||||||
135
app/Console/Commands/Plugin/MakePluginCommand.php
Normal file
135
app/Console/Commands/Plugin/MakePluginCommand.php
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands\Plugin;
|
||||||
|
|
||||||
|
use App\Enums\PluginCategory;
|
||||||
|
use App\Enums\PluginStatus;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
use Illuminate\Filesystem\Filesystem;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
|
class MakePluginCommand extends Command
|
||||||
|
{
|
||||||
|
protected $signature = 'p:plugin:make
|
||||||
|
{--name=}
|
||||||
|
{--author=}
|
||||||
|
{--description=}
|
||||||
|
{--category=}
|
||||||
|
{--url=}
|
||||||
|
{--updateUrl=}
|
||||||
|
{--panels=}
|
||||||
|
{--panelVersion=}';
|
||||||
|
|
||||||
|
protected $description = 'Create a new plugin';
|
||||||
|
|
||||||
|
public function __construct(private Filesystem $filesystem)
|
||||||
|
{
|
||||||
|
parent::__construct();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
$name = $this->option('name') ?? $this->ask('Name');
|
||||||
|
$name = preg_replace('/[^A-Za-z0-9 ]/', '', Str::ascii($name));
|
||||||
|
|
||||||
|
$id = Str::slug($name);
|
||||||
|
|
||||||
|
if ($this->filesystem->exists(plugin_path($id))) {
|
||||||
|
$this->error('Plugin with that name already exists!');
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$author = $this->option('author') ?? $this->ask('Author', cache('plugin.author'));
|
||||||
|
$author = preg_replace('/[^A-Za-z0-9 ]/', '', Str::ascii($author));
|
||||||
|
cache()->forever('plugin.author', $author);
|
||||||
|
|
||||||
|
$namespace = Str::studly($author) . '\\' . Str::studly($name);
|
||||||
|
$class = Str::studly($name . 'Plugin');
|
||||||
|
|
||||||
|
if (class_exists('\\' . $namespace . '\\' . $class)) {
|
||||||
|
$this->error('Plugin class with that name already exists!');
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->info('Creating Plugin "' . $name . '" (' . $id . ') by ' . $author);
|
||||||
|
|
||||||
|
$description = $this->option('description') ?? $this->ask('Description (can be empty)');
|
||||||
|
|
||||||
|
$category = $this->option('category') ?? $this->choice('Category', collect(PluginCategory::cases())->mapWithKeys(fn (PluginCategory $category) => [$category->value => $category->getLabel()])->toArray(), PluginCategory::Plugin->value);
|
||||||
|
|
||||||
|
if (!PluginCategory::tryFrom($category)) {
|
||||||
|
$this->error('Unknown plugin category!');
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$url = $this->option('url') ?? $this->ask('URL (can be empty)');
|
||||||
|
$updateUrl = $this->option('updateUrl') ?? $this->ask('Update URL (can be empty)');
|
||||||
|
|
||||||
|
$panels = $this->option('panels');
|
||||||
|
if (!$panels) {
|
||||||
|
if ($this->confirm('Should the plugin be available on all panels?', true)) {
|
||||||
|
$panels = null;
|
||||||
|
} else {
|
||||||
|
$panels = $this->choice('Panels (comma separated list)', [
|
||||||
|
'admin' => 'Admin Area',
|
||||||
|
'server' => 'Client Area',
|
||||||
|
'app' => 'Server List',
|
||||||
|
], multiple: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$panels = is_string($panels) ? explode(',', $panels) : $panels;
|
||||||
|
|
||||||
|
$panelVersion = $this->option('panelVersion');
|
||||||
|
if (!$panelVersion) {
|
||||||
|
$panelVersion = $this->ask('Required panel version (leave empty for no constraint)', config('app.version') === 'canary' ? null : config('app.version'));
|
||||||
|
|
||||||
|
if ($panelVersion && $this->confirm("Should the version constraint be minimal instead of strict? ($panelVersion or higher instead of only $panelVersion)")) {
|
||||||
|
$panelVersion = "^$panelVersion";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$composerPackages = null;
|
||||||
|
// TODO: ask for composer packages?
|
||||||
|
|
||||||
|
// Create base directory
|
||||||
|
$this->filesystem->makeDirectory(plugin_path($id));
|
||||||
|
|
||||||
|
// Write plugin.json
|
||||||
|
$this->filesystem->put(plugin_path($id, 'plugin.json'), json_encode([
|
||||||
|
'id' => $id,
|
||||||
|
'name' => $name,
|
||||||
|
'author' => $author,
|
||||||
|
'version' => '1.0.0',
|
||||||
|
'description' => $description,
|
||||||
|
'category' => $category,
|
||||||
|
'url' => $url,
|
||||||
|
'update_url' => $updateUrl,
|
||||||
|
'namespace' => $namespace,
|
||||||
|
'class' => $class,
|
||||||
|
'panels' => $panels,
|
||||||
|
'panel_version' => $panelVersion,
|
||||||
|
'composer_packages' => $composerPackages,
|
||||||
|
'meta' => [
|
||||||
|
'status' => PluginStatus::Enabled,
|
||||||
|
'status_message' => null,
|
||||||
|
],
|
||||||
|
], JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
|
||||||
|
|
||||||
|
// Create src directory and create main class
|
||||||
|
$this->filesystem->makeDirectory(plugin_path($id, 'src'));
|
||||||
|
$this->filesystem->put(plugin_path($id, 'src', $class . '.php'), Str::replace(['$namespace$', '$class$', '$id$'], [$namespace, $class, $id], file_get_contents(__DIR__ . '/Plugin.stub')));
|
||||||
|
|
||||||
|
// Create Providers directory and create service provider
|
||||||
|
$this->filesystem->makeDirectory(plugin_path($id, 'src', 'Providers'));
|
||||||
|
$this->filesystem->put(plugin_path($id, 'src', 'Providers', $class . 'Provider.php'), Str::replace(['$namespace$', '$class$'], [$namespace, $class], file_get_contents(__DIR__ . '/PluginProvider.stub')));
|
||||||
|
|
||||||
|
// Create config directory and create config file
|
||||||
|
$this->filesystem->makeDirectory(plugin_path($id, 'config'));
|
||||||
|
$this->filesystem->put(plugin_path($id, 'config', $id . '.php'), Str::replace(['$name$'], [$name], file_get_contents(__DIR__ . '/PluginConfig.stub')));
|
||||||
|
|
||||||
|
$this->info('Plugin created.');
|
||||||
|
}
|
||||||
|
}
|
||||||
25
app/Console/Commands/Plugin/Plugin.stub
Normal file
25
app/Console/Commands/Plugin/Plugin.stub
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace $namespace$;
|
||||||
|
|
||||||
|
use Filament\Contracts\Plugin;
|
||||||
|
use Filament\Panel;
|
||||||
|
|
||||||
|
class $class$ implements Plugin
|
||||||
|
{
|
||||||
|
public function getId(): string
|
||||||
|
{
|
||||||
|
return '$id$';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function register(Panel $panel): void
|
||||||
|
{
|
||||||
|
// Allows you to use any configuration option that is available to the panel.
|
||||||
|
// This includes registering resources, custom pages, themes, render hooks and more.
|
||||||
|
}
|
||||||
|
|
||||||
|
public function boot(Panel $panel): void
|
||||||
|
{
|
||||||
|
// Is run only when the panel that the plugin is being registered to is actually in-use. It is executed by a middleware class.
|
||||||
|
}
|
||||||
|
}
|
||||||
5
app/Console/Commands/Plugin/PluginConfig.stub
Normal file
5
app/Console/Commands/Plugin/PluginConfig.stub
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
// Config values for $name$
|
||||||
|
];
|
||||||
18
app/Console/Commands/Plugin/PluginProvider.stub
Normal file
18
app/Console/Commands/Plugin/PluginProvider.stub
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace $namespace$\Providers;
|
||||||
|
|
||||||
|
use Illuminate\Support\ServiceProvider;
|
||||||
|
|
||||||
|
class $class$Provider extends ServiceProvider
|
||||||
|
{
|
||||||
|
public function register(): void
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
public function boot(): void
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
}
|
||||||
48
app/Console/Commands/Plugin/UninstallPluginCommand.php
Normal file
48
app/Console/Commands/Plugin/UninstallPluginCommand.php
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands\Plugin;
|
||||||
|
|
||||||
|
use App\Enums\PluginStatus;
|
||||||
|
use App\Models\Plugin;
|
||||||
|
use App\Services\Helpers\PluginService;
|
||||||
|
use Exception;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
|
||||||
|
class UninstallPluginCommand extends Command
|
||||||
|
{
|
||||||
|
protected $signature = 'p:plugin:uninstall {id?} {--delete : Delete the plugin files}';
|
||||||
|
|
||||||
|
protected $description = 'Uninstalls a plugin';
|
||||||
|
|
||||||
|
public function handle(PluginService $pluginService): void
|
||||||
|
{
|
||||||
|
$id = $this->argument('id') ?? $this->choice('Plugin', Plugin::pluck('name', 'id')->toArray());
|
||||||
|
|
||||||
|
$plugin = Plugin::find(str($id)->lower()->toString());
|
||||||
|
|
||||||
|
if (!$plugin) {
|
||||||
|
$this->error('Plugin does not exist!');
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($plugin->status === PluginStatus::NotInstalled) {
|
||||||
|
$this->error('Plugin is not installed!');
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$deleteFiles = $this->option('delete');
|
||||||
|
if ($this->input->isInteractive() && !$deleteFiles) {
|
||||||
|
$deleteFiles = $this->confirm('Do you also want to delete the plugin files?');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$pluginService->uninstallPlugin($plugin, $deleteFiles);
|
||||||
|
|
||||||
|
$this->info('Plugin uninstalled' . ($deleteFiles ? ' and files deleted' : '') . '.');
|
||||||
|
} catch (Exception $exception) {
|
||||||
|
$this->error('Could not uninstall plugin: ' . $exception->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
42
app/Console/Commands/Plugin/UpdatePluginCommand.php
Normal file
42
app/Console/Commands/Plugin/UpdatePluginCommand.php
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands\Plugin;
|
||||||
|
|
||||||
|
use App\Models\Plugin;
|
||||||
|
use App\Services\Helpers\PluginService;
|
||||||
|
use Exception;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
|
||||||
|
class UpdatePluginCommand extends Command
|
||||||
|
{
|
||||||
|
protected $signature = 'p:plugin:update {id?}';
|
||||||
|
|
||||||
|
protected $description = 'Updates a plugin';
|
||||||
|
|
||||||
|
public function handle(PluginService $pluginService): void
|
||||||
|
{
|
||||||
|
$id = $this->argument('id') ?? $this->choice('Plugin', Plugin::pluck('name', 'id')->toArray());
|
||||||
|
|
||||||
|
$plugin = Plugin::find(str($id)->lower()->toString());
|
||||||
|
|
||||||
|
if (!$plugin) {
|
||||||
|
$this->error('Plugin does not exist!');
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$plugin->isUpdateAvailable()) {
|
||||||
|
$this->error("Plugin doesn't need updating!");
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$pluginService->updatePlugin($plugin);
|
||||||
|
|
||||||
|
$this->info('Plugin updated.');
|
||||||
|
} catch (Exception $exception) {
|
||||||
|
$this->error('Could not update plugin: ' . $exception->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,12 +2,13 @@
|
|||||||
|
|
||||||
namespace App\Contracts\Http;
|
namespace App\Contracts\Http;
|
||||||
|
|
||||||
|
use App\Enums\SubuserPermission;
|
||||||
|
|
||||||
interface ClientPermissionsRequest
|
interface ClientPermissionsRequest
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Returns the permissions string indicating which permission should be used to
|
* Returns the permission used to validate that the authenticated user may perform
|
||||||
* validate that the authenticated user has permission to perform this action against
|
* this action against the given resource (server).
|
||||||
* the given resource (server).
|
|
||||||
*/
|
*/
|
||||||
public function permission(): string;
|
public function permission(): SubuserPermission|string;
|
||||||
}
|
}
|
||||||
|
|||||||
18
app/Contracts/Plugins/HasPluginSettings.php
Normal file
18
app/Contracts/Plugins/HasPluginSettings.php
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Contracts\Plugins;
|
||||||
|
|
||||||
|
use Filament\Schemas\Components\Component;
|
||||||
|
|
||||||
|
interface HasPluginSettings
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @return Component[]
|
||||||
|
*/
|
||||||
|
public function getSettingsForm(): array;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<mixed, mixed> $data
|
||||||
|
*/
|
||||||
|
public function saveSettings(array $data): void;
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Enums;
|
namespace App\Enums;
|
||||||
|
|
||||||
|
use BackedEnum;
|
||||||
use Filament\Support\Contracts\HasColor;
|
use Filament\Support\Contracts\HasColor;
|
||||||
use Filament\Support\Contracts\HasIcon;
|
use Filament\Support\Contracts\HasIcon;
|
||||||
use Filament\Support\Contracts\HasLabel;
|
use Filament\Support\Contracts\HasLabel;
|
||||||
@@ -12,12 +13,12 @@ enum BackupStatus: string implements HasColor, HasIcon, HasLabel
|
|||||||
case Successful = 'successful';
|
case Successful = 'successful';
|
||||||
case Failed = 'failed';
|
case Failed = 'failed';
|
||||||
|
|
||||||
public function getIcon(): string
|
public function getIcon(): BackedEnum
|
||||||
{
|
{
|
||||||
return match ($this) {
|
return match ($this) {
|
||||||
self::InProgress => 'tabler-circle-dashed',
|
self::InProgress => TablerIcon::CircleDashed,
|
||||||
self::Successful => 'tabler-circle-check',
|
self::Successful => TablerIcon::CircleCheck,
|
||||||
self::Failed => 'tabler-circle-x',
|
self::Failed => TablerIcon::CircleX,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Enums;
|
namespace App\Enums;
|
||||||
|
|
||||||
|
use BackedEnum;
|
||||||
use Filament\Support\Contracts\HasColor;
|
use Filament\Support\Contracts\HasColor;
|
||||||
use Filament\Support\Contracts\HasIcon;
|
use Filament\Support\Contracts\HasIcon;
|
||||||
use Filament\Support\Contracts\HasLabel;
|
use Filament\Support\Contracts\HasLabel;
|
||||||
@@ -23,20 +24,20 @@ enum ContainerStatus: string implements HasColor, HasIcon, HasLabel
|
|||||||
// HTTP Based
|
// HTTP Based
|
||||||
case Missing = 'missing';
|
case Missing = 'missing';
|
||||||
|
|
||||||
public function getIcon(): string
|
public function getIcon(): BackedEnum
|
||||||
{
|
{
|
||||||
return match ($this) {
|
return match ($this) {
|
||||||
|
|
||||||
self::Created => 'tabler-heart-plus',
|
self::Created => TablerIcon::HeartPlus,
|
||||||
self::Starting => 'tabler-heart-up',
|
self::Starting => TablerIcon::HeartUp,
|
||||||
self::Running => 'tabler-heartbeat',
|
self::Running => TablerIcon::Heartbeat,
|
||||||
self::Restarting => 'tabler-heart-bolt',
|
self::Restarting => TablerIcon::HeartBolt,
|
||||||
self::Exited => 'tabler-heart-exclamation',
|
self::Exited => TablerIcon::HeartExclamation,
|
||||||
self::Paused => 'tabler-heart-pause',
|
self::Paused => TablerIcon::HeartPause,
|
||||||
self::Dead, self::Offline => 'tabler-heart-x',
|
self::Dead, self::Offline => TablerIcon::HeartX,
|
||||||
self::Removing => 'tabler-heart-down',
|
self::Removing => TablerIcon::HeartDown,
|
||||||
self::Missing => 'tabler-heart-search',
|
self::Missing => TablerIcon::HeartSearch,
|
||||||
self::Stopping => 'tabler-heart-minus',
|
self::Stopping => TablerIcon::HeartMinus,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,9 @@ enum CustomizationKey: string
|
|||||||
case TopNavigation = 'top_navigation';
|
case TopNavigation = 'top_navigation';
|
||||||
case DashboardLayout = 'dashboard_layout';
|
case DashboardLayout = 'dashboard_layout';
|
||||||
|
|
||||||
|
case ButtonStyle = 'button_style';
|
||||||
|
case RedirectToAdmin = 'redirect_to_admin';
|
||||||
|
|
||||||
public function getDefaultValue(): string|int|bool
|
public function getDefaultValue(): string|int|bool
|
||||||
{
|
{
|
||||||
return match ($this) {
|
return match ($this) {
|
||||||
@@ -20,6 +23,8 @@ enum CustomizationKey: string
|
|||||||
self::ConsoleGraphPeriod => 30,
|
self::ConsoleGraphPeriod => 30,
|
||||||
self::TopNavigation => config('panel.filament.default-navigation', 'sidebar'),
|
self::TopNavigation => config('panel.filament.default-navigation', 'sidebar'),
|
||||||
self::DashboardLayout => 'grid',
|
self::DashboardLayout => 'grid',
|
||||||
|
self::ButtonStyle => true,
|
||||||
|
self::RedirectToAdmin => false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
141
app/Enums/EditorLanguages.php
Normal file
141
app/Enums/EditorLanguages.php
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Enums;
|
||||||
|
|
||||||
|
use Filament\Support\Contracts\HasLabel;
|
||||||
|
|
||||||
|
enum EditorLanguages: string implements HasLabel
|
||||||
|
{
|
||||||
|
case plaintext = 'plaintext';
|
||||||
|
case abap = 'abap';
|
||||||
|
case apex = 'apex';
|
||||||
|
case azcali = 'azcali';
|
||||||
|
case bat = 'bat';
|
||||||
|
case bicep = 'bicep';
|
||||||
|
case cameligo = 'cameligo';
|
||||||
|
case clojure = 'clojure';
|
||||||
|
case coffeescript = 'coffeescript';
|
||||||
|
case c = 'c';
|
||||||
|
case cpp = 'cpp';
|
||||||
|
case csharp = 'csharp';
|
||||||
|
case csp = 'csp';
|
||||||
|
case css = 'css';
|
||||||
|
case cypher = 'cypher';
|
||||||
|
case dart = 'dart';
|
||||||
|
case dockerfile = 'dockerfile';
|
||||||
|
case ecl = 'ecl';
|
||||||
|
case elixir = 'elixir';
|
||||||
|
case flow9 = 'flow9';
|
||||||
|
case fsharp = 'fsharp';
|
||||||
|
case go = 'go';
|
||||||
|
case graphql = 'graphql';
|
||||||
|
case handlebars = 'handlebars';
|
||||||
|
case hcl = 'hcl';
|
||||||
|
case html = 'html';
|
||||||
|
case ini = 'ini';
|
||||||
|
case java = 'java';
|
||||||
|
case javascript = 'javascript';
|
||||||
|
case julia = 'julia';
|
||||||
|
case json = 'json';
|
||||||
|
case kotlin = 'kotlin';
|
||||||
|
case less = 'less';
|
||||||
|
case lexon = 'lexon';
|
||||||
|
case lua = 'lua';
|
||||||
|
case liquid = 'liquid';
|
||||||
|
case m3 = 'm3';
|
||||||
|
case markdown = 'markdown';
|
||||||
|
case mdx = 'mdx';
|
||||||
|
case mips = 'mips';
|
||||||
|
case msdax = 'msdax';
|
||||||
|
case mysql = 'mysql';
|
||||||
|
case objectivec = 'objective-c';
|
||||||
|
case pascal = 'pascal';
|
||||||
|
case pascaligo = 'pascaligo';
|
||||||
|
case perl = 'perl';
|
||||||
|
case pgsql = 'pgsql';
|
||||||
|
case php = 'php';
|
||||||
|
case pla = 'pla';
|
||||||
|
case postiats = 'postiats';
|
||||||
|
case powerquery = 'powerquery';
|
||||||
|
case powershell = 'powershell';
|
||||||
|
case proto = 'proto';
|
||||||
|
case pug = 'pug';
|
||||||
|
case python = 'python';
|
||||||
|
case qsharp = 'qsharp';
|
||||||
|
case r = 'r';
|
||||||
|
case razor = 'razor';
|
||||||
|
case redis = 'redis';
|
||||||
|
case redshift = 'redshift';
|
||||||
|
case restructuredtext = 'restructuredtext';
|
||||||
|
case ruby = 'ruby';
|
||||||
|
case rust = 'rust';
|
||||||
|
case sb = 'sb';
|
||||||
|
case scala = 'scala';
|
||||||
|
case scheme = 'scheme';
|
||||||
|
case scss = 'scss';
|
||||||
|
case shell = 'shell';
|
||||||
|
case sol = 'sol';
|
||||||
|
case aes = 'aes';
|
||||||
|
case sparql = 'sparql';
|
||||||
|
case sql = 'sql';
|
||||||
|
case st = 'st';
|
||||||
|
case swift = 'swift';
|
||||||
|
case systemverilog = 'systemverilog';
|
||||||
|
case verilog = 'verilog';
|
||||||
|
case tcl = 'tcl';
|
||||||
|
case twig = 'twig';
|
||||||
|
case typescript = 'typescript';
|
||||||
|
case typespec = 'typespec';
|
||||||
|
case vb = 'vb';
|
||||||
|
case wgsl = 'wgsl';
|
||||||
|
case xml = 'xml';
|
||||||
|
case yaml = 'yaml';
|
||||||
|
|
||||||
|
public static function fromWithAlias(string $match): self
|
||||||
|
{
|
||||||
|
return match ($match) {
|
||||||
|
'h' => self::c,
|
||||||
|
|
||||||
|
'cc', 'hpp' => self::cpp,
|
||||||
|
|
||||||
|
'cs' => self::csharp,
|
||||||
|
|
||||||
|
'class' => self::java,
|
||||||
|
|
||||||
|
'htm' => self::html,
|
||||||
|
|
||||||
|
'js', 'mjs', 'cjs' => self::javascript,
|
||||||
|
|
||||||
|
'kt', 'kts' => self::kotlin,
|
||||||
|
|
||||||
|
'md' => self::markdown,
|
||||||
|
|
||||||
|
'm' => self::objectivec,
|
||||||
|
|
||||||
|
'pl', 'pm' => self::perl,
|
||||||
|
|
||||||
|
'php3', 'php4', 'php5', 'phtml' => self::php,
|
||||||
|
|
||||||
|
'py', 'pyc', 'pyo', 'pyi' => self::python,
|
||||||
|
|
||||||
|
'rdata', 'rds' => self::r,
|
||||||
|
|
||||||
|
'rb', 'erb' => self::ruby,
|
||||||
|
|
||||||
|
'sc' => self::scala,
|
||||||
|
|
||||||
|
'sh', 'zsh' => self::shell,
|
||||||
|
|
||||||
|
'ts', 'tsx' => self::typescript,
|
||||||
|
|
||||||
|
'yml' => self::yaml,
|
||||||
|
|
||||||
|
default => self::tryFrom($match) ?? self::plaintext,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getLabel(): string
|
||||||
|
{
|
||||||
|
return $this->name;
|
||||||
|
}
|
||||||
|
}
|
||||||
28
app/Enums/PluginCategory.php
Normal file
28
app/Enums/PluginCategory.php
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Enums;
|
||||||
|
|
||||||
|
use BackedEnum;
|
||||||
|
use Filament\Support\Contracts\HasIcon;
|
||||||
|
use Filament\Support\Contracts\HasLabel;
|
||||||
|
|
||||||
|
enum PluginCategory: string implements HasIcon, HasLabel
|
||||||
|
{
|
||||||
|
case Plugin = 'plugin';
|
||||||
|
case Theme = 'theme';
|
||||||
|
case Language = 'language';
|
||||||
|
|
||||||
|
public function getIcon(): BackedEnum
|
||||||
|
{
|
||||||
|
return match ($this) {
|
||||||
|
self::Plugin => TablerIcon::Package,
|
||||||
|
self::Theme => TablerIcon::Palette,
|
||||||
|
self::Language => TablerIcon::Language,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getLabel(): string
|
||||||
|
{
|
||||||
|
return trans('admin/plugin.category_enum.' . $this->value);
|
||||||
|
}
|
||||||
|
}
|
||||||
44
app/Enums/PluginStatus.php
Normal file
44
app/Enums/PluginStatus.php
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Enums;
|
||||||
|
|
||||||
|
use BackedEnum;
|
||||||
|
use Filament\Support\Contracts\HasColor;
|
||||||
|
use Filament\Support\Contracts\HasIcon;
|
||||||
|
use Filament\Support\Contracts\HasLabel;
|
||||||
|
|
||||||
|
enum PluginStatus: string implements HasColor, HasIcon, HasLabel
|
||||||
|
{
|
||||||
|
case NotInstalled = 'not_installed';
|
||||||
|
case Disabled = 'disabled';
|
||||||
|
case Enabled = 'enabled';
|
||||||
|
case Errored = 'errored';
|
||||||
|
case Incompatible = 'incompatible';
|
||||||
|
|
||||||
|
public function getIcon(): BackedEnum
|
||||||
|
{
|
||||||
|
return match ($this) {
|
||||||
|
self::NotInstalled => TablerIcon::HeartOff,
|
||||||
|
self::Disabled => TablerIcon::HeartX,
|
||||||
|
self::Enabled => TablerIcon::HeartCheck,
|
||||||
|
self::Errored => TablerIcon::HeartBroken,
|
||||||
|
self::Incompatible => TablerIcon::HeartCancel,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getColor(): string
|
||||||
|
{
|
||||||
|
return match ($this) {
|
||||||
|
self::NotInstalled => 'gray',
|
||||||
|
self::Disabled => 'warning',
|
||||||
|
self::Enabled => 'success',
|
||||||
|
self::Errored => 'danger',
|
||||||
|
self::Incompatible => 'danger',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getLabel(): string
|
||||||
|
{
|
||||||
|
return trans('admin/plugin.status_enum.' . $this->value);
|
||||||
|
}
|
||||||
|
}
|
||||||
65
app/Enums/ResourceLimit.php
Normal file
65
app/Enums/ResourceLimit.php
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Enums;
|
||||||
|
|
||||||
|
use App\Models\Server;
|
||||||
|
use Illuminate\Cache\RateLimiting\Limit;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Routing\Middleware\ThrottleRequests;
|
||||||
|
use Illuminate\Support\Facades\RateLimiter;
|
||||||
|
use Webmozart\Assert\Assert;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A basic resource throttler for individual servers. This is applied in addition
|
||||||
|
* to existing rate limits and allows the code to slow down speedy users that might
|
||||||
|
* be creating resources a little too quickly for comfort. This throttle generally
|
||||||
|
* only applies to creation flows, and not general view/edit/delete flows.
|
||||||
|
*/
|
||||||
|
enum ResourceLimit: string
|
||||||
|
{
|
||||||
|
case Websocket = 'websocket';
|
||||||
|
case AllocationCreate = 'allocation-create';
|
||||||
|
case BackupRestore = 'backup-restore';
|
||||||
|
case DatabaseCreate = 'database-create';
|
||||||
|
case ScheduleCreate = 'schedule-create';
|
||||||
|
case SubuserCreate = 'subuser-create';
|
||||||
|
case FilePull = 'file-pull';
|
||||||
|
|
||||||
|
public function throttleKey(): string
|
||||||
|
{
|
||||||
|
return "api.client:server-resource:{$this->name}";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a middleware that will throttle the specific resource by server. This
|
||||||
|
* throttle applies to any user making changes to that resource on the specific
|
||||||
|
* server, it is NOT per-user.
|
||||||
|
*/
|
||||||
|
public function middleware(): string
|
||||||
|
{
|
||||||
|
return ThrottleRequests::using($this->throttleKey());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function limit(): Limit
|
||||||
|
{
|
||||||
|
return match ($this) {
|
||||||
|
self::Websocket => Limit::perMinute(5),
|
||||||
|
self::BackupRestore => Limit::perMinutes(15, 3),
|
||||||
|
self::DatabaseCreate => Limit::perMinute(2),
|
||||||
|
self::SubuserCreate => Limit::perMinutes(15, 10),
|
||||||
|
self::FilePull => Limit::perMinutes(10, 5),
|
||||||
|
default => Limit::perMinute(2),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function boot(): void
|
||||||
|
{
|
||||||
|
foreach (self::cases() as $case) {
|
||||||
|
RateLimiter::for($case->throttleKey(), function (Request $request) use ($case) {
|
||||||
|
Assert::isInstanceOf($server = $request->route()->parameter('server'), Server::class);
|
||||||
|
|
||||||
|
return $case->limit()->by($server->uuid);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ namespace App\Enums;
|
|||||||
enum RolePermissionModels: string
|
enum RolePermissionModels: string
|
||||||
{
|
{
|
||||||
case ApiKey = 'apiKey';
|
case ApiKey = 'apiKey';
|
||||||
|
case Allocation = 'allocation';
|
||||||
case DatabaseHost = 'databaseHost';
|
case DatabaseHost = 'databaseHost';
|
||||||
case Database = 'database';
|
case Database = 'database';
|
||||||
case Egg = 'egg';
|
case Egg = 'egg';
|
||||||
@@ -34,4 +35,9 @@ enum RolePermissionModels: string
|
|||||||
{
|
{
|
||||||
return RolePermissionPrefixes::Update->value . ' ' . $this->value;
|
return RolePermissionPrefixes::Update->value . ' ' . $this->value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function delete(): string
|
||||||
|
{
|
||||||
|
return RolePermissionPrefixes::Delete->value . ' ' . $this->value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Enums;
|
namespace App\Enums;
|
||||||
|
|
||||||
|
use BackedEnum;
|
||||||
use Filament\Support\Contracts\HasColor;
|
use Filament\Support\Contracts\HasColor;
|
||||||
use Filament\Support\Contracts\HasIcon;
|
use Filament\Support\Contracts\HasIcon;
|
||||||
use Filament\Support\Contracts\HasLabel;
|
use Filament\Support\Contracts\HasLabel;
|
||||||
@@ -14,14 +15,13 @@ enum ServerState: string implements HasColor, HasIcon, HasLabel
|
|||||||
case Suspended = 'suspended';
|
case Suspended = 'suspended';
|
||||||
case RestoringBackup = 'restoring_backup';
|
case RestoringBackup = 'restoring_backup';
|
||||||
|
|
||||||
public function getIcon(): string
|
public function getIcon(): BackedEnum
|
||||||
{
|
{
|
||||||
return match ($this) {
|
return match ($this) {
|
||||||
self::Installing => 'tabler-heart-bolt',
|
self::Installing => TablerIcon::HeartBolt,
|
||||||
self::InstallFailed => 'tabler-heart-x',
|
self::InstallFailed, self::ReinstallFailed => TablerIcon::HeartX,
|
||||||
self::ReinstallFailed => 'tabler-heart-x',
|
self::Suspended => TablerIcon::HeartCancel,
|
||||||
self::Suspended => 'tabler-heart-cancel',
|
self::RestoringBackup => TablerIcon::HeartUp,
|
||||||
self::RestoringBackup => 'tabler-heart-up',
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
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';
|
||||||
|
}
|
||||||
95
app/Enums/SubuserPermission.php
Normal file
95
app/Enums/SubuserPermission.php
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Enums;
|
||||||
|
|
||||||
|
use BackedEnum;
|
||||||
|
|
||||||
|
enum SubuserPermission: string
|
||||||
|
{
|
||||||
|
case WebsocketConnect = 'websocket.connect';
|
||||||
|
|
||||||
|
case ControlConsole = 'control.console';
|
||||||
|
case ControlStart = 'control.start';
|
||||||
|
case ControlStop = 'control.stop';
|
||||||
|
case ControlRestart = 'control.restart';
|
||||||
|
|
||||||
|
case FileRead = 'file.read';
|
||||||
|
case FileReadContent = 'file.read-content';
|
||||||
|
case FileCreate = 'file.create';
|
||||||
|
case FileUpdate = 'file.update';
|
||||||
|
case FileDelete = 'file.delete';
|
||||||
|
case FileArchive = 'file.archive';
|
||||||
|
case FileSftp = 'file.sftp';
|
||||||
|
|
||||||
|
case BackupRead = 'backup.read';
|
||||||
|
case BackupCreate = 'backup.create';
|
||||||
|
case BackupDelete = 'backup.delete';
|
||||||
|
case BackupDownload = 'backup.download';
|
||||||
|
case BackupRestore = 'backup.restore';
|
||||||
|
|
||||||
|
case ScheduleRead = 'schedule.read';
|
||||||
|
case ScheduleCreate = 'schedule.create';
|
||||||
|
case ScheduleUpdate = 'schedule.update';
|
||||||
|
case ScheduleDelete = 'schedule.delete';
|
||||||
|
|
||||||
|
case UserRead = 'user.read';
|
||||||
|
case UserCreate = 'user.create';
|
||||||
|
case UserUpdate = 'user.update';
|
||||||
|
case UserDelete = 'user.delete';
|
||||||
|
|
||||||
|
case DatabaseRead = 'database.read';
|
||||||
|
case DatabaseCreate = 'database.create';
|
||||||
|
case DatabaseUpdate = 'database.update';
|
||||||
|
case DatabaseDelete = 'database.delete';
|
||||||
|
case DatabaseViewPassword = 'database.view-password';
|
||||||
|
|
||||||
|
case AllocationRead = 'allocation.read';
|
||||||
|
case AllocationCreate = 'allocation.create';
|
||||||
|
case AllocationUpdate = 'allocation.update';
|
||||||
|
case AllocationDelete = 'allocation.delete';
|
||||||
|
|
||||||
|
case ActivityRead = 'activity.read';
|
||||||
|
|
||||||
|
case MountRead = 'mount.read';
|
||||||
|
case MountUpdate = 'mount.update';
|
||||||
|
|
||||||
|
case StartupRead = 'startup.read';
|
||||||
|
case StartupUpdate = 'startup.update';
|
||||||
|
case StartupDockerImage = 'startup.docker-image';
|
||||||
|
|
||||||
|
case SettingsRename = 'settings.rename';
|
||||||
|
case SettingsDescription = 'settings.description';
|
||||||
|
case SettingsReinstall = 'settings.reinstall';
|
||||||
|
case SettingsChangeIcon = 'settings.change-icon';
|
||||||
|
|
||||||
|
/** @return string[] */
|
||||||
|
public function split(): array
|
||||||
|
{
|
||||||
|
return explode('.', $this->value, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isHidden(): bool
|
||||||
|
{
|
||||||
|
return $this === self::WebsocketConnect;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getIcon(): ?BackedEnum
|
||||||
|
{
|
||||||
|
[$group, $permission] = $this->split();
|
||||||
|
|
||||||
|
return match ($group) {
|
||||||
|
'control' => TablerIcon::Terminal2,
|
||||||
|
'user' => TablerIcon::Users,
|
||||||
|
'file' => TablerIcon::Files,
|
||||||
|
'backup' => TablerIcon::FileZip,
|
||||||
|
'allocation' => TablerIcon::Network,
|
||||||
|
'startup' => TablerIcon::PlayerPlay,
|
||||||
|
'database' => TablerIcon::Database,
|
||||||
|
'schedule' => TablerIcon::Clock,
|
||||||
|
'settings' => TablerIcon::Settings,
|
||||||
|
'activity' => TablerIcon::Stack,
|
||||||
|
'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;
|
namespace App\Enums;
|
||||||
|
|
||||||
|
use BackedEnum;
|
||||||
use Filament\Support\Contracts\HasColor;
|
use Filament\Support\Contracts\HasColor;
|
||||||
use Filament\Support\Contracts\HasIcon;
|
use Filament\Support\Contracts\HasIcon;
|
||||||
use Filament\Support\Contracts\HasLabel;
|
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) {
|
return match ($this) {
|
||||||
self::Regular => 'tabler-world-www',
|
self::Regular => TablerIcon::WorldWww,
|
||||||
self::Discord => 'tabler-brand-discord',
|
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',
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Extensions\Captcha\Schemas;
|
namespace App\Extensions\Captcha\Schemas;
|
||||||
|
|
||||||
|
use BackedEnum;
|
||||||
use Filament\Schemas\Components\Component;
|
use Filament\Schemas\Components\Component;
|
||||||
|
|
||||||
interface CaptchaSchemaInterface
|
interface CaptchaSchemaInterface
|
||||||
@@ -24,7 +25,7 @@ interface CaptchaSchemaInterface
|
|||||||
*/
|
*/
|
||||||
public function getSettingsForm(): array;
|
public function getSettingsForm(): array;
|
||||||
|
|
||||||
public function getIcon(): ?string;
|
public function getIcon(): null|string|BackedEnum;
|
||||||
|
|
||||||
public function validateResponse(?string $captchaResponse = null): void;
|
public function validateResponse(?string $captchaResponse = null): void;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,10 @@
|
|||||||
|
|
||||||
namespace App\Extensions\Captcha\Schemas\Turnstile;
|
namespace App\Extensions\Captcha\Schemas\Turnstile;
|
||||||
|
|
||||||
|
use App\Enums\TablerIcon;
|
||||||
use App\Extensions\Captcha\Schemas\BaseSchema;
|
use App\Extensions\Captcha\Schemas\BaseSchema;
|
||||||
use App\Extensions\Captcha\Schemas\CaptchaSchemaInterface;
|
use App\Extensions\Captcha\Schemas\CaptchaSchemaInterface;
|
||||||
|
use BackedEnum;
|
||||||
use Exception;
|
use Exception;
|
||||||
use Filament\Forms\Components\Toggle;
|
use Filament\Forms\Components\Toggle;
|
||||||
use Filament\Infolists\Components\TextEntry;
|
use Filament\Infolists\Components\TextEntry;
|
||||||
@@ -49,8 +51,8 @@ class TurnstileSchema extends BaseSchema implements CaptchaSchemaInterface
|
|||||||
->label(trans('admin/setting.captcha.verify'))
|
->label(trans('admin/setting.captcha.verify'))
|
||||||
->columnSpan(2)
|
->columnSpan(2)
|
||||||
->inline(false)
|
->inline(false)
|
||||||
->onIcon('tabler-check')
|
->onIcon(TablerIcon::Check)
|
||||||
->offIcon('tabler-x')
|
->offIcon(TablerIcon::X)
|
||||||
->onColor('success')
|
->onColor('success')
|
||||||
->offColor('danger')
|
->offColor('danger')
|
||||||
->default(env('CAPTCHA_TURNSTILE_VERIFY_DOMAIN', true)),
|
->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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -2,9 +2,10 @@
|
|||||||
|
|
||||||
namespace App\Extensions\Features\Schemas;
|
namespace App\Extensions\Features\Schemas;
|
||||||
|
|
||||||
|
use App\Enums\SubuserPermission;
|
||||||
|
use App\Enums\TablerIcon;
|
||||||
use App\Extensions\Features\FeatureSchemaInterface;
|
use App\Extensions\Features\FeatureSchemaInterface;
|
||||||
use App\Facades\Activity;
|
use App\Facades\Activity;
|
||||||
use App\Models\Permission;
|
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use App\Models\ServerVariable;
|
use App\Models\ServerVariable;
|
||||||
use App\Repositories\Daemon\DaemonServerRepository;
|
use App\Repositories\Daemon\DaemonServerRepository;
|
||||||
@@ -54,7 +55,7 @@ class GSLTokenSchema implements FeatureSchemaInterface
|
|||||||
->modalHeading('Invalid GSL token')
|
->modalHeading('Invalid GSL token')
|
||||||
->modalDescription('It seems like your Gameserver Login Token (GSL token) is invalid or has expired.')
|
->modalDescription('It seems like your Gameserver Login Token (GSL token) is invalid or has expired.')
|
||||||
->modalSubmitActionLabel('Update GSL Token')
|
->modalSubmitActionLabel('Update GSL Token')
|
||||||
->disabledSchema(fn () => !user()?->can(Permission::ACTION_STARTUP_UPDATE, $server))
|
->disabledSchema(fn () => !user()?->can(SubuserPermission::StartupUpdate, $server))
|
||||||
->schema([
|
->schema([
|
||||||
TextEntry::make('info')
|
TextEntry::make('info')
|
||||||
->label(new HtmlString(Blade::render('You can either <x-filament::link href="https://steamcommunity.com/dev/managegameservers" target="_blank">generate a new one</x-filament::link> and enter it below or leave the field blank to remove it completely.'))),
|
->label(new HtmlString(Blade::render('You can either <x-filament::link href="https://steamcommunity.com/dev/managegameservers" target="_blank">generate a new one</x-filament::link> and enter it below or leave the field blank to remove it completely.'))),
|
||||||
@@ -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)
|
->label(fn () => $serverVariable->variable->name)
|
||||||
->prefix(fn () => '{{' . $serverVariable->variable->env_variable . '}}')
|
->prefix(fn () => '{{' . $serverVariable->variable->env_variable . '}}')
|
||||||
->helperText(fn () => empty($serverVariable->variable->description) ? '—' : $serverVariable->variable->description),
|
->helperText(fn () => empty($serverVariable->variable->description) ? '—' : $serverVariable->variable->description),
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
namespace App\Extensions\Features\Schemas;
|
namespace App\Extensions\Features\Schemas;
|
||||||
|
|
||||||
|
use App\Enums\SubuserPermission;
|
||||||
use App\Extensions\Features\FeatureSchemaInterface;
|
use App\Extensions\Features\FeatureSchemaInterface;
|
||||||
use App\Facades\Activity;
|
use App\Facades\Activity;
|
||||||
use App\Models\Permission;
|
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use App\Repositories\Daemon\DaemonServerRepository;
|
use App\Repositories\Daemon\DaemonServerRepository;
|
||||||
use Exception;
|
use Exception;
|
||||||
@@ -44,7 +44,7 @@ class JavaVersionSchema implements FeatureSchemaInterface
|
|||||||
->modalHeading('Unsupported Java Version')
|
->modalHeading('Unsupported Java Version')
|
||||||
->modalDescription('This server is currently running an unsupported version of Java and cannot be started.')
|
->modalDescription('This server is currently running an unsupported version of Java and cannot be started.')
|
||||||
->modalSubmitActionLabel('Update Docker Image')
|
->modalSubmitActionLabel('Update Docker Image')
|
||||||
->disabledSchema(fn () => !user()?->can(Permission::ACTION_STARTUP_DOCKER_IMAGE, $server))
|
->disabledSchema(fn () => !user()?->can(SubuserPermission::StartupDockerImage, $server))
|
||||||
->schema([
|
->schema([
|
||||||
TextEntry::make('java')
|
TextEntry::make('java')
|
||||||
->label('Please select a supported version from the list below to continue starting the server.'),
|
->label('Please select a supported version from the list below to continue starting the server.'),
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Extensions\Features\Schemas;
|
namespace App\Extensions\Features\Schemas;
|
||||||
|
|
||||||
|
use App\Enums\TablerIcon;
|
||||||
use App\Extensions\Features\FeatureSchemaInterface;
|
use App\Extensions\Features\FeatureSchemaInterface;
|
||||||
use Filament\Actions\Action;
|
use Filament\Actions\Action;
|
||||||
use Illuminate\Support\Facades\Blade;
|
use Illuminate\Support\Facades\Blade;
|
||||||
@@ -31,7 +32,7 @@ class PIDLimitSchema implements FeatureSchemaInterface
|
|||||||
{
|
{
|
||||||
return Action::make($this->getId())
|
return Action::make($this->getId())
|
||||||
->requiresConfirmation()
|
->requiresConfirmation()
|
||||||
->icon('tabler-alert-triangle')
|
->icon(TablerIcon::AlertTriangle)
|
||||||
->modalHeading(fn () => user()?->isAdmin() ? 'Memory or process limit reached...' : 'Possible resource limit reached...')
|
->modalHeading(fn () => user()?->isAdmin() ? 'Memory or process limit reached...' : 'Possible resource limit reached...')
|
||||||
->modalDescription(new HtmlString(Blade::render(
|
->modalDescription(new HtmlString(Blade::render(
|
||||||
user()?->isAdmin() ? <<<'HTML'
|
user()?->isAdmin() ? <<<'HTML'
|
||||||
|
|||||||
@@ -2,8 +2,11 @@
|
|||||||
|
|
||||||
namespace App\Extensions\OAuth;
|
namespace App\Extensions\OAuth;
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
use BackedEnum;
|
||||||
use Filament\Schemas\Components\Component;
|
use Filament\Schemas\Components\Component;
|
||||||
use Filament\Schemas\Components\Wizard\Step;
|
use Filament\Schemas\Components\Wizard\Step;
|
||||||
|
use Laravel\Socialite\Contracts\User as OAuthUser;
|
||||||
|
|
||||||
interface OAuthSchemaInterface
|
interface OAuthSchemaInterface
|
||||||
{
|
{
|
||||||
@@ -27,13 +30,13 @@ interface OAuthSchemaInterface
|
|||||||
/** @return Step[] */
|
/** @return Step[] */
|
||||||
public function getSetupSteps(): array;
|
public function getSetupSteps(): array;
|
||||||
|
|
||||||
public function getIcon(): ?string;
|
public function getIcon(): null|string|BackedEnum;
|
||||||
|
|
||||||
public function getHexColor(): ?string;
|
public function getHexColor(): ?string;
|
||||||
|
|
||||||
public function isEnabled(): bool;
|
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;
|
namespace App\Extensions\OAuth\Schemas;
|
||||||
|
|
||||||
|
use App\Enums\TablerIcon;
|
||||||
|
use BackedEnum;
|
||||||
use Filament\Forms\Components\TextInput;
|
use Filament\Forms\Components\TextInput;
|
||||||
use Filament\Infolists\Components\TextEntry;
|
use Filament\Infolists\Components\TextEntry;
|
||||||
use Filament\Schemas\Components\Wizard\Step;
|
use Filament\Schemas\Components\Wizard\Step;
|
||||||
@@ -33,9 +35,9 @@ final class BitbucketSchema extends OAuthSchema
|
|||||||
], parent::getSetupSteps());
|
], parent::getSetupSteps());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getIcon(): string
|
public function getIcon(): BackedEnum
|
||||||
{
|
{
|
||||||
return 'tabler-brand-bitbucket-f';
|
return TablerIcon::BrandBitbucketFilled;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getHexColor(): string
|
public function getHexColor(): string
|
||||||
|
|||||||
@@ -2,13 +2,15 @@
|
|||||||
|
|
||||||
namespace App\Extensions\OAuth\Schemas;
|
namespace App\Extensions\OAuth\Schemas;
|
||||||
|
|
||||||
|
use BackedEnum;
|
||||||
|
|
||||||
final class CommonSchema extends OAuthSchema
|
final class CommonSchema extends OAuthSchema
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly string $id,
|
private readonly string $id,
|
||||||
private readonly ?string $name = null,
|
private readonly ?string $name = null,
|
||||||
private readonly ?string $configName = null,
|
private readonly ?string $configName = null,
|
||||||
private readonly ?string $icon = null,
|
private readonly null|string|BackedEnum $icon = null,
|
||||||
private readonly ?string $hexColor = null,
|
private readonly ?string $hexColor = null,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@@ -27,7 +29,7 @@ final class CommonSchema extends OAuthSchema
|
|||||||
return $this->configName ?? parent::getConfigKey();
|
return $this->configName ?? parent::getConfigKey();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getIcon(): ?string
|
public function getIcon(): null|string|BackedEnum
|
||||||
{
|
{
|
||||||
return $this->icon;
|
return $this->icon;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
namespace App\Extensions\OAuth\Schemas;
|
namespace App\Extensions\OAuth\Schemas;
|
||||||
|
|
||||||
|
use App\Enums\TablerIcon;
|
||||||
|
use BackedEnum;
|
||||||
use Filament\Forms\Components\TextInput;
|
use Filament\Forms\Components\TextInput;
|
||||||
use Filament\Infolists\Components\TextEntry;
|
use Filament\Infolists\Components\TextEntry;
|
||||||
use Filament\Schemas\Components\Wizard\Step;
|
use Filament\Schemas\Components\Wizard\Step;
|
||||||
@@ -42,9 +44,9 @@ final class DiscordSchema extends OAuthSchema
|
|||||||
], parent::getSetupSteps());
|
], parent::getSetupSteps());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getIcon(): string
|
public function getIcon(): BackedEnum
|
||||||
{
|
{
|
||||||
return 'tabler-brand-discord-f';
|
return TablerIcon::BrandDiscordFilled;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getHexColor(): string
|
public function getHexColor(): string
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
namespace App\Extensions\OAuth\Schemas;
|
namespace App\Extensions\OAuth\Schemas;
|
||||||
|
|
||||||
|
use App\Enums\TablerIcon;
|
||||||
|
use BackedEnum;
|
||||||
use Filament\Forms\Components\TextInput;
|
use Filament\Forms\Components\TextInput;
|
||||||
use Filament\Infolists\Components\TextEntry;
|
use Filament\Infolists\Components\TextEntry;
|
||||||
use Filament\Schemas\Components\Wizard\Step;
|
use Filament\Schemas\Components\Wizard\Step;
|
||||||
@@ -36,9 +38,9 @@ final class FacebookSchema extends OAuthSchema
|
|||||||
], parent::getSetupSteps());
|
], parent::getSetupSteps());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getIcon(): string
|
public function getIcon(): BackedEnum
|
||||||
{
|
{
|
||||||
return 'tabler-brand-facebook-f';
|
return TablerIcon::BrandFacebookFilled;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getHexColor(): string
|
public function getHexColor(): string
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
namespace App\Extensions\OAuth\Schemas;
|
namespace App\Extensions\OAuth\Schemas;
|
||||||
|
|
||||||
|
use App\Enums\TablerIcon;
|
||||||
|
use BackedEnum;
|
||||||
use Filament\Forms\Components\TextInput;
|
use Filament\Forms\Components\TextInput;
|
||||||
use Filament\Infolists\Components\TextEntry;
|
use Filament\Infolists\Components\TextEntry;
|
||||||
use Filament\Schemas\Components\Wizard\Step;
|
use Filament\Schemas\Components\Wizard\Step;
|
||||||
@@ -18,11 +20,11 @@ final class GithubSchema extends OAuthSchema
|
|||||||
public function getSetupSteps(): array
|
public function getSetupSteps(): array
|
||||||
{
|
{
|
||||||
return array_merge([
|
return array_merge([
|
||||||
Step::make('Register new Github OAuth App')
|
Step::make('Register new GitHub OAuth App')
|
||||||
->schema([
|
->schema([
|
||||||
TextEntry::make('create_application')
|
TextEntry::make('create_application')
|
||||||
->hiddenLabel()
|
->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')
|
TextInput::make('_noenv_callback')
|
||||||
->label('Authorization callback URL')
|
->label('Authorization callback URL')
|
||||||
->dehydrated()
|
->dehydrated()
|
||||||
@@ -42,9 +44,9 @@ final class GithubSchema extends OAuthSchema
|
|||||||
], parent::getSetupSteps());
|
], parent::getSetupSteps());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getIcon(): string
|
public function getIcon(): BackedEnum
|
||||||
{
|
{
|
||||||
return 'tabler-brand-github-f';
|
return TablerIcon::BrandGithubFilled;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getHexColor(): string
|
public function getHexColor(): string
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
namespace App\Extensions\OAuth\Schemas;
|
namespace App\Extensions\OAuth\Schemas;
|
||||||
|
|
||||||
|
use App\Enums\TablerIcon;
|
||||||
|
use BackedEnum;
|
||||||
use Filament\Forms\Components\TextInput;
|
use Filament\Forms\Components\TextInput;
|
||||||
use Filament\Infolists\Components\TextEntry;
|
use Filament\Infolists\Components\TextEntry;
|
||||||
use Filament\Schemas\Components\Wizard\Step;
|
use Filament\Schemas\Components\Wizard\Step;
|
||||||
@@ -53,9 +55,9 @@ final class GitlabSchema extends OAuthSchema
|
|||||||
], parent::getSetupSteps());
|
], parent::getSetupSteps());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getIcon(): string
|
public function getIcon(): BackedEnum
|
||||||
{
|
{
|
||||||
return 'tabler-brand-gitlab';
|
return TablerIcon::BrandGitlab;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getHexColor(): string
|
public function getHexColor(): string
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
namespace App\Extensions\OAuth\Schemas;
|
namespace App\Extensions\OAuth\Schemas;
|
||||||
|
|
||||||
|
use App\Enums\TablerIcon;
|
||||||
|
use BackedEnum;
|
||||||
use Filament\Forms\Components\TextInput;
|
use Filament\Forms\Components\TextInput;
|
||||||
use Filament\Infolists\Components\TextEntry;
|
use Filament\Infolists\Components\TextEntry;
|
||||||
use Filament\Schemas\Components\Wizard\Step;
|
use Filament\Schemas\Components\Wizard\Step;
|
||||||
@@ -42,9 +44,9 @@ final class GoogleSchema extends OAuthSchema
|
|||||||
], parent::getSetupSteps());
|
], parent::getSetupSteps());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getIcon(): string
|
public function getIcon(): BackedEnum
|
||||||
{
|
{
|
||||||
return 'tabler-brand-google-f';
|
return TablerIcon::BrandGoogleFilled;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getHexColor(): string
|
public function getHexColor(): string
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
namespace App\Extensions\OAuth\Schemas;
|
namespace App\Extensions\OAuth\Schemas;
|
||||||
|
|
||||||
|
use App\Enums\TablerIcon;
|
||||||
|
use BackedEnum;
|
||||||
use Filament\Forms\Components\TextInput;
|
use Filament\Forms\Components\TextInput;
|
||||||
use Filament\Infolists\Components\TextEntry;
|
use Filament\Infolists\Components\TextEntry;
|
||||||
use Filament\Schemas\Components\Wizard\Step;
|
use Filament\Schemas\Components\Wizard\Step;
|
||||||
@@ -33,9 +35,9 @@ final class LinkedinSchema extends OAuthSchema
|
|||||||
], parent::getSetupSteps());
|
], parent::getSetupSteps());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getIcon(): string
|
public function getIcon(): BackedEnum
|
||||||
{
|
{
|
||||||
return 'tabler-brand-linkedin-f';
|
return TablerIcon::BrandLinkedinFilled;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getHexColor(): string
|
public function getHexColor(): string
|
||||||
|
|||||||
@@ -2,13 +2,17 @@
|
|||||||
|
|
||||||
namespace App\Extensions\OAuth\Schemas;
|
namespace App\Extensions\OAuth\Schemas;
|
||||||
|
|
||||||
|
use App\Enums\TablerIcon;
|
||||||
use App\Extensions\OAuth\OAuthSchemaInterface;
|
use App\Extensions\OAuth\OAuthSchemaInterface;
|
||||||
|
use App\Models\User;
|
||||||
|
use BackedEnum;
|
||||||
use Filament\Forms\Components\TextInput;
|
use Filament\Forms\Components\TextInput;
|
||||||
use Filament\Forms\Components\Toggle;
|
use Filament\Forms\Components\Toggle;
|
||||||
use Filament\Schemas\Components\Component;
|
use Filament\Schemas\Components\Component;
|
||||||
use Filament\Schemas\Components\Utilities\Set;
|
use Filament\Schemas\Components\Utilities\Set;
|
||||||
use Filament\Schemas\Components\Wizard\Step;
|
use Filament\Schemas\Components\Wizard\Step;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
|
use Laravel\Socialite\Contracts\User as OAuthUser;
|
||||||
|
|
||||||
abstract class OAuthSchema implements OAuthSchemaInterface
|
abstract class OAuthSchema implements OAuthSchemaInterface
|
||||||
{
|
{
|
||||||
@@ -59,8 +63,8 @@ abstract class OAuthSchema implements OAuthSchemaInterface
|
|||||||
->label(trans('admin/setting.oauth.create_missing_users'))
|
->label(trans('admin/setting.oauth.create_missing_users'))
|
||||||
->columnSpan(2)
|
->columnSpan(2)
|
||||||
->inline(false)
|
->inline(false)
|
||||||
->onIcon('tabler-check')
|
->onIcon(TablerIcon::Check)
|
||||||
->offIcon('tabler-x')
|
->offIcon(TablerIcon::X)
|
||||||
->onColor('success')
|
->onColor('success')
|
||||||
->offColor('danger')
|
->offColor('danger')
|
||||||
->formatStateUsing(fn ($state) => (bool) $state)
|
->formatStateUsing(fn ($state) => (bool) $state)
|
||||||
@@ -70,8 +74,8 @@ abstract class OAuthSchema implements OAuthSchemaInterface
|
|||||||
->label(trans('admin/setting.oauth.link_missing_users'))
|
->label(trans('admin/setting.oauth.link_missing_users'))
|
||||||
->columnSpan(2)
|
->columnSpan(2)
|
||||||
->inline(false)
|
->inline(false)
|
||||||
->onIcon('tabler-check')
|
->onIcon(TablerIcon::Check)
|
||||||
->offIcon('tabler-x')
|
->offIcon(TablerIcon::X)
|
||||||
->onColor('success')
|
->onColor('success')
|
||||||
->offColor('danger')
|
->offColor('danger')
|
||||||
->formatStateUsing(fn ($state) => (bool) $state)
|
->formatStateUsing(fn ($state) => (bool) $state)
|
||||||
@@ -104,7 +108,7 @@ abstract class OAuthSchema implements OAuthSchemaInterface
|
|||||||
return "OAUTH_{$id}_ENABLED";
|
return "OAUTH_{$id}_ENABLED";
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getIcon(): ?string
|
public function getIcon(): null|string|BackedEnum
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -116,19 +120,17 @@ abstract class OAuthSchema implements OAuthSchemaInterface
|
|||||||
|
|
||||||
public function isEnabled(): bool
|
public function isEnabled(): bool
|
||||||
{
|
{
|
||||||
$id = Str::upper($this->getId());
|
return env($this->getConfigKey(), false);
|
||||||
|
|
||||||
return env("OAUTH_{$id}_ENABLED", false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function shouldCreateMissingUsers(): bool
|
public function shouldCreateMissingUser(OAuthUser $user): bool
|
||||||
{
|
{
|
||||||
$id = Str::upper($this->getId());
|
$id = Str::upper($this->getId());
|
||||||
|
|
||||||
return env("OAUTH_{$id}_SHOULD_CREATE_MISSING_USERS", false);
|
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());
|
$id = Str::upper($this->getId());
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
namespace App\Extensions\OAuth\Schemas;
|
namespace App\Extensions\OAuth\Schemas;
|
||||||
|
|
||||||
|
use App\Enums\TablerIcon;
|
||||||
|
use BackedEnum;
|
||||||
use Filament\Forms\Components\TextInput;
|
use Filament\Forms\Components\TextInput;
|
||||||
use Filament\Infolists\Components\TextEntry;
|
use Filament\Infolists\Components\TextEntry;
|
||||||
use Filament\Schemas\Components\Wizard\Step;
|
use Filament\Schemas\Components\Wizard\Step;
|
||||||
@@ -33,9 +35,9 @@ final class SlackSchema extends OAuthSchema
|
|||||||
], parent::getSetupSteps());
|
], parent::getSetupSteps());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getIcon(): string
|
public function getIcon(): BackedEnum
|
||||||
{
|
{
|
||||||
return 'tabler-brand-slack';
|
return TablerIcon::BrandSlack;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getHexColor(): string
|
public function getHexColor(): string
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
namespace App\Extensions\OAuth\Schemas;
|
namespace App\Extensions\OAuth\Schemas;
|
||||||
|
|
||||||
|
use App\Enums\TablerIcon;
|
||||||
|
use BackedEnum;
|
||||||
use Filament\Forms\Components\TextInput;
|
use Filament\Forms\Components\TextInput;
|
||||||
use Filament\Infolists\Components\TextEntry;
|
use Filament\Infolists\Components\TextEntry;
|
||||||
use Filament\Schemas\Components\Wizard\Step;
|
use Filament\Schemas\Components\Wizard\Step;
|
||||||
@@ -59,9 +61,9 @@ final class SteamSchema extends OAuthSchema
|
|||||||
], parent::getSetupSteps());
|
], parent::getSetupSteps());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getIcon(): string
|
public function getIcon(): BackedEnum
|
||||||
{
|
{
|
||||||
return 'tabler-brand-steam-f';
|
return TablerIcon::BrandSteamFilled;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getHexColor(): string
|
public function getHexColor(): string
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
namespace App\Extensions\OAuth\Schemas;
|
namespace App\Extensions\OAuth\Schemas;
|
||||||
|
|
||||||
|
use App\Enums\TablerIcon;
|
||||||
|
use BackedEnum;
|
||||||
use Filament\Forms\Components\TextInput;
|
use Filament\Forms\Components\TextInput;
|
||||||
use Filament\Infolists\Components\TextEntry;
|
use Filament\Infolists\Components\TextEntry;
|
||||||
use Filament\Schemas\Components\Wizard\Step;
|
use Filament\Schemas\Components\Wizard\Step;
|
||||||
@@ -42,9 +44,9 @@ final class XSchema extends OAuthSchema
|
|||||||
], parent::getSetupSteps());
|
], parent::getSetupSteps());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getIcon(): string
|
public function getIcon(): BackedEnum
|
||||||
{
|
{
|
||||||
return 'tabler-brand-x';
|
return TablerIcon::BrandX;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getHexColor(): string
|
public function getHexColor(): string
|
||||||
|
|||||||
@@ -2,12 +2,14 @@
|
|||||||
|
|
||||||
namespace App\Filament\Admin\Pages;
|
namespace App\Filament\Admin\Pages;
|
||||||
|
|
||||||
|
use App\Enums\TablerIcon;
|
||||||
use App\Services\Helpers\SoftwareVersionService;
|
use App\Services\Helpers\SoftwareVersionService;
|
||||||
|
use BackedEnum;
|
||||||
use Filament\Pages\Dashboard as BaseDashboard;
|
use Filament\Pages\Dashboard as BaseDashboard;
|
||||||
|
|
||||||
class Dashboard extends 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;
|
private SoftwareVersionService $softwareVersionService;
|
||||||
|
|
||||||
|
|||||||
@@ -2,11 +2,12 @@
|
|||||||
|
|
||||||
namespace App\Filament\Admin\Pages;
|
namespace App\Filament\Admin\Pages;
|
||||||
|
|
||||||
|
use App\Enums\TablerIcon;
|
||||||
|
use BackedEnum;
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
use Filament\Actions\Action;
|
use Filament\Actions\Action;
|
||||||
use Filament\Notifications\Notification;
|
use Filament\Notifications\Notification;
|
||||||
use Filament\Pages\Page;
|
use Filament\Pages\Page;
|
||||||
use Filament\Support\Enums\IconSize;
|
|
||||||
use Illuminate\Support\Facades\Artisan;
|
use Illuminate\Support\Facades\Artisan;
|
||||||
use Spatie\Health\Commands\RunHealthChecksCommand;
|
use Spatie\Health\Commands\RunHealthChecksCommand;
|
||||||
use Spatie\Health\Enums\Status;
|
use Spatie\Health\Enums\Status;
|
||||||
@@ -14,7 +15,7 @@ use Spatie\Health\ResultStores\ResultStore;
|
|||||||
|
|
||||||
class Health extends Page
|
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';
|
protected string $view = 'filament.pages.health';
|
||||||
|
|
||||||
@@ -47,9 +48,9 @@ class Health extends Page
|
|||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
Action::make('refresh')
|
Action::make('refresh')
|
||||||
->label(trans('admin/health.refresh'))
|
->hiddenLabel()
|
||||||
->iconButton()->iconSize(IconSize::ExtraLarge)
|
->tooltip(trans('admin/health.refresh'))
|
||||||
->icon('tabler-refresh')
|
->icon(TablerIcon::Refresh)
|
||||||
->action('refresh'),
|
->action('refresh'),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@@ -128,16 +129,16 @@ class Health extends Page
|
|||||||
return trans('admin/health.checks.failed', ['checks' => implode(', ', $failedNames)]);
|
return trans('admin/health.checks.failed', ['checks' => implode(', ', $failedNames)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function getNavigationIcon(): string
|
public static function getNavigationIcon(): BackedEnum
|
||||||
{
|
{
|
||||||
// @phpstan-ignore myCustomRules.forbiddenGlobalFunctions
|
// @phpstan-ignore myCustomRules.forbiddenGlobalFunctions
|
||||||
$results = app(ResultStore::class)->latestResults();
|
$results = app(ResultStore::class)->latestResults();
|
||||||
|
|
||||||
if ($results === null) {
|
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
|
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) {
|
return match ($str) {
|
||||||
Status::ok()->value => 'tabler-circle-check',
|
Status::ok()->value => TablerIcon::CircleCheck,
|
||||||
Status::warning()->value => 'tabler-exclamation-circle',
|
Status::warning()->value => TablerIcon::ExclamationCircle,
|
||||||
Status::skipped()->value => 'tabler-circle-chevron-right',
|
Status::skipped()->value => TablerIcon::CircleChevronRight,
|
||||||
Status::failed()->value, Status::crashed()->value => 'tabler-circle-x',
|
Status::failed()->value, Status::crashed()->value => TablerIcon::CircleX,
|
||||||
default => 'tabler-help-circle'
|
default => TablerIcon::HelpCircle
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,24 +2,26 @@
|
|||||||
|
|
||||||
namespace App\Filament\Admin\Pages;
|
namespace App\Filament\Admin\Pages;
|
||||||
|
|
||||||
|
use App\Enums\TablerIcon;
|
||||||
use Boquizo\FilamentLogViewer\Actions\DeleteAction;
|
use Boquizo\FilamentLogViewer\Actions\DeleteAction;
|
||||||
use Boquizo\FilamentLogViewer\Actions\DownloadAction;
|
use Boquizo\FilamentLogViewer\Actions\DownloadAction;
|
||||||
use Boquizo\FilamentLogViewer\Actions\ViewLogAction;
|
use Boquizo\FilamentLogViewer\Actions\ViewLogAction;
|
||||||
use Boquizo\FilamentLogViewer\Pages\ListLogs as BaseListLogs;
|
use Boquizo\FilamentLogViewer\Pages\ListLogs as BaseListLogs;
|
||||||
use Boquizo\FilamentLogViewer\Tables\Columns\LevelColumn;
|
use Boquizo\FilamentLogViewer\Tables\Columns\LevelColumn;
|
||||||
use Boquizo\FilamentLogViewer\Tables\Columns\NameColumn;
|
use Boquizo\FilamentLogViewer\Tables\Columns\NameColumn;
|
||||||
|
use Boquizo\FilamentLogViewer\UseCases\ParseDateUseCase;
|
||||||
use Boquizo\FilamentLogViewer\Utils\Level;
|
use Boquizo\FilamentLogViewer\Utils\Level;
|
||||||
use Filament\Actions\Action;
|
use Filament\Actions\Action;
|
||||||
use Filament\Notifications\Notification;
|
use Filament\Notifications\Notification;
|
||||||
use Filament\Support\Enums\IconSize;
|
|
||||||
use Filament\Tables\Table;
|
use Filament\Tables\Table;
|
||||||
|
use Illuminate\Contracts\Support\Htmlable;
|
||||||
use Illuminate\Support\Facades\Http;
|
use Illuminate\Support\Facades\Http;
|
||||||
|
|
||||||
class ListLogs extends BaseListLogs
|
class ListLogs extends BaseListLogs
|
||||||
{
|
{
|
||||||
protected string $view = 'filament.components.list-logs';
|
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');
|
return trans('admin/log.navigation.panel_logs');
|
||||||
}
|
}
|
||||||
@@ -28,7 +30,7 @@ class ListLogs extends BaseListLogs
|
|||||||
{
|
{
|
||||||
return parent::table($table)
|
return parent::table($table)
|
||||||
->emptyStateHeading(trans('admin/log.empty_table'))
|
->emptyStateHeading(trans('admin/log.empty_table'))
|
||||||
->emptyStateIcon('tabler-check')
|
->emptyStateIcon(TablerIcon::Check)
|
||||||
->columns([
|
->columns([
|
||||||
NameColumn::make('date'),
|
NameColumn::make('date'),
|
||||||
LevelColumn::make(Level::ALL)
|
LevelColumn::make(Level::ALL)
|
||||||
@@ -46,17 +48,21 @@ class ListLogs extends BaseListLogs
|
|||||||
])
|
])
|
||||||
->recordActions([
|
->recordActions([
|
||||||
ViewLogAction::make()
|
ViewLogAction::make()
|
||||||
->icon('tabler-file-description')->iconSize(IconSize::Large)->iconButton(),
|
->icon(TablerIcon::FileDescription)->iconButton(),
|
||||||
DownloadAction::make()
|
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')
|
Action::make('uploadLogs')
|
||||||
->hiddenLabel()
|
->hiddenLabel()
|
||||||
->icon('tabler-world-upload')->iconSize(IconSize::Large)->iconButton()
|
->tooltip(trans('admin/log.actions.upload_tooltip', ['url' => 'logs.pelican.dev']))
|
||||||
|
->icon(TablerIcon::WorldUpload)
|
||||||
->requiresConfirmation()
|
->requiresConfirmation()
|
||||||
->modalHeading(trans('admin/log.actions.upload_logs'))
|
->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']))
|
->modalDescription(fn ($record) => trans('admin/log.actions.upload_logs_description', ['file' => $record['date'], 'url' => 'https://logs.pelican.dev']))
|
||||||
->action(function ($record) {
|
->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)) {
|
if (!file_exists($logPath)) {
|
||||||
Notification::make()
|
Notification::make()
|
||||||
@@ -73,18 +79,12 @@ class ListLogs extends BaseListLogs
|
|||||||
$uploadLines = $totalLines <= 1000 ? $lines : array_slice($lines, -1000);
|
$uploadLines = $totalLines <= 1000 ? $lines : array_slice($lines, -1000);
|
||||||
$content = implode("\n", $uploadLines);
|
$content = implode("\n", $uploadLines);
|
||||||
|
|
||||||
$logUrl = 'https://logs.pelican.dev';
|
|
||||||
try {
|
try {
|
||||||
$response = Http::timeout(10)->asMultipart()->post($logUrl, [
|
$response = Http::timeout(10)
|
||||||
[
|
->asMultipart()
|
||||||
'name' => 'c',
|
->attach('c', $content)
|
||||||
'contents' => $content,
|
->attach('e', '14d')
|
||||||
],
|
->post('https://logs.pelican.dev');
|
||||||
[
|
|
||||||
'name' => 'e',
|
|
||||||
'contents' => '14d',
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
|
|
||||||
if ($response->failed()) {
|
if ($response->failed()) {
|
||||||
Notification::make()
|
Notification::make()
|
||||||
@@ -104,7 +104,7 @@ class ListLogs extends BaseListLogs
|
|||||||
->body("{$url}")
|
->body("{$url}")
|
||||||
->success()
|
->success()
|
||||||
->actions([
|
->actions([
|
||||||
Action::make('viewLogs')
|
Action::make('exclude_viewLogs')
|
||||||
->label(trans('admin/log.actions.view_logs'))
|
->label(trans('admin/log.actions.view_logs'))
|
||||||
->url($url)
|
->url($url)
|
||||||
->openUrlInNewTab(true),
|
->openUrlInNewTab(true),
|
||||||
@@ -123,7 +123,7 @@ class ListLogs extends BaseListLogs
|
|||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
DeleteAction::make()
|
DeleteAction::make()
|
||||||
->icon('tabler-trash')->iconSize(IconSize::Medium)->iconButton(),
|
->icon(TablerIcon::Trash)->iconButton(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Filament\Admin\Pages;
|
namespace App\Filament\Admin\Pages;
|
||||||
|
|
||||||
|
use App\Enums\TablerIcon;
|
||||||
use App\Extensions\Avatar\AvatarService;
|
use App\Extensions\Avatar\AvatarService;
|
||||||
use App\Extensions\Captcha\CaptchaService;
|
use App\Extensions\Captcha\CaptchaService;
|
||||||
use App\Extensions\OAuth\OAuthService;
|
use App\Extensions\OAuth\OAuthService;
|
||||||
@@ -10,7 +11,10 @@ use App\Notifications\MailTested;
|
|||||||
use App\Traits\EnvironmentWriterTrait;
|
use App\Traits\EnvironmentWriterTrait;
|
||||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||||
|
use App\Traits\Filament\CanCustomizeTabs;
|
||||||
use BackedEnum;
|
use BackedEnum;
|
||||||
|
use BladeUI\Icons\Exceptions\SvgNotFound;
|
||||||
|
use BladeUI\Icons\Factory as IconFactory;
|
||||||
use Exception;
|
use Exception;
|
||||||
use Filament\Actions\Action;
|
use Filament\Actions\Action;
|
||||||
use Filament\Actions\ActionGroup;
|
use Filament\Actions\ActionGroup;
|
||||||
@@ -36,7 +40,6 @@ use Filament\Schemas\Components\Utilities\Get;
|
|||||||
use Filament\Schemas\Components\Utilities\Set;
|
use Filament\Schemas\Components\Utilities\Set;
|
||||||
use Filament\Schemas\Contracts\HasSchemas;
|
use Filament\Schemas\Contracts\HasSchemas;
|
||||||
use Filament\Schemas\Schema;
|
use Filament\Schemas\Schema;
|
||||||
use Filament\Support\Enums\IconSize;
|
|
||||||
use Filament\Support\Enums\Width;
|
use Filament\Support\Enums\Width;
|
||||||
use Illuminate\Http\Client\Factory;
|
use Illuminate\Http\Client\Factory;
|
||||||
use Illuminate\Support\Arr;
|
use Illuminate\Support\Arr;
|
||||||
@@ -53,10 +56,11 @@ class Settings extends Page implements HasSchemas
|
|||||||
CanCustomizeHeaderActions::getHeaderActions insteadof InteractsWithHeaderActions;
|
CanCustomizeHeaderActions::getHeaderActions insteadof InteractsWithHeaderActions;
|
||||||
}
|
}
|
||||||
use CanCustomizeHeaderWidgets;
|
use CanCustomizeHeaderWidgets;
|
||||||
|
use CanCustomizeTabs;
|
||||||
use EnvironmentWriterTrait;
|
use EnvironmentWriterTrait;
|
||||||
use InteractsWithForms;
|
use InteractsWithForms;
|
||||||
|
|
||||||
protected static string|\BackedEnum|null $navigationIcon = 'tabler-settings';
|
protected static string|BackedEnum|null $navigationIcon = TablerIcon::Settings;
|
||||||
|
|
||||||
protected string $view = 'filament.pages.settings';
|
protected string $view = 'filament.pages.settings';
|
||||||
|
|
||||||
@@ -66,6 +70,8 @@ class Settings extends Page implements HasSchemas
|
|||||||
|
|
||||||
protected CaptchaService $captchaService;
|
protected CaptchaService $captchaService;
|
||||||
|
|
||||||
|
protected IconFactory $iconFactory;
|
||||||
|
|
||||||
/** @var array<mixed>|null */
|
/** @var array<mixed>|null */
|
||||||
public ?array $data = [];
|
public ?array $data = [];
|
||||||
|
|
||||||
@@ -74,11 +80,12 @@ class Settings extends Page implements HasSchemas
|
|||||||
$this->form->fill();
|
$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->oauthService = $oauthService;
|
||||||
$this->avatarService = $avatarService;
|
$this->avatarService = $avatarService;
|
||||||
$this->captchaService = $captchaService;
|
$this->captchaService = $captchaService;
|
||||||
|
$this->iconFactory = $iconFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function canAccess(): bool
|
public static function canAccess(): bool
|
||||||
@@ -96,11 +103,7 @@ class Settings extends Page implements HasSchemas
|
|||||||
return trans('admin/setting.title');
|
return trans('admin/setting.title');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** @return array<Component> */
|
||||||
* @return array<Component>
|
|
||||||
*
|
|
||||||
* @throws Exception
|
|
||||||
*/
|
|
||||||
protected function getFormSchema(): array
|
protected function getFormSchema(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
@@ -108,34 +111,44 @@ class Settings extends Page implements HasSchemas
|
|||||||
->columns()
|
->columns()
|
||||||
->persistTabInQueryString()
|
->persistTabInQueryString()
|
||||||
->disabled(fn () => !user()?->can('update settings'))
|
->disabled(fn () => !user()?->can('update settings'))
|
||||||
->tabs([
|
->tabs($this->getTabs()),
|
||||||
Tab::make('general')
|
];
|
||||||
->label(trans('admin/setting.navigation.general'))
|
}
|
||||||
->icon('tabler-home')
|
|
||||||
->schema($this->generalSettings()),
|
/**
|
||||||
Tab::make('captcha')
|
* @return Tab[]
|
||||||
->label(trans('admin/setting.navigation.captcha'))
|
*
|
||||||
->icon('tabler-shield')
|
* @throws Exception
|
||||||
->schema($this->captchaSettings())
|
*/
|
||||||
->columns(1),
|
protected function getDefaultTabs(): array
|
||||||
Tab::make('mail')
|
{
|
||||||
->label(trans('admin/setting.navigation.mail'))
|
return [
|
||||||
->icon('tabler-mail')
|
Tab::make('general')
|
||||||
->schema($this->mailSettings()),
|
->label(trans('admin/setting.navigation.general'))
|
||||||
Tab::make('backup')
|
->icon(TablerIcon::Home)
|
||||||
->label(trans('admin/setting.navigation.backup'))
|
->schema($this->generalSettings()),
|
||||||
->icon('tabler-box')
|
Tab::make('captcha')
|
||||||
->schema($this->backupSettings()),
|
->label(trans('admin/setting.navigation.captcha'))
|
||||||
Tab::make('oauth')
|
->icon(TablerIcon::Shield)
|
||||||
->label(trans('admin/setting.navigation.oauth'))
|
->schema($this->captchaSettings())
|
||||||
->icon('tabler-brand-oauth')
|
->columns(1),
|
||||||
->schema($this->oauthSettings())
|
Tab::make('mail')
|
||||||
->columns(1),
|
->label(trans('admin/setting.navigation.mail'))
|
||||||
Tab::make('misc')
|
->icon(TablerIcon::Mail)
|
||||||
->label(trans('admin/setting.navigation.misc'))
|
->schema($this->mailSettings()),
|
||||||
->icon('tabler-tool')
|
Tab::make('backup')
|
||||||
->schema($this->miscSettings()),
|
->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 +167,12 @@ class Settings extends Page implements HasSchemas
|
|||||||
->schema([
|
->schema([
|
||||||
TextInput::make('APP_LOGO')
|
TextInput::make('APP_LOGO')
|
||||||
->label(trans('admin/setting.general.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'))
|
->default(env('APP_LOGO'))
|
||||||
->placeholder('/pelican.svg'),
|
->placeholder('/pelican.svg'),
|
||||||
TextInput::make('APP_FAVICON')
|
TextInput::make('APP_FAVICON')
|
||||||
->label(trans('admin/setting.general.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()
|
->required()
|
||||||
->default(env('APP_FAVICON', '/pelican.ico'))
|
->default(env('APP_FAVICON', '/pelican.ico'))
|
||||||
->placeholder('/pelican.ico'),
|
->placeholder('/pelican.ico'),
|
||||||
@@ -170,8 +183,8 @@ class Settings extends Page implements HasSchemas
|
|||||||
Toggle::make('APP_DEBUG')
|
Toggle::make('APP_DEBUG')
|
||||||
->label(trans('admin/setting.general.debug_mode'))
|
->label(trans('admin/setting.general.debug_mode'))
|
||||||
->inline(false)
|
->inline(false)
|
||||||
->onIcon('tabler-check')
|
->onIcon(TablerIcon::Check)
|
||||||
->offIcon('tabler-x')
|
->offIcon(TablerIcon::X)
|
||||||
->onColor('success')
|
->onColor('success')
|
||||||
->offColor('danger')
|
->offColor('danger')
|
||||||
->stateCast(new BooleanStateCast(false))
|
->stateCast(new BooleanStateCast(false))
|
||||||
@@ -188,8 +201,8 @@ class Settings extends Page implements HasSchemas
|
|||||||
Toggle::make('FILAMENT_UPLOADABLE_AVATARS')
|
Toggle::make('FILAMENT_UPLOADABLE_AVATARS')
|
||||||
->label(trans('admin/setting.general.uploadable_avatars'))
|
->label(trans('admin/setting.general.uploadable_avatars'))
|
||||||
->inline(false)
|
->inline(false)
|
||||||
->onIcon('tabler-check')
|
->onIcon(TablerIcon::Check)
|
||||||
->offIcon('tabler-x')
|
->offIcon(TablerIcon::X)
|
||||||
->onColor('success')
|
->onColor('success')
|
||||||
->offColor('danger')
|
->offColor('danger')
|
||||||
->stateCast(new BooleanStateCast(false))
|
->stateCast(new BooleanStateCast(false))
|
||||||
@@ -236,16 +249,16 @@ class Settings extends Page implements HasSchemas
|
|||||||
->placeholder(trans('admin/setting.general.trusted_proxies_help'))
|
->placeholder(trans('admin/setting.general.trusted_proxies_help'))
|
||||||
->default(env('TRUSTED_PROXIES', implode(',', Arr::wrap(config('trustedproxy.proxies')))))
|
->default(env('TRUSTED_PROXIES', implode(',', Arr::wrap(config('trustedproxy.proxies')))))
|
||||||
->hintActions([
|
->hintActions([
|
||||||
Action::make('clear')
|
Action::make('hint_clear')
|
||||||
->label(trans('admin/setting.general.clear'))
|
->label(trans('admin/setting.general.clear'))
|
||||||
->color('danger')
|
->color('danger')
|
||||||
->icon('tabler-trash')
|
->icon(TablerIcon::Trash)
|
||||||
->requiresConfirmation()
|
->requiresConfirmation()
|
||||||
->authorize(fn () => user()?->can('update settings'))
|
->authorize(fn () => user()?->can('update settings'))
|
||||||
->action(fn (Set $set) => $set('TRUSTED_PROXIES', [])),
|
->action(fn (Set $set) => $set('TRUSTED_PROXIES', [])),
|
||||||
Action::make('cloudflare')
|
Action::make('hint_cloudflare')
|
||||||
->label(trans('admin/setting.general.set_to_cf'))
|
->label(trans('admin/setting.general.set_to_cf'))
|
||||||
->icon('tabler-brand-cloudflare')
|
->icon(TablerIcon::BrandCloudflare)
|
||||||
->authorize(fn () => user()?->can('update settings'))
|
->authorize(fn () => user()?->can('update settings'))
|
||||||
->action(function (Factory $client, Set $set) {
|
->action(function (Factory $client, Set $set) {
|
||||||
$ips = collect();
|
$ips = collect();
|
||||||
@@ -256,7 +269,7 @@ class Settings extends Page implements HasSchemas
|
|||||||
->connectTimeout(3)
|
->connectTimeout(3)
|
||||||
->get('https://api.cloudflare.com/client/v4/ips');
|
->get('https://api.cloudflare.com/client/v4/ips');
|
||||||
|
|
||||||
if ($response->getStatusCode() === 200) {
|
if ($response->status() === 200) {
|
||||||
$result = $response->json('result');
|
$result = $response->json('result');
|
||||||
foreach (['ipv4_cidrs', 'ipv6_cidrs'] as $value) {
|
foreach (['ipv4_cidrs', 'ipv6_cidrs'] as $value) {
|
||||||
$ips->push(...data_get($result, $value));
|
$ips->push(...data_get($result, $value));
|
||||||
@@ -287,7 +300,7 @@ class Settings extends Page implements HasSchemas
|
|||||||
|
|
||||||
$formFields[] = Section::make($schema->getName())
|
$formFields[] = Section::make($schema->getName())
|
||||||
->columns(5)
|
->columns(5)
|
||||||
->icon($schema->getIcon() ?? 'tabler-shield')
|
->icon($schema->getIcon() ?? TablerIcon::Shield)
|
||||||
->collapsed(fn () => !$schema->isEnabled())
|
->collapsed(fn () => !$schema->isEnabled())
|
||||||
->collapsible()
|
->collapsible()
|
||||||
->schema([
|
->schema([
|
||||||
@@ -341,9 +354,9 @@ class Settings extends Page implements HasSchemas
|
|||||||
->live()
|
->live()
|
||||||
->default(env('MAIL_MAILER', config('mail.default')))
|
->default(env('MAIL_MAILER', config('mail.default')))
|
||||||
->hintAction(
|
->hintAction(
|
||||||
Action::make('test')
|
Action::make('hint_test')
|
||||||
->label(trans('admin/setting.mail.test_mail'))
|
->label(trans('admin/setting.mail.test_mail'))
|
||||||
->icon('tabler-send')
|
->icon(TablerIcon::Send)
|
||||||
->hidden(fn (Get $get) => $get('MAIL_MAILER') === 'log')
|
->hidden(fn (Get $get) => $get('MAIL_MAILER') === 'log')
|
||||||
->authorize(fn () => user()?->can('update settings'))
|
->authorize(fn () => user()?->can('update settings'))
|
||||||
->action(function (Get $get) {
|
->action(function (Get $get) {
|
||||||
@@ -533,8 +546,8 @@ class Settings extends Page implements HasSchemas
|
|||||||
Toggle::make('AWS_USE_PATH_STYLE_ENDPOINT')
|
Toggle::make('AWS_USE_PATH_STYLE_ENDPOINT')
|
||||||
->label(trans('admin/setting.backup.s3.use_path_style_endpoint'))
|
->label(trans('admin/setting.backup.s3.use_path_style_endpoint'))
|
||||||
->inline(false)
|
->inline(false)
|
||||||
->onIcon('tabler-check')
|
->onIcon(TablerIcon::Check)
|
||||||
->offIcon('tabler-x')
|
->offIcon(TablerIcon::X)
|
||||||
->onColor('success')
|
->onColor('success')
|
||||||
->offColor('danger')
|
->offColor('danger')
|
||||||
->live()
|
->live()
|
||||||
@@ -555,26 +568,34 @@ class Settings extends Page implements HasSchemas
|
|||||||
|
|
||||||
$oauthSchemas = $this->oauthService->getAll();
|
$oauthSchemas = $this->oauthService->getAll();
|
||||||
foreach ($oauthSchemas as $schema) {
|
foreach ($oauthSchemas as $schema) {
|
||||||
$id = Str::upper($schema->getId());
|
|
||||||
$key = $schema->getConfigKey();
|
$key = $schema->getConfigKey();
|
||||||
|
|
||||||
|
$icon = $schema->getIcon();
|
||||||
|
if (is_string($icon)) {
|
||||||
|
try {
|
||||||
|
$this->iconFactory->svg($icon);
|
||||||
|
} catch (SvgNotFound) {
|
||||||
|
$icon = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$formFields[] = Section::make($schema->getName())
|
$formFields[] = Section::make($schema->getName())
|
||||||
->columns(5)
|
->columns(5)
|
||||||
->icon($schema->getIcon() ?? 'tabler-brand-oauth')
|
->icon($icon ?? TablerIcon::BrandOauth)
|
||||||
->collapsed(fn () => !env($key, false))
|
->collapsed(fn () => !$schema->isEnabled())
|
||||||
->collapsible()
|
->collapsible()
|
||||||
->schema([
|
->schema([
|
||||||
Hidden::make($key)
|
Hidden::make($key)
|
||||||
->live()
|
->live()
|
||||||
->default(env($key)),
|
->default($schema->isEnabled()),
|
||||||
Actions::make([
|
Actions::make([
|
||||||
Action::make("disable_oauth_$id")
|
Action::make('disable_oauth_' . $schema->getId())
|
||||||
->visible(fn (Get $get) => $get($key))
|
->visible(fn (Get $get) => $get($key))
|
||||||
->disabled(fn () => !user()?->can('update settings'))
|
->disabled(fn () => !user()?->can('update settings'))
|
||||||
->label(trans('admin/setting.oauth.disable'))
|
->label(trans('admin/setting.oauth.disable'))
|
||||||
->color('danger')
|
->color('danger')
|
||||||
->action(fn (Set $set) => $set($key, false)),
|
->action(fn (Set $set) => $set($key, false)),
|
||||||
Action::make("enable_oauth_$id")
|
Action::make('enable_oauth_' . $schema->getId())
|
||||||
->visible(fn (Get $get) => !$get($key))
|
->visible(fn (Get $get) => !$get($key))
|
||||||
->disabled(fn () => !user()?->can('update settings'))
|
->disabled(fn () => !user()?->can('update settings'))
|
||||||
->label(trans('admin/setting.oauth.enable'))
|
->label(trans('admin/setting.oauth.enable'))
|
||||||
@@ -619,8 +640,8 @@ class Settings extends Page implements HasSchemas
|
|||||||
->schema([
|
->schema([
|
||||||
Toggle::make('PANEL_CLIENT_ALLOCATIONS_ENABLED')
|
Toggle::make('PANEL_CLIENT_ALLOCATIONS_ENABLED')
|
||||||
->label(trans('admin/setting.misc.auto_allocation.question'))
|
->label(trans('admin/setting.misc.auto_allocation.question'))
|
||||||
->onIcon('tabler-check')
|
->onIcon(TablerIcon::Check)
|
||||||
->offIcon('tabler-x')
|
->offIcon(TablerIcon::X)
|
||||||
->onColor('success')
|
->onColor('success')
|
||||||
->offColor('danger')
|
->offColor('danger')
|
||||||
->live()
|
->live()
|
||||||
@@ -630,8 +651,8 @@ class Settings extends Page implements HasSchemas
|
|||||||
Toggle::make('PANEL_CLIENT_ALLOCATIONS_CREATE_NEW')
|
Toggle::make('PANEL_CLIENT_ALLOCATIONS_CREATE_NEW')
|
||||||
->label(trans('admin/setting.misc.auto_allocation.create_new'))
|
->label(trans('admin/setting.misc.auto_allocation.create_new'))
|
||||||
->helperText(trans('admin/setting.misc.auto_allocation.create_new_help'))
|
->helperText(trans('admin/setting.misc.auto_allocation.create_new_help'))
|
||||||
->onIcon('tabler-check')
|
->onIcon(TablerIcon::Check)
|
||||||
->offIcon('tabler-x')
|
->offIcon(TablerIcon::X)
|
||||||
->onColor('success')
|
->onColor('success')
|
||||||
->offColor('danger')
|
->offColor('danger')
|
||||||
->live()
|
->live()
|
||||||
@@ -664,8 +685,8 @@ class Settings extends Page implements HasSchemas
|
|||||||
->schema([
|
->schema([
|
||||||
Toggle::make('PANEL_SEND_INSTALL_NOTIFICATION')
|
Toggle::make('PANEL_SEND_INSTALL_NOTIFICATION')
|
||||||
->label(trans('admin/setting.misc.mail_notifications.server_installed'))
|
->label(trans('admin/setting.misc.mail_notifications.server_installed'))
|
||||||
->onIcon('tabler-check')
|
->onIcon(TablerIcon::Check)
|
||||||
->offIcon('tabler-x')
|
->offIcon(TablerIcon::X)
|
||||||
->onColor('success')
|
->onColor('success')
|
||||||
->offColor('danger')
|
->offColor('danger')
|
||||||
->live()
|
->live()
|
||||||
@@ -674,8 +695,8 @@ class Settings extends Page implements HasSchemas
|
|||||||
->default(env('PANEL_SEND_INSTALL_NOTIFICATION', config('panel.email.send_install_notification'))),
|
->default(env('PANEL_SEND_INSTALL_NOTIFICATION', config('panel.email.send_install_notification'))),
|
||||||
Toggle::make('PANEL_SEND_REINSTALL_NOTIFICATION')
|
Toggle::make('PANEL_SEND_REINSTALL_NOTIFICATION')
|
||||||
->label(trans('admin/setting.misc.mail_notifications.server_reinstalled'))
|
->label(trans('admin/setting.misc.mail_notifications.server_reinstalled'))
|
||||||
->onIcon('tabler-check')
|
->onIcon(TablerIcon::Check)
|
||||||
->offIcon('tabler-x')
|
->offIcon(TablerIcon::X)
|
||||||
->onColor('success')
|
->onColor('success')
|
||||||
->offColor('danger')
|
->offColor('danger')
|
||||||
->live()
|
->live()
|
||||||
@@ -723,8 +744,8 @@ class Settings extends Page implements HasSchemas
|
|||||||
Toggle::make('APP_ACTIVITY_HIDE_ADMIN')
|
Toggle::make('APP_ACTIVITY_HIDE_ADMIN')
|
||||||
->label(trans('admin/setting.misc.activity_log.log_admin'))
|
->label(trans('admin/setting.misc.activity_log.log_admin'))
|
||||||
->inline(false)
|
->inline(false)
|
||||||
->onIcon('tabler-check')
|
->onIcon(TablerIcon::Check)
|
||||||
->offIcon('tabler-x')
|
->offIcon(TablerIcon::X)
|
||||||
->onColor('success')
|
->onColor('success')
|
||||||
->offColor('danger')
|
->offColor('danger')
|
||||||
->live()
|
->live()
|
||||||
@@ -760,8 +781,8 @@ class Settings extends Page implements HasSchemas
|
|||||||
->schema([
|
->schema([
|
||||||
Toggle::make('PANEL_EDITABLE_SERVER_DESCRIPTIONS')
|
Toggle::make('PANEL_EDITABLE_SERVER_DESCRIPTIONS')
|
||||||
->label(trans('admin/setting.misc.server.edit_server_desc'))
|
->label(trans('admin/setting.misc.server.edit_server_desc'))
|
||||||
->onIcon('tabler-check')
|
->onIcon(TablerIcon::Check)
|
||||||
->offIcon('tabler-x')
|
->offIcon(TablerIcon::X)
|
||||||
->onColor('success')
|
->onColor('success')
|
||||||
->offColor('danger')
|
->offColor('danger')
|
||||||
->live()
|
->live()
|
||||||
@@ -823,7 +844,6 @@ class Settings extends Page implements HasSchemas
|
|||||||
|
|
||||||
$this->writeToEnvironment($data);
|
$this->writeToEnvironment($data);
|
||||||
|
|
||||||
Artisan::call('config:clear');
|
|
||||||
Artisan::call('queue:restart');
|
Artisan::call('queue:restart');
|
||||||
|
|
||||||
$this->redirect($this->getUrl());
|
$this->redirect($this->getUrl());
|
||||||
@@ -846,9 +866,10 @@ class Settings extends Page implements HasSchemas
|
|||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
Action::make('save')
|
Action::make('save')
|
||||||
->iconButton()->iconSize(IconSize::ExtraLarge)
|
->hiddenLabel()
|
||||||
->icon('tabler-device-floppy')
|
->icon(TablerIcon::DeviceFloppy)
|
||||||
->action('save')
|
->action('save')
|
||||||
|
->tooltip(trans('filament-panels::resources/pages/edit-record.form.actions.save.label'))
|
||||||
->authorize(fn () => user()?->can('update settings'))
|
->authorize(fn () => user()?->can('update settings'))
|
||||||
->keyBindings(['mod+s']),
|
->keyBindings(['mod+s']),
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Filament\Admin\Pages;
|
namespace App\Filament\Admin\Pages;
|
||||||
|
|
||||||
|
use App\Enums\TablerIcon;
|
||||||
use App\Traits\ResolvesRecordDate;
|
use App\Traits\ResolvesRecordDate;
|
||||||
use Boquizo\FilamentLogViewer\Actions\BackAction;
|
use Boquizo\FilamentLogViewer\Actions\BackAction;
|
||||||
use Boquizo\FilamentLogViewer\Actions\DeleteAction;
|
use Boquizo\FilamentLogViewer\Actions\DeleteAction;
|
||||||
@@ -9,7 +10,6 @@ use Boquizo\FilamentLogViewer\Actions\DownloadAction;
|
|||||||
use Boquizo\FilamentLogViewer\Pages\ViewLog as BaseViewLog;
|
use Boquizo\FilamentLogViewer\Pages\ViewLog as BaseViewLog;
|
||||||
use Filament\Actions\Action;
|
use Filament\Actions\Action;
|
||||||
use Filament\Notifications\Notification;
|
use Filament\Notifications\Notification;
|
||||||
use Filament\Support\Enums\IconSize;
|
|
||||||
use Illuminate\Support\Facades\Http;
|
use Illuminate\Support\Facades\Http;
|
||||||
|
|
||||||
class ViewLogs extends BaseViewLog
|
class ViewLogs extends BaseViewLog
|
||||||
@@ -20,20 +20,23 @@ class ViewLogs extends BaseViewLog
|
|||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
BackAction::make()
|
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)
|
DeleteAction::make(withTooltip: true)
|
||||||
->icon('tabler-trash')->iconSize(IconSize::ExtraLarge)->iconButton(),
|
->icon(TablerIcon::Trash)->iconButton(),
|
||||||
DownloadAction::make(withTooltip: true)
|
DownloadAction::make(withTooltip: true)
|
||||||
->icon('tabler-file-download')->iconSize(IconSize::ExtraLarge)->iconButton(),
|
->icon(TablerIcon::FileDownload)->iconButton(),
|
||||||
Action::make('uploadLogs')
|
Action::make('uploadLogs')
|
||||||
->hiddenLabel()
|
->hiddenLabel()
|
||||||
->icon('tabler-world-upload')->iconSize(IconSize::ExtraLarge)->iconButton()
|
->icon(TablerIcon::WorldUpload)
|
||||||
->requiresConfirmation()
|
->requiresConfirmation()
|
||||||
->tooltip(trans('admin/log.actions.upload_tooltip', ['url' => 'logs.pelican.dev']))
|
->tooltip(trans('admin/log.actions.upload_tooltip', ['url' => 'logs.pelican.dev']))
|
||||||
->modalHeading(trans('admin/log.actions.upload_logs'))
|
->modalHeading(trans('admin/log.actions.upload_logs'))
|
||||||
->modalDescription(fn () => trans('admin/log.actions.upload_logs_description', ['file' => $this->resolveRecordDate(), 'url' => 'https://logs.pelican.dev']))
|
->modalDescription(fn () => trans('admin/log.actions.upload_logs_description', ['file' => $this->resolveRecordDate(), 'url' => 'https://logs.pelican.dev']))
|
||||||
->action(function () {
|
->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)) {
|
if (!file_exists($logPath)) {
|
||||||
Notification::make()
|
Notification::make()
|
||||||
@@ -50,18 +53,12 @@ class ViewLogs extends BaseViewLog
|
|||||||
$uploadLines = $totalLines <= 1000 ? $lines : array_slice($lines, -1000);
|
$uploadLines = $totalLines <= 1000 ? $lines : array_slice($lines, -1000);
|
||||||
$content = implode("\n", $uploadLines);
|
$content = implode("\n", $uploadLines);
|
||||||
|
|
||||||
$logUrl = 'https://logs.pelican.dev';
|
|
||||||
try {
|
try {
|
||||||
$response = Http::timeout(10)->asMultipart()->post($logUrl, [
|
$response = Http::timeout(10)
|
||||||
[
|
->asMultipart()
|
||||||
'name' => 'c',
|
->attach('c', $content)
|
||||||
'contents' => $content,
|
->attach('e', '14d')
|
||||||
],
|
->post('https://logs.pelican.dev');
|
||||||
[
|
|
||||||
'name' => 'e',
|
|
||||||
'contents' => '14d',
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
|
|
||||||
if ($response->failed()) {
|
if ($response->failed()) {
|
||||||
Notification::make()
|
Notification::make()
|
||||||
@@ -81,7 +78,7 @@ class ViewLogs extends BaseViewLog
|
|||||||
->body("{$url}")
|
->body("{$url}")
|
||||||
->success()
|
->success()
|
||||||
->actions([
|
->actions([
|
||||||
Action::make('viewLogs')
|
Action::make('exclude_viewLogs')
|
||||||
->label(trans('admin/log.actions.view_logs'))
|
->label(trans('admin/log.actions.view_logs'))
|
||||||
->url($url)
|
->url($url)
|
||||||
->openUrlInNewTab(true),
|
->openUrlInNewTab(true),
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Filament\Admin\Resources\ApiKeys;
|
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\CreateApiKey;
|
||||||
use App\Filament\Admin\Resources\ApiKeys\Pages\ListApiKeys;
|
use App\Filament\Admin\Resources\ApiKeys\Pages\ListApiKeys;
|
||||||
use App\Filament\Admin\Resources\Users\Pages\EditUser;
|
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\CanCustomizeRelations;
|
||||||
use App\Traits\Filament\CanModifyForm;
|
use App\Traits\Filament\CanModifyForm;
|
||||||
use App\Traits\Filament\CanModifyTable;
|
use App\Traits\Filament\CanModifyTable;
|
||||||
|
use BackedEnum;
|
||||||
use Exception;
|
use Exception;
|
||||||
|
use Filament\Actions\CreateAction;
|
||||||
use Filament\Actions\DeleteAction;
|
use Filament\Actions\DeleteAction;
|
||||||
use Filament\Forms\Components\TagsInput;
|
use Filament\Forms\Components\TagsInput;
|
||||||
use Filament\Forms\Components\Textarea;
|
use Filament\Forms\Components\Textarea;
|
||||||
@@ -19,8 +22,8 @@ use Filament\Forms\Components\ToggleButtons;
|
|||||||
use Filament\Resources\Pages\PageRegistration;
|
use Filament\Resources\Pages\PageRegistration;
|
||||||
use Filament\Resources\Resource;
|
use Filament\Resources\Resource;
|
||||||
use Filament\Schemas\Components\Fieldset;
|
use Filament\Schemas\Components\Fieldset;
|
||||||
|
use Filament\Schemas\Components\Section;
|
||||||
use Filament\Schemas\Schema;
|
use Filament\Schemas\Schema;
|
||||||
use Filament\Support\Enums\IconSize;
|
|
||||||
use Filament\Tables\Columns\TextColumn;
|
use Filament\Tables\Columns\TextColumn;
|
||||||
use Filament\Tables\Table;
|
use Filament\Tables\Table;
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
@@ -34,7 +37,7 @@ class ApiKeyResource extends Resource
|
|||||||
|
|
||||||
protected static ?string $model = ApiKey::class;
|
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
|
public static function getNavigationLabel(): string
|
||||||
{
|
{
|
||||||
@@ -77,7 +80,7 @@ class ApiKeyResource extends Resource
|
|||||||
->columns([
|
->columns([
|
||||||
TextColumn::make('key')
|
TextColumn::make('key')
|
||||||
->label(trans('admin/apikey.table.key'))
|
->label(trans('admin/apikey.table.key'))
|
||||||
->icon('tabler-clipboard-text')
|
->icon(TablerIcon::ClipboardText)
|
||||||
->state(fn (ApiKey $key) => $key->identifier . $key->token)
|
->state(fn (ApiKey $key) => $key->identifier . $key->token)
|
||||||
->copyable(),
|
->copyable(),
|
||||||
TextColumn::make('memo')
|
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),
|
->url(fn (ApiKey $apiKey) => user()?->can('update', $apiKey->user) ? EditUser::getUrl(['record' => $apiKey->user]) : null),
|
||||||
])
|
])
|
||||||
->recordActions([
|
->recordActions([
|
||||||
DeleteAction::make()
|
DeleteAction::make(),
|
||||||
->iconButton()->iconSize(IconSize::ExtraLarge),
|
|
||||||
])
|
])
|
||||||
->emptyStateIcon('tabler-key')
|
->toolbarActions([
|
||||||
|
CreateAction::make(),
|
||||||
|
])
|
||||||
|
->emptyStateIcon(TablerIcon::Key)
|
||||||
->emptyStateDescription('')
|
->emptyStateDescription('')
|
||||||
->emptyStateHeading(trans('admin/apikey.empty'));
|
->emptyStateHeading(trans('admin/apikey.empty'));
|
||||||
}
|
}
|
||||||
@@ -109,12 +114,44 @@ class ApiKeyResource extends Resource
|
|||||||
*/
|
*/
|
||||||
public static function defaultForm(Schema $schema): Schema
|
public static function defaultForm(Schema $schema): Schema
|
||||||
{
|
{
|
||||||
|
$permissionList = ApiKey::getPermissionList();
|
||||||
|
|
||||||
return $schema
|
return $schema
|
||||||
->components([
|
->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')
|
Fieldset::make('Permissions')
|
||||||
->columnSpanFull()
|
->columnSpanFull()
|
||||||
->schema(
|
->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()
|
->label(str($resource)->replace('_', ' ')->title())->inline()
|
||||||
->options([
|
->options([
|
||||||
0 => trans('admin/apikey.permissions.none'),
|
0 => trans('admin/apikey.permissions.none'),
|
||||||
@@ -122,9 +159,9 @@ class ApiKeyResource extends Resource
|
|||||||
3 => trans('admin/apikey.permissions.read_write'),
|
3 => trans('admin/apikey.permissions.read_write'),
|
||||||
])
|
])
|
||||||
->icons([
|
->icons([
|
||||||
0 => 'tabler-book-off',
|
0 => TablerIcon::BookOff,
|
||||||
1 => 'tabler-book',
|
1 => TablerIcon::Book,
|
||||||
3 => 'tabler-writing',
|
3 => TablerIcon::Writing,
|
||||||
])
|
])
|
||||||
->colors([
|
->colors([
|
||||||
0 => 'success',
|
0 => 'success',
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Filament\Admin\Resources\ApiKeys\Pages;
|
namespace App\Filament\Admin\Resources\ApiKeys\Pages;
|
||||||
|
|
||||||
|
use App\Enums\TablerIcon;
|
||||||
use App\Filament\Admin\Resources\ApiKeys\ApiKeyResource;
|
use App\Filament\Admin\Resources\ApiKeys\ApiKeyResource;
|
||||||
use App\Models\ApiKey;
|
use App\Models\ApiKey;
|
||||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||||
@@ -9,7 +10,6 @@ use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
|||||||
use Filament\Actions\Action;
|
use Filament\Actions\Action;
|
||||||
use Filament\Actions\ActionGroup;
|
use Filament\Actions\ActionGroup;
|
||||||
use Filament\Resources\Pages\CreateRecord;
|
use Filament\Resources\Pages\CreateRecord;
|
||||||
use Filament\Support\Enums\IconSize;
|
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
@@ -26,9 +26,12 @@ class CreateApiKey extends CreateRecord
|
|||||||
protected function getDefaultHeaderActions(): array
|
protected function getDefaultHeaderActions(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
$this->getCreateFormAction()->formId('form')
|
Action::make('create')
|
||||||
->iconButton()->iconSize(IconSize::ExtraLarge)
|
->hiddenLabel()
|
||||||
->icon('tabler-file-plus'),
|
->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\Filament\Admin\Resources\ApiKeys\ApiKeyResource;
|
||||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||||
use Filament\Actions\Action;
|
|
||||||
use Filament\Actions\ActionGroup;
|
|
||||||
use Filament\Actions\CreateAction;
|
|
||||||
use Filament\Resources\Pages\ListRecords;
|
use Filament\Resources\Pages\ListRecords;
|
||||||
use Filament\Support\Enums\IconSize;
|
|
||||||
|
|
||||||
class ListApiKeys extends ListRecords
|
class ListApiKeys extends ListRecords
|
||||||
{
|
{
|
||||||
@@ -17,14 +13,4 @@ class ListApiKeys extends ListRecords
|
|||||||
use CanCustomizeHeaderWidgets;
|
use CanCustomizeHeaderWidgets;
|
||||||
|
|
||||||
protected static string $resource = ApiKeyResource::class;
|
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'),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Filament\Admin\Resources\DatabaseHosts;
|
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\CreateDatabaseHost;
|
||||||
use App\Filament\Admin\Resources\DatabaseHosts\Pages\EditDatabaseHost;
|
use App\Filament\Admin\Resources\DatabaseHosts\Pages\EditDatabaseHost;
|
||||||
use App\Filament\Admin\Resources\DatabaseHosts\Pages\ListDatabaseHosts;
|
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\CanCustomizeRelations;
|
||||||
use App\Traits\Filament\CanModifyForm;
|
use App\Traits\Filament\CanModifyForm;
|
||||||
use App\Traits\Filament\CanModifyTable;
|
use App\Traits\Filament\CanModifyTable;
|
||||||
|
use BackedEnum;
|
||||||
use Exception;
|
use Exception;
|
||||||
|
use Filament\Actions\BulkActionGroup;
|
||||||
|
use Filament\Actions\CreateAction;
|
||||||
use Filament\Actions\DeleteBulkAction;
|
use Filament\Actions\DeleteBulkAction;
|
||||||
use Filament\Actions\EditAction;
|
use Filament\Actions\EditAction;
|
||||||
use Filament\Actions\ViewAction;
|
use Filament\Actions\ViewAction;
|
||||||
@@ -37,7 +41,7 @@ class DatabaseHostResource extends Resource
|
|||||||
|
|
||||||
protected static ?string $model = DatabaseHost::class;
|
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';
|
protected static ?string $recordTitleAttribute = 'name';
|
||||||
|
|
||||||
@@ -94,10 +98,13 @@ class DatabaseHostResource extends Resource
|
|||||||
->hidden(fn ($record) => static::getEditAuthorizationResponse($record)->allowed()),
|
->hidden(fn ($record) => static::getEditAuthorizationResponse($record)->allowed()),
|
||||||
EditAction::make(),
|
EditAction::make(),
|
||||||
])
|
])
|
||||||
->groupedBulkActions([
|
->toolbarActions([
|
||||||
DeleteBulkAction::make(),
|
CreateAction::make(),
|
||||||
|
BulkActionGroup::make([
|
||||||
|
DeleteBulkAction::make('exclude_bulk_delete'),
|
||||||
|
]),
|
||||||
])
|
])
|
||||||
->emptyStateIcon('tabler-database')
|
->emptyStateIcon(TablerIcon::Database)
|
||||||
->emptyStateDescription('')
|
->emptyStateDescription('')
|
||||||
->emptyStateHeading(trans('admin/databasehost.no_database_hosts'));
|
->emptyStateHeading(trans('admin/databasehost.no_database_hosts'));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,11 +2,13 @@
|
|||||||
|
|
||||||
namespace App\Filament\Admin\Resources\DatabaseHosts\Pages;
|
namespace App\Filament\Admin\Resources\DatabaseHosts\Pages;
|
||||||
|
|
||||||
|
use App\Enums\TablerIcon;
|
||||||
use App\Filament\Admin\Resources\DatabaseHosts\DatabaseHostResource;
|
use App\Filament\Admin\Resources\DatabaseHosts\DatabaseHostResource;
|
||||||
use App\Services\Databases\Hosts\HostCreationService;
|
use App\Services\Databases\Hosts\HostCreationService;
|
||||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||||
use Exception;
|
use Exception;
|
||||||
|
use Filament\Actions\Action;
|
||||||
use Filament\Forms\Components\Hidden;
|
use Filament\Forms\Components\Hidden;
|
||||||
use Filament\Forms\Components\Select;
|
use Filament\Forms\Components\Select;
|
||||||
use Filament\Forms\Components\TextInput;
|
use Filament\Forms\Components\TextInput;
|
||||||
@@ -44,6 +46,17 @@ class CreateDatabaseHost extends CreateRecord
|
|||||||
$this->service = $service;
|
$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[]
|
/** @return Step[]
|
||||||
* @throws Exception
|
* @throws Exception
|
||||||
*/
|
*/
|
||||||
@@ -175,7 +188,7 @@ class CreateDatabaseHost extends CreateRecord
|
|||||||
->title(trans('admin/databasehost.error'))
|
->title(trans('admin/databasehost.error'))
|
||||||
->body($exception->getMessage())
|
->body($exception->getMessage())
|
||||||
->color('danger')
|
->color('danger')
|
||||||
->icon('tabler-database')
|
->icon(TablerIcon::Database)
|
||||||
->danger()
|
->danger()
|
||||||
->send();
|
->send();
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Filament\Admin\Resources\DatabaseHosts\Pages;
|
namespace App\Filament\Admin\Resources\DatabaseHosts\Pages;
|
||||||
|
|
||||||
|
use App\Enums\TablerIcon;
|
||||||
use App\Filament\Admin\Resources\DatabaseHosts\DatabaseHostResource;
|
use App\Filament\Admin\Resources\DatabaseHosts\DatabaseHostResource;
|
||||||
use App\Models\DatabaseHost;
|
use App\Models\DatabaseHost;
|
||||||
use App\Services\Databases\Hosts\HostUpdateService;
|
use App\Services\Databases\Hosts\HostUpdateService;
|
||||||
@@ -12,7 +13,6 @@ use Filament\Actions\ActionGroup;
|
|||||||
use Filament\Actions\DeleteAction;
|
use Filament\Actions\DeleteAction;
|
||||||
use Filament\Notifications\Notification;
|
use Filament\Notifications\Notification;
|
||||||
use Filament\Resources\Pages\EditRecord;
|
use Filament\Resources\Pages\EditRecord;
|
||||||
use Filament\Support\Enums\IconSize;
|
|
||||||
use Filament\Support\Exceptions\Halt;
|
use Filament\Support\Exceptions\Halt;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use PDOException;
|
use PDOException;
|
||||||
@@ -36,12 +36,14 @@ class EditDatabaseHost extends EditRecord
|
|||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
DeleteAction::make()
|
DeleteAction::make()
|
||||||
->label(fn (DatabaseHost $databaseHost) => $databaseHost->databases()->count() > 0 ? trans('admin/databasehost.delete_help') : trans('filament-actions::delete.single.modal.actions.delete.label'))
|
->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)
|
->disabled(fn (DatabaseHost $databaseHost) => $databaseHost->databases()->count() > 0),
|
||||||
->iconButton()->iconSize(IconSize::ExtraLarge),
|
Action::make('save')
|
||||||
$this->getSaveFormAction()->formId('form')
|
->hiddenLabel()
|
||||||
->iconButton()->iconSize(IconSize::ExtraLarge)
|
->action('save')
|
||||||
->icon('tabler-device-floppy'),
|
->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'))
|
->title(trans('admin/databasehost.error'))
|
||||||
->body($exception->getMessage())
|
->body($exception->getMessage())
|
||||||
->color('danger')
|
->color('danger')
|
||||||
->icon('tabler-database')
|
->icon(TablerIcon::Database)
|
||||||
->danger()
|
->danger()
|
||||||
->send();
|
->send();
|
||||||
|
|
||||||
|
|||||||
@@ -5,11 +5,7 @@ namespace App\Filament\Admin\Resources\DatabaseHosts\Pages;
|
|||||||
use App\Filament\Admin\Resources\DatabaseHosts\DatabaseHostResource;
|
use App\Filament\Admin\Resources\DatabaseHosts\DatabaseHostResource;
|
||||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||||
use Filament\Actions\Action;
|
|
||||||
use Filament\Actions\ActionGroup;
|
|
||||||
use Filament\Actions\CreateAction;
|
|
||||||
use Filament\Resources\Pages\ListRecords;
|
use Filament\Resources\Pages\ListRecords;
|
||||||
use Filament\Support\Enums\IconSize;
|
|
||||||
|
|
||||||
class ListDatabaseHosts extends ListRecords
|
class ListDatabaseHosts extends ListRecords
|
||||||
{
|
{
|
||||||
@@ -17,14 +13,4 @@ class ListDatabaseHosts extends ListRecords
|
|||||||
use CanCustomizeHeaderWidgets;
|
use CanCustomizeHeaderWidgets;
|
||||||
|
|
||||||
protected static string $resource = DatabaseHostResource::class;
|
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\ActionGroup;
|
||||||
use Filament\Actions\EditAction;
|
use Filament\Actions\EditAction;
|
||||||
use Filament\Resources\Pages\ViewRecord;
|
use Filament\Resources\Pages\ViewRecord;
|
||||||
use Filament\Support\Enums\IconSize;
|
|
||||||
|
|
||||||
class ViewDatabaseHost extends ViewRecord
|
class ViewDatabaseHost extends ViewRecord
|
||||||
{
|
{
|
||||||
@@ -22,8 +21,7 @@ class ViewDatabaseHost extends ViewRecord
|
|||||||
protected function getDefaultHeaderActions(): array
|
protected function getDefaultHeaderActions(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
EditAction::make()
|
EditAction::make(),
|
||||||
->iconButton()->iconSize(IconSize::ExtraLarge),
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ use Filament\Actions\ViewAction;
|
|||||||
use Filament\Forms\Components\TextInput;
|
use Filament\Forms\Components\TextInput;
|
||||||
use Filament\Resources\RelationManagers\RelationManager;
|
use Filament\Resources\RelationManagers\RelationManager;
|
||||||
use Filament\Schemas\Schema;
|
use Filament\Schemas\Schema;
|
||||||
use Filament\Support\Enums\IconSize;
|
|
||||||
use Filament\Tables\Columns\TextColumn;
|
use Filament\Tables\Columns\TextColumn;
|
||||||
use Filament\Tables\Table;
|
use Filament\Tables\Table;
|
||||||
|
|
||||||
@@ -37,7 +36,7 @@ class DatabasesRelationManager extends RelationManager
|
|||||||
->formatStateUsing(fn (Database $record) => $record->remote === '%' ? trans('admin/databasehost.anywhere'). ' ( % )' : $record->remote),
|
->formatStateUsing(fn (Database $record) => $record->remote === '%' ? trans('admin/databasehost.anywhere'). ' ( % )' : $record->remote),
|
||||||
TextInput::make('max_connections')
|
TextInput::make('max_connections')
|
||||||
->label(trans('admin/databasehost.table.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')
|
TextInput::make('jdbc')
|
||||||
->label(trans('admin/databasehost.table.connection_string'))
|
->label(trans('admin/databasehost.table.connection_string'))
|
||||||
->columnSpanFull()
|
->columnSpanFull()
|
||||||
@@ -63,15 +62,14 @@ class DatabasesRelationManager extends RelationManager
|
|||||||
->url(fn (Database $database) => route('filament.admin.resources.servers.edit', ['record' => $database->server_id])),
|
->url(fn (Database $database) => route('filament.admin.resources.servers.edit', ['record' => $database->server_id])),
|
||||||
TextColumn::make('max_connections')
|
TextColumn::make('max_connections')
|
||||||
->label(trans('admin/databasehost.table.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')
|
DateTimeColumn::make('created_at')
|
||||||
->label(trans('admin/databasehost.table.created_at')),
|
->label(trans('admin/databasehost.table.created_at')),
|
||||||
])
|
])
|
||||||
->recordActions([
|
->recordActions([
|
||||||
ViewAction::make()
|
ViewAction::make()
|
||||||
->color('primary'),
|
->color('primary'),
|
||||||
DeleteAction::make()
|
DeleteAction::make(),
|
||||||
->iconButton()->iconSize(IconSize::ExtraLarge),
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
namespace App\Filament\Admin\Resources\Eggs;
|
namespace App\Filament\Admin\Resources\Eggs;
|
||||||
|
|
||||||
use App\Enums\CustomizationKey;
|
use App\Enums\CustomizationKey;
|
||||||
|
use App\Enums\TablerIcon;
|
||||||
use App\Filament\Admin\Resources\Eggs\Pages\CreateEgg;
|
use App\Filament\Admin\Resources\Eggs\Pages\CreateEgg;
|
||||||
use App\Filament\Admin\Resources\Eggs\Pages\EditEgg;
|
use App\Filament\Admin\Resources\Eggs\Pages\EditEgg;
|
||||||
use App\Filament\Admin\Resources\Eggs\Pages\ListEggs;
|
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\Models\Egg;
|
||||||
use App\Traits\Filament\CanCustomizePages;
|
use App\Traits\Filament\CanCustomizePages;
|
||||||
use App\Traits\Filament\CanCustomizeRelations;
|
use App\Traits\Filament\CanCustomizeRelations;
|
||||||
|
use BackedEnum;
|
||||||
use Filament\Resources\Pages\PageRegistration;
|
use Filament\Resources\Pages\PageRegistration;
|
||||||
use Filament\Resources\RelationManagers\RelationManager;
|
use Filament\Resources\RelationManagers\RelationManager;
|
||||||
use Filament\Resources\Resource;
|
use Filament\Resources\Resource;
|
||||||
@@ -21,7 +23,7 @@ class EggResource extends Resource
|
|||||||
|
|
||||||
protected static ?string $model = Egg::class;
|
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';
|
protected static ?string $recordTitleAttribute = 'name';
|
||||||
|
|
||||||
|
|||||||
@@ -2,16 +2,18 @@
|
|||||||
|
|
||||||
namespace App\Filament\Admin\Resources\Eggs\Pages;
|
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\Admin\Resources\Eggs\EggResource;
|
||||||
use App\Filament\Components\Forms\Fields\CopyFrom;
|
use App\Filament\Components\Forms\Fields\CopyFrom;
|
||||||
|
use App\Filament\Components\Forms\Fields\MonacoEditor;
|
||||||
use App\Models\EggVariable;
|
use App\Models\EggVariable;
|
||||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||||
use Exception;
|
use App\Traits\Filament\CanCustomizeTabs;
|
||||||
use Filament\Actions\Action;
|
use Filament\Actions\Action;
|
||||||
use Filament\Actions\ActionGroup;
|
use Filament\Actions\ActionGroup;
|
||||||
use Filament\Forms\Components\Checkbox;
|
use Filament\Forms\Components\Checkbox;
|
||||||
use Filament\Forms\Components\CodeEditor;
|
|
||||||
use Filament\Forms\Components\Hidden;
|
use Filament\Forms\Components\Hidden;
|
||||||
use Filament\Forms\Components\KeyValue;
|
use Filament\Forms\Components\KeyValue;
|
||||||
use Filament\Forms\Components\Repeater;
|
use Filament\Forms\Components\Repeater;
|
||||||
@@ -27,7 +29,6 @@ use Filament\Schemas\Components\Tabs\Tab;
|
|||||||
use Filament\Schemas\Components\Utilities\Get;
|
use Filament\Schemas\Components\Utilities\Get;
|
||||||
use Filament\Schemas\Components\Utilities\Set;
|
use Filament\Schemas\Components\Utilities\Set;
|
||||||
use Filament\Schemas\Schema;
|
use Filament\Schemas\Schema;
|
||||||
use Filament\Support\Enums\IconSize;
|
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use Illuminate\Validation\Rules\Unique;
|
use Illuminate\Validation\Rules\Unique;
|
||||||
@@ -36,6 +37,7 @@ class CreateEgg extends CreateRecord
|
|||||||
{
|
{
|
||||||
use CanCustomizeHeaderActions;
|
use CanCustomizeHeaderActions;
|
||||||
use CanCustomizeHeaderWidgets;
|
use CanCustomizeHeaderWidgets;
|
||||||
|
use CanCustomizeTabs;
|
||||||
|
|
||||||
protected static string $resource = EggResource::class;
|
protected static string $resource = EggResource::class;
|
||||||
|
|
||||||
@@ -45,9 +47,12 @@ class CreateEgg extends CreateRecord
|
|||||||
protected function getDefaultHeaderActions(): array
|
protected function getDefaultHeaderActions(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
$this->getCreateFormAction()->formId('form')
|
Action::make('create')
|
||||||
->iconButton()->iconSize(IconSize::ExtraLarge)
|
->hiddenLabel()
|
||||||
->icon('tabler-file-plus'),
|
->action('create')
|
||||||
|
->keyBindings(['mod+s'])
|
||||||
|
->tooltip(trans('filament-panels::resources/pages/create-record.form.actions.create.label'))
|
||||||
|
->icon(TablerIcon::FilePlus),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,226 +61,232 @@ class CreateEgg extends CreateRecord
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @throws Exception
|
|
||||||
*/
|
|
||||||
public function form(Schema $schema): Schema
|
public function form(Schema $schema): Schema
|
||||||
{
|
{
|
||||||
return $schema
|
return $schema
|
||||||
->components([
|
->components([
|
||||||
Tabs::make()->tabs([
|
Tabs::make()
|
||||||
Tab::make('configuration')
|
->tabs($this->getTabs())
|
||||||
->label(trans('admin/egg.tabs.configuration'))
|
->columnSpanFull()
|
||||||
->columns(['default' => 1, 'sm' => 1, 'md' => 2, 'lg' => 4])
|
->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([
|
->schema([
|
||||||
TextInput::make('name')
|
TextInput::make('name')
|
||||||
->label(trans('admin/egg.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()
|
->live()
|
||||||
->columnSpanFull()
|
->debounce(750)
|
||||||
->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()
|
|
||||||
->maxLength(255)
|
->maxLength(255)
|
||||||
->helperText(trans('admin/egg.stop_command_help')),
|
->columnSpanFull()
|
||||||
Textarea::make('config_startup')->rows(10)->json()
|
->afterStateUpdated(fn (Set $set, $state) => $set('env_variable', str($state)->trim()->snake()->upper()->toString()))
|
||||||
->label(trans('admin/egg.start_config'))
|
->unique(modifyRuleUsing: fn (Unique $rule, Get $get) => $rule->where('egg_id', $get('../../id')))
|
||||||
->default('{}')
|
->validationMessages([
|
||||||
->helperText(trans('admin/egg.start_config_help')),
|
'unique' => trans('admin/egg.error_unique'),
|
||||||
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',
|
|
||||||
])
|
])
|
||||||
->required(),
|
->required(),
|
||||||
CodeEditor::make('script_install')
|
Textarea::make('description')->label(trans('admin/egg.description'))->columnSpanFull(),
|
||||||
->label(trans('admin/egg.script_install'))
|
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()
|
->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
|
protected function handleRecordCreation(array $data): Model
|
||||||
|
|||||||
@@ -2,21 +2,24 @@
|
|||||||
|
|
||||||
namespace App\Filament\Admin\Resources\Eggs\Pages;
|
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\Admin\Resources\Eggs\EggResource;
|
||||||
|
use App\Filament\Components\Actions\DeleteIcon;
|
||||||
use App\Filament\Components\Actions\ExportEggAction;
|
use App\Filament\Components\Actions\ExportEggAction;
|
||||||
use App\Filament\Components\Actions\ImportEggAction;
|
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\CopyFrom;
|
||||||
|
use App\Filament\Components\Forms\Fields\MonacoEditor;
|
||||||
use App\Models\Egg;
|
use App\Models\Egg;
|
||||||
use App\Models\EggVariable;
|
use App\Models\EggVariable;
|
||||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||||
use Exception;
|
use App\Traits\Filament\CanCustomizeTabs;
|
||||||
use Filament\Actions\Action;
|
use Filament\Actions\Action;
|
||||||
use Filament\Actions\ActionGroup;
|
use Filament\Actions\ActionGroup;
|
||||||
use Filament\Actions\DeleteAction;
|
use Filament\Actions\DeleteAction;
|
||||||
use Filament\Forms\Components\Checkbox;
|
use Filament\Forms\Components\Checkbox;
|
||||||
use Filament\Forms\Components\CodeEditor;
|
|
||||||
use Filament\Forms\Components\FileUpload;
|
|
||||||
use Filament\Forms\Components\Hidden;
|
use Filament\Forms\Components\Hidden;
|
||||||
use Filament\Forms\Components\KeyValue;
|
use Filament\Forms\Components\KeyValue;
|
||||||
use Filament\Forms\Components\Repeater;
|
use Filament\Forms\Components\Repeater;
|
||||||
@@ -25,11 +28,8 @@ use Filament\Forms\Components\TagsInput;
|
|||||||
use Filament\Forms\Components\Textarea;
|
use Filament\Forms\Components\Textarea;
|
||||||
use Filament\Forms\Components\TextInput;
|
use Filament\Forms\Components\TextInput;
|
||||||
use Filament\Forms\Components\Toggle;
|
use Filament\Forms\Components\Toggle;
|
||||||
use Filament\Infolists\Components\TextEntry;
|
|
||||||
use Filament\Notifications\Notification;
|
|
||||||
use Filament\Resources\Pages\EditRecord;
|
use Filament\Resources\Pages\EditRecord;
|
||||||
use Filament\Schemas\Components\Fieldset;
|
use Filament\Schemas\Components\Fieldset;
|
||||||
use Filament\Schemas\Components\Flex;
|
|
||||||
use Filament\Schemas\Components\Grid;
|
use Filament\Schemas\Components\Grid;
|
||||||
use Filament\Schemas\Components\Image;
|
use Filament\Schemas\Components\Image;
|
||||||
use Filament\Schemas\Components\Tabs;
|
use Filament\Schemas\Components\Tabs;
|
||||||
@@ -37,412 +37,260 @@ use Filament\Schemas\Components\Tabs\Tab;
|
|||||||
use Filament\Schemas\Components\Utilities\Get;
|
use Filament\Schemas\Components\Utilities\Get;
|
||||||
use Filament\Schemas\Components\Utilities\Set;
|
use Filament\Schemas\Components\Utilities\Set;
|
||||||
use Filament\Schemas\Schema;
|
use Filament\Schemas\Schema;
|
||||||
use Filament\Support\Enums\IconSize;
|
|
||||||
use Illuminate\Validation\Rules\Unique;
|
use Illuminate\Validation\Rules\Unique;
|
||||||
|
|
||||||
class EditEgg extends EditRecord
|
class EditEgg extends EditRecord
|
||||||
{
|
{
|
||||||
use CanCustomizeHeaderActions;
|
use CanCustomizeHeaderActions;
|
||||||
use CanCustomizeHeaderWidgets;
|
use CanCustomizeHeaderWidgets;
|
||||||
|
use CanCustomizeTabs;
|
||||||
|
|
||||||
protected static string $resource = EggResource::class;
|
protected static string $resource = EggResource::class;
|
||||||
|
|
||||||
/**
|
|
||||||
* @throws Exception
|
|
||||||
*/
|
|
||||||
public function form(Schema $schema): Schema
|
public function form(Schema $schema): Schema
|
||||||
{
|
{
|
||||||
return $schema
|
return $schema
|
||||||
->components([
|
->components([
|
||||||
Tabs::make()->tabs([
|
Tabs::make()
|
||||||
Tab::make('configuration')
|
->tabs($this->getTabs())
|
||||||
->label(trans('admin/egg.tabs.configuration'))
|
->columnSpanFull()
|
||||||
->columns(['default' => 2, 'sm' => 2, 'md' => 4, 'lg' => 6])
|
->persistTabInQueryString(),
|
||||||
->icon('tabler-egg')
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @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([
|
->schema([
|
||||||
Grid::make(2)
|
Image::make('', 'icon')
|
||||||
->columnSpan(1)
|
->hidden(fn ($record) => !$record->icon)
|
||||||
->schema([
|
->url(fn ($record) => $record->icon)
|
||||||
Image::make('', '')
|
->imageSize(150)
|
||||||
->hidden(fn ($record) => !$record->image)
|
->columnSpanFull()
|
||||||
->url(fn ($record) => $record->image)
|
->alignJustify(),
|
||||||
->alt('')
|
UploadIcon::make(),
|
||||||
->alignJustify()
|
DeleteIcon::make()
|
||||||
->imageSize(150)
|
->iconStoragePath(Egg::getIconStoragePath()),
|
||||||
->columnSpanFull(),
|
]),
|
||||||
Flex::make([
|
TextInput::make('name')
|
||||||
Action::make('uploadImage')
|
->label(trans('admin/egg.name'))
|
||||||
->iconButton()
|
->required()
|
||||||
->iconSize(IconSize::Large)
|
->maxLength(255)
|
||||||
->icon('tabler-photo-up')
|
->columnSpan(['default' => 2, 'sm' => 2, 'md' => 3, 'lg' => 2])
|
||||||
->modal()
|
->helperText(trans('admin/egg.name_help')),
|
||||||
->modalHeading('')
|
Textarea::make('description')
|
||||||
->modalSubmitActionLabel(trans('admin/egg.import.import_image'))
|
->label(trans('admin/egg.description'))
|
||||||
->schema([
|
->rows(3)
|
||||||
Tabs::make()
|
->columnSpan(['default' => 2, 'sm' => 2, 'md' => 4, 'lg' => 3])
|
||||||
->contained(false)
|
->helperText(trans('admin/egg.description_help')),
|
||||||
->tabs([
|
TextInput::make('id')
|
||||||
Tab::make(trans('admin/egg.import.url'))
|
->label(trans('admin/egg.egg_id'))
|
||||||
->schema([
|
->columnSpan(1)
|
||||||
Hidden::make('base64Image'),
|
->disabled(),
|
||||||
TextInput::make('image_url')
|
TextInput::make('uuid')
|
||||||
->label(trans('admin/egg.import.image_url'))
|
->label(trans('admin/egg.egg_uuid'))
|
||||||
->reactive()
|
->disabled()
|
||||||
->autocomplete(false)
|
->columnSpan(['default' => 1, 'sm' => 1, 'md' => 1, 'lg' => 2])
|
||||||
->debounce(500)
|
->helperText(trans('admin/egg.uuid_help')),
|
||||||
->afterStateUpdated(function ($state, Set $set) {
|
TextInput::make('author')
|
||||||
if (!$state) {
|
->label(trans('admin/egg.author'))
|
||||||
$set('image_url_error', null);
|
->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 {
|
return $data;
|
||||||
if (!filter_var($state, FILTER_VALIDATE_URL)) {
|
})
|
||||||
throw new \Exception(trans('admin/egg.import.invalid_url'));
|
->schema([
|
||||||
}
|
|
||||||
|
|
||||||
$allowedExtensions = [
|
|
||||||
'png' => 'image/png',
|
|
||||||
'jpg' => 'image/jpeg',
|
|
||||||
'jpeg' => 'image/jpeg',
|
|
||||||
'gif' => 'image/gif',
|
|
||||||
'webp' => 'image/webp',
|
|
||||||
'svg' => 'image/svg+xml',
|
|
||||||
];
|
|
||||||
|
|
||||||
$extension = strtolower(pathinfo(parse_url($state, PHP_URL_PATH), PATHINFO_EXTENSION));
|
|
||||||
|
|
||||||
if (!array_key_exists($extension, $allowedExtensions)) {
|
|
||||||
throw new \Exception(trans('admin/egg.import.unsupported_format', ['format' => implode(', ', $allowedExtensions)]));
|
|
||||||
}
|
|
||||||
|
|
||||||
$host = parse_url($state, PHP_URL_HOST);
|
|
||||||
$ip = gethostbyname($host);
|
|
||||||
|
|
||||||
if (
|
|
||||||
filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) === false
|
|
||||||
) {
|
|
||||||
throw new \Exception(trans('admin/egg.import.no_local_ip'));
|
|
||||||
}
|
|
||||||
|
|
||||||
$context = stream_context_create([
|
|
||||||
'http' => ['timeout' => 3],
|
|
||||||
'https' => [
|
|
||||||
'timeout' => 3,
|
|
||||||
'verify_peer' => true,
|
|
||||||
'verify_peer_name' => true,
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
|
|
||||||
$imageContent = @file_get_contents($state, false, $context, 0, 1048576); // 1024KB
|
|
||||||
|
|
||||||
if (!$imageContent) {
|
|
||||||
throw new \Exception(trans('admin/egg.import.image_error'));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (strlen($imageContent) >= 1048576) {
|
|
||||||
throw new \Exception(trans('admin/egg.import.image_too_large'));
|
|
||||||
}
|
|
||||||
|
|
||||||
$mimeType = $allowedExtensions[$extension];
|
|
||||||
$base64 = 'data:' . $mimeType . ';base64,' . base64_encode($imageContent);
|
|
||||||
|
|
||||||
$set('base64Image', $base64);
|
|
||||||
$set('image_url_error', null);
|
|
||||||
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
$set('image_url_error', $e->getMessage());
|
|
||||||
$set('base64Image', null);
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
TextEntry::make('image_url_error')
|
|
||||||
->hiddenLabel()
|
|
||||||
->visible(fn ($get) => $get('image_url_error') !== null)
|
|
||||||
->afterStateHydrated(fn ($set, $get) => $get('image_url_error')),
|
|
||||||
Image::make(fn (Get $get) => $get('image_url'), '')
|
|
||||||
->imageSize(150)
|
|
||||||
->visible(fn ($get) => $get('image_url') && !$get('image_url_error'))
|
|
||||||
->alignCenter(),
|
|
||||||
]),
|
|
||||||
Tab::make(trans('admin/egg.import.file'))
|
|
||||||
->schema([
|
|
||||||
FileUpload::make('image')
|
|
||||||
->hiddenLabel()
|
|
||||||
->previewable()
|
|
||||||
->openable(false)
|
|
||||||
->downloadable(false)
|
|
||||||
->maxSize(1024)
|
|
||||||
->maxFiles(1)
|
|
||||||
->columnSpanFull()
|
|
||||||
->alignCenter()
|
|
||||||
->imageEditor()
|
|
||||||
->saveUploadedFileUsing(function ($file, Set $set) {
|
|
||||||
$base64 = "data:{$file->getMimeType()};base64,". base64_encode(file_get_contents($file->getRealPath()));
|
|
||||||
$set('base64Image', $base64);
|
|
||||||
|
|
||||||
return $base64;
|
|
||||||
}),
|
|
||||||
]),
|
|
||||||
]),
|
|
||||||
])
|
|
||||||
->action(function (array $data, $record): void {
|
|
||||||
$base64 = $data['base64Image'] ?? null;
|
|
||||||
|
|
||||||
if (empty($base64) && !empty($data['image'])) {
|
|
||||||
$base64 = $data['image'];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!empty($base64)) {
|
|
||||||
$record->update([
|
|
||||||
'image' => $base64,
|
|
||||||
]);
|
|
||||||
|
|
||||||
Notification::make()
|
|
||||||
->title(trans('admin/egg.import.image_updated'))
|
|
||||||
->success()
|
|
||||||
->send();
|
|
||||||
|
|
||||||
$record->refresh();
|
|
||||||
} else {
|
|
||||||
Notification::make()
|
|
||||||
->title(trans('admin/egg.import.no_image'))
|
|
||||||
->warning()
|
|
||||||
->send();
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
Action::make('deleteImage')
|
|
||||||
->visible(fn ($record) => $record->image)
|
|
||||||
->label('')
|
|
||||||
->icon('tabler-trash')
|
|
||||||
->iconButton()
|
|
||||||
->iconSize(IconSize::Large)
|
|
||||||
->color('danger')
|
|
||||||
->action(function ($record) {
|
|
||||||
|
|
||||||
$record->update([
|
|
||||||
'image' => null,
|
|
||||||
]);
|
|
||||||
|
|
||||||
Notification::make()
|
|
||||||
->title(trans('admin/egg.import.image_deleted'))
|
|
||||||
->success()
|
|
||||||
->send();
|
|
||||||
|
|
||||||
$record->refresh();
|
|
||||||
}),
|
|
||||||
]),
|
|
||||||
]),
|
|
||||||
TextInput::make('name')
|
TextInput::make('name')
|
||||||
->label(trans('admin/egg.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()
|
->live()
|
||||||
->columnSpanFull()
|
->debounce(750)
|
||||||
->required()
|
|
||||||
->addActionLabel(trans('admin/egg.add_startup'))
|
|
||||||
->keyLabel(trans('admin/egg.startup_name'))
|
|
||||||
->valueLabel(trans('admin/egg.startup_command'))
|
|
||||||
->helperText(trans('admin/egg.startup_help')),
|
|
||||||
TagsInput::make('file_denylist')
|
|
||||||
->label(trans('admin/egg.file_denylist'))
|
|
||||||
->placeholder('denied-file.txt')
|
|
||||||
->helperText(trans('admin/egg.file_denylist_help'))
|
|
||||||
->columnSpan(['default' => 2, 'sm' => 2, 'md' => 2, 'lg' => 3]),
|
|
||||||
TextInput::make('update_url')
|
|
||||||
->label(trans('admin/egg.update_url'))
|
|
||||||
->url()
|
|
||||||
->hintIcon('tabler-question-mark', trans('admin/egg.update_url_help'))
|
|
||||||
->columnSpan(['default' => 2, 'sm' => 2, 'md' => 2, 'lg' => 3]),
|
|
||||||
TagsInput::make('features')
|
|
||||||
->label(trans('admin/egg.features'))
|
|
||||||
->columnSpan(['default' => 2, 'sm' => 2, 'md' => 2, 'lg' => 3]),
|
|
||||||
Hidden::make('script_is_privileged')
|
|
||||||
->helperText('The docker images available to servers using this egg.'),
|
|
||||||
TagsInput::make('tags')
|
|
||||||
->label(trans('admin/egg.tags'))
|
|
||||||
->columnSpan(['default' => 2, 'sm' => 2, 'md' => 2, 'lg' => 3]),
|
|
||||||
KeyValue::make('docker_images')
|
|
||||||
->label(trans('admin/egg.docker_images'))
|
|
||||||
->live()
|
|
||||||
->columnSpanFull()
|
|
||||||
->required()
|
|
||||||
->addActionLabel(trans('admin/egg.add_image'))
|
|
||||||
->keyLabel(trans('admin/egg.docker_name'))
|
|
||||||
->valueLabel(trans('admin/egg.docker_uri'))
|
|
||||||
->helperText(trans('admin/egg.docker_help')),
|
|
||||||
]),
|
|
||||||
Tab::make('process_management')
|
|
||||||
->label(trans('admin/egg.tabs.process_management'))
|
|
||||||
->columns()
|
|
||||||
->icon('tabler-server-cog')
|
|
||||||
->schema([
|
|
||||||
CopyFrom::make('copy_process_from')
|
|
||||||
->process(),
|
|
||||||
TextInput::make('config_stop')
|
|
||||||
->label(trans('admin/egg.stop_command'))
|
|
||||||
->maxLength(255)
|
->maxLength(255)
|
||||||
->helperText(trans('admin/egg.stop_command_help')),
|
->columnSpanFull()
|
||||||
Textarea::make('config_startup')->rows(10)->json()
|
->afterStateUpdated(fn (Set $set, $state) => $set('env_variable', str($state)->trim()->snake()->upper()->toString()))
|
||||||
->label(trans('admin/egg.start_config'))
|
->unique(modifyRuleUsing: fn (Unique $rule, Get $get) => $rule->where('egg_id', $get('../../id')))
|
||||||
->helperText(trans('admin/egg.start_config_help')),
|
->validationMessages([
|
||||||
Textarea::make('config_files')->rows(10)->json()
|
'unique' => trans('admin/egg.error_unique'),
|
||||||
->label(trans('admin/egg.config_files'))
|
|
||||||
->helperText(trans('admin/egg.config_files_help')),
|
|
||||||
Textarea::make('config_logs')->rows(10)->json()
|
|
||||||
->label(trans('admin/egg.log_config'))
|
|
||||||
->helperText(trans('admin/egg.log_config_help')),
|
|
||||||
]),
|
|
||||||
Tab::make('egg_variables')
|
|
||||||
->label(trans('admin/egg.tabs.egg_variables'))
|
|
||||||
->columnSpanFull()
|
|
||||||
->icon('tabler-variable')
|
|
||||||
->schema([
|
|
||||||
Repeater::make('variables')
|
|
||||||
->hiddenLabel()
|
|
||||||
->grid()
|
|
||||||
->relationship('variables')
|
|
||||||
->reorderable()
|
|
||||||
->collapsible()->collapsed()
|
|
||||||
->orderColumn()
|
|
||||||
->addActionLabel(trans('admin/egg.add_new_variable'))
|
|
||||||
->itemLabel(fn (array $state) => $state['name'])
|
|
||||||
->mutateRelationshipDataBeforeCreateUsing(function (array $data): array {
|
|
||||||
$data['default_value'] ??= '';
|
|
||||||
$data['description'] ??= '';
|
|
||||||
$data['rules'] ??= [];
|
|
||||||
$data['user_viewable'] ??= '';
|
|
||||||
$data['user_editable'] ??= '';
|
|
||||||
|
|
||||||
return $data;
|
|
||||||
})
|
|
||||||
->mutateRelationshipDataBeforeSaveUsing(function (array $data): array {
|
|
||||||
$data['default_value'] ??= '';
|
|
||||||
$data['description'] ??= '';
|
|
||||||
$data['rules'] ??= [];
|
|
||||||
$data['user_viewable'] ??= '';
|
|
||||||
$data['user_editable'] ??= '';
|
|
||||||
|
|
||||||
return $data;
|
|
||||||
})
|
|
||||||
->schema([
|
|
||||||
TextInput::make('name')
|
|
||||||
->label(trans('admin/egg.name'))
|
|
||||||
->live()
|
|
||||||
->debounce(750)
|
|
||||||
->maxLength(255)
|
|
||||||
->columnSpanFull()
|
|
||||||
->afterStateUpdated(fn (Set $set, $state) => $set('env_variable', str($state)->trim()->snake()->upper()->toString()))
|
|
||||||
->unique(modifyRuleUsing: fn (Unique $rule, Get $get) => $rule->where('egg_id', $get('../../id')))
|
|
||||||
->validationMessages([
|
|
||||||
'unique' => trans('admin/egg.error_unique'),
|
|
||||||
])
|
|
||||||
->required(),
|
|
||||||
Textarea::make('description')->label(trans('admin/egg.description'))->columnSpanFull(),
|
|
||||||
TextInput::make('env_variable')
|
|
||||||
->label(trans('admin/egg.environment_variable'))
|
|
||||||
->maxLength(255)
|
|
||||||
->prefix('{{')
|
|
||||||
->suffix('}}')
|
|
||||||
->hintIcon('tabler-code', fn ($state) => "{{{$state}}}")
|
|
||||||
->unique(modifyRuleUsing: fn (Unique $rule, Get $get) => $rule->where('egg_id', $get('../../id')))
|
|
||||||
->rules(EggVariable::getRulesForField('env_variable'))
|
|
||||||
->validationMessages([
|
|
||||||
'unique' => trans('admin/egg.error_unique'),
|
|
||||||
'required' => trans('admin/egg.error_required'),
|
|
||||||
'*' => trans('admin/egg.error_reserved'),
|
|
||||||
])
|
|
||||||
->required(),
|
|
||||||
TextInput::make('default_value')->label(trans('admin/egg.default_value')),
|
|
||||||
Fieldset::make(trans('admin/egg.user_permissions'))
|
|
||||||
->schema([
|
|
||||||
Checkbox::make('user_viewable')->label(trans('admin/egg.viewable')),
|
|
||||||
Checkbox::make('user_editable')->label(trans('admin/egg.editable')),
|
|
||||||
]),
|
|
||||||
TagsInput::make('rules')
|
|
||||||
->label(trans('admin/egg.rules'))
|
|
||||||
->columnSpanFull()
|
|
||||||
->reorderable()
|
|
||||||
->suggestions([
|
|
||||||
'required',
|
|
||||||
'nullable',
|
|
||||||
'string',
|
|
||||||
'integer',
|
|
||||||
'numeric',
|
|
||||||
'boolean',
|
|
||||||
'alpha',
|
|
||||||
'alpha_dash',
|
|
||||||
'alpha_num',
|
|
||||||
'url',
|
|
||||||
'email',
|
|
||||||
'regex:',
|
|
||||||
'min:',
|
|
||||||
'max:',
|
|
||||||
'between:',
|
|
||||||
'between:1024,65535',
|
|
||||||
'in:',
|
|
||||||
'in:true,false',
|
|
||||||
]),
|
|
||||||
]),
|
|
||||||
]),
|
|
||||||
Tab::make('install_script')
|
|
||||||
->label(trans('admin/egg.tabs.install_script'))
|
|
||||||
->columns(3)
|
|
||||||
->icon('tabler-file-download')
|
|
||||||
->schema([
|
|
||||||
CopyFrom::make('copy_script_from')
|
|
||||||
->script(),
|
|
||||||
TextInput::make('script_container')
|
|
||||||
->label(trans('admin/egg.script_container'))
|
|
||||||
->required()
|
|
||||||
->maxLength(255)
|
|
||||||
->placeholder('ghcr.io/pelican-eggs/installers:debian'),
|
|
||||||
Select::make('script_entry')
|
|
||||||
->label(trans('admin/egg.script_entry'))
|
|
||||||
->selectablePlaceholder(false)
|
|
||||||
->options([
|
|
||||||
'bash' => 'bash',
|
|
||||||
'ash' => 'ash',
|
|
||||||
'/bin/bash' => '/bin/bash',
|
|
||||||
])
|
])
|
||||||
->required(),
|
->required(),
|
||||||
CodeEditor::make('script_install')
|
Textarea::make('description')->label(trans('admin/egg.description'))->columnSpanFull(),
|
||||||
->hiddenLabel()
|
TextInput::make('env_variable')
|
||||||
->columnSpanFull(),
|
->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> */
|
/** @return array<Action|ActionGroup> */
|
||||||
@@ -451,14 +299,16 @@ class EditEgg extends EditRecord
|
|||||||
return [
|
return [
|
||||||
DeleteAction::make()
|
DeleteAction::make()
|
||||||
->disabled(fn (Egg $egg): bool => $egg->servers()->count() > 0)
|
->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'))
|
->tooltip(fn (Egg $egg): string => $egg->servers()->count() <= 0 ? trans('filament-actions::delete.single.label') : trans('admin/egg.in_use')),
|
||||||
->iconButton()->iconSize(IconSize::ExtraLarge),
|
|
||||||
ExportEggAction::make(),
|
ExportEggAction::make(),
|
||||||
ImportEggAction::make()
|
ImportEggAction::make()
|
||||||
->multiple(false),
|
->multiple(false),
|
||||||
$this->getSaveFormAction()->formId('form')
|
Action::make('save')
|
||||||
->iconButton()->iconSize(IconSize::ExtraLarge)
|
->hiddenLabel()
|
||||||
->icon('tabler-device-floppy'),
|
->action('save')
|
||||||
|
->keyBindings(['mod+s'])
|
||||||
|
->tooltip(trans('filament-panels::resources/pages/edit-record.form.actions.save.label'))
|
||||||
|
->icon(TablerIcon::DeviceFloppy),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Filament\Admin\Resources\Eggs\Pages;
|
namespace App\Filament\Admin\Resources\Eggs\Pages;
|
||||||
|
|
||||||
|
use App\Enums\TablerIcon;
|
||||||
use App\Filament\Admin\Resources\Eggs\EggResource;
|
use App\Filament\Admin\Resources\Eggs\EggResource;
|
||||||
use App\Filament\Components\Actions\ExportEggAction;
|
use App\Filament\Components\Actions\ExportEggAction;
|
||||||
use App\Filament\Components\Actions\ImportEggAction;
|
use App\Filament\Components\Actions\ImportEggAction;
|
||||||
@@ -12,17 +13,17 @@ use App\Models\Egg;
|
|||||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||||
use Exception;
|
use Exception;
|
||||||
use Filament\Actions\Action;
|
use Filament\Actions\BulkActionGroup;
|
||||||
use Filament\Actions\ActionGroup;
|
|
||||||
use Filament\Actions\CreateAction;
|
use Filament\Actions\CreateAction;
|
||||||
use Filament\Actions\DeleteBulkAction;
|
use Filament\Actions\DeleteBulkAction;
|
||||||
use Filament\Actions\EditAction;
|
use Filament\Actions\EditAction;
|
||||||
use Filament\Actions\ReplicateAction;
|
use Filament\Actions\ReplicateAction;
|
||||||
|
use Filament\Notifications\Notification;
|
||||||
use Filament\Resources\Pages\ListRecords;
|
use Filament\Resources\Pages\ListRecords;
|
||||||
use Filament\Support\Enums\IconSize;
|
|
||||||
use Filament\Tables\Columns\ImageColumn;
|
use Filament\Tables\Columns\ImageColumn;
|
||||||
use Filament\Tables\Columns\TextColumn;
|
use Filament\Tables\Columns\TextColumn;
|
||||||
use Filament\Tables\Table;
|
use Filament\Tables\Table;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
class ListEggs extends ListRecords
|
class ListEggs extends ListRecords
|
||||||
@@ -37,6 +38,8 @@ class ListEggs extends ListRecords
|
|||||||
*/
|
*/
|
||||||
public function table(Table $table): Table
|
public function table(Table $table): Table
|
||||||
{
|
{
|
||||||
|
$defaultEggIcon = 'data:image/svg+xml;base64,' . base64_encode(file_get_contents(public_path('pelican.svg')));
|
||||||
|
|
||||||
return $table
|
return $table
|
||||||
->searchable(true)
|
->searchable(true)
|
||||||
->defaultPaginationPageOption(25)
|
->defaultPaginationPageOption(25)
|
||||||
@@ -44,13 +47,11 @@ class ListEggs extends ListRecords
|
|||||||
TextColumn::make('id')
|
TextColumn::make('id')
|
||||||
->label('Id')
|
->label('Id')
|
||||||
->hidden(),
|
->hidden(),
|
||||||
ImageColumn::make('image')
|
ImageColumn::make('icon')
|
||||||
->label('')
|
->label('')
|
||||||
->alignCenter()
|
->alignCenter()
|
||||||
->circular()
|
->circular()
|
||||||
->getStateUsing(fn ($record) => $record->image
|
->getStateUsing(fn (Egg $record) => $record->icon ?: $defaultEggIcon),
|
||||||
? $record->image
|
|
||||||
: 'data:image/svg+xml;base64,' . base64_encode(file_get_contents(public_path('pelican.svg')))),
|
|
||||||
TextColumn::make('name')
|
TextColumn::make('name')
|
||||||
->label(trans('admin/egg.name'))
|
->label(trans('admin/egg.name'))
|
||||||
->description(fn ($record): ?string => (strlen($record->description) > 120) ? substr($record->description, 0, 120).'...' : $record->description)
|
->description(fn ($record): ?string => (strlen($record->description) > 120) ? substr($record->description, 0, 120).'...' : $record->description)
|
||||||
@@ -63,19 +64,13 @@ class ListEggs extends ListRecords
|
|||||||
])
|
])
|
||||||
->recordActions([
|
->recordActions([
|
||||||
EditAction::make()
|
EditAction::make()
|
||||||
->iconButton()
|
->tooltip(trans('filament-actions::edit.single.label')),
|
||||||
->tooltip(trans('filament-actions::edit.single.label'))
|
|
||||||
->iconSize(IconSize::Large),
|
|
||||||
ExportEggAction::make()
|
ExportEggAction::make()
|
||||||
->tooltip(trans('filament-actions::export.modal.actions.export.label'))
|
->tooltip(trans('filament-actions::export.modal.actions.export.label')),
|
||||||
->iconSize(IconSize::Large),
|
|
||||||
UpdateEggAction::make()
|
UpdateEggAction::make()
|
||||||
->tooltip(trans_choice('admin/egg.update', 1))
|
->tooltip(trans_choice('admin/egg.update', 1)),
|
||||||
->iconSize(IconSize::Large),
|
|
||||||
ReplicateAction::make()
|
ReplicateAction::make()
|
||||||
->iconButton()
|
|
||||||
->tooltip(trans('filament-actions::replicate.single.label'))
|
->tooltip(trans('filament-actions::replicate.single.label'))
|
||||||
->iconSize(IconSize::Large)
|
|
||||||
->modal(false)
|
->modal(false)
|
||||||
->excludeAttributes(['author', 'uuid', 'update_url', 'servers_count', 'created_at', 'updated_at'])
|
->excludeAttributes(['author', 'uuid', 'update_url', 'servers_count', 'created_at', 'updated_at'])
|
||||||
->beforeReplicaSaved(function (Egg $replica) {
|
->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()))
|
->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])),
|
->successRedirectUrl(fn (Egg $replica) => EditEgg::getUrl(['record' => $replica])),
|
||||||
])
|
])
|
||||||
->groupedBulkActions([
|
->toolbarActions([
|
||||||
DeleteBulkAction::make()
|
ImportEggAction::make()
|
||||||
->before(fn (&$records) => $records = $records->filter(function ($egg) {
|
->multiple(),
|
||||||
/** @var Egg $egg */
|
CreateAction::make(),
|
||||||
return $egg->servers_count <= 0;
|
BulkActionGroup::make([
|
||||||
})),
|
DeleteBulkAction::make('exclude_bulk_delete')
|
||||||
UpdateEggBulkAction::make()
|
->before(function (Collection &$records) {
|
||||||
->before(fn (&$records) => $records = $records->filter(function ($egg) {
|
$eggsWithServers = $records->filter(fn (Egg $egg) => $egg->servers_count > 0);
|
||||||
/** @var Egg $egg */
|
|
||||||
return cache()->get("eggs.$egg->uuid.update", false);
|
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('')
|
->emptyStateDescription('')
|
||||||
->emptyStateHeading(trans('admin/egg.no_eggs'))
|
->emptyStateHeading(trans('admin/egg.no_eggs'))
|
||||||
->filters([
|
->filters([
|
||||||
@@ -106,18 +136,4 @@ class ListEggs extends ListRecords
|
|||||||
->model(Egg::class),
|
->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;
|
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\CreateMount;
|
||||||
use App\Filament\Admin\Resources\Mounts\Pages\EditMount;
|
use App\Filament\Admin\Resources\Mounts\Pages\EditMount;
|
||||||
use App\Filament\Admin\Resources\Mounts\Pages\ListMounts;
|
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\CanCustomizeRelations;
|
||||||
use App\Traits\Filament\CanModifyForm;
|
use App\Traits\Filament\CanModifyForm;
|
||||||
use App\Traits\Filament\CanModifyTable;
|
use App\Traits\Filament\CanModifyTable;
|
||||||
|
use BackedEnum;
|
||||||
use Exception;
|
use Exception;
|
||||||
|
use Filament\Actions\BulkActionGroup;
|
||||||
|
use Filament\Actions\CreateAction;
|
||||||
use Filament\Actions\DeleteBulkAction;
|
use Filament\Actions\DeleteBulkAction;
|
||||||
use Filament\Actions\EditAction;
|
use Filament\Actions\EditAction;
|
||||||
use Filament\Actions\ViewAction;
|
use Filament\Actions\ViewAction;
|
||||||
@@ -21,7 +25,6 @@ use Filament\Forms\Components\TextInput;
|
|||||||
use Filament\Forms\Components\ToggleButtons;
|
use Filament\Forms\Components\ToggleButtons;
|
||||||
use Filament\Resources\Pages\PageRegistration;
|
use Filament\Resources\Pages\PageRegistration;
|
||||||
use Filament\Resources\Resource;
|
use Filament\Resources\Resource;
|
||||||
use Filament\Schemas\Components\Group;
|
|
||||||
use Filament\Schemas\Components\Section;
|
use Filament\Schemas\Components\Section;
|
||||||
use Filament\Schemas\Components\StateCasts\BooleanStateCast;
|
use Filament\Schemas\Components\StateCasts\BooleanStateCast;
|
||||||
use Filament\Schemas\Schema;
|
use Filament\Schemas\Schema;
|
||||||
@@ -38,7 +41,7 @@ class MountResource extends Resource
|
|||||||
|
|
||||||
protected static ?string $model = Mount::class;
|
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';
|
protected static ?string $recordTitleAttribute = 'name';
|
||||||
|
|
||||||
@@ -89,19 +92,28 @@ class MountResource extends Resource
|
|||||||
TextColumn::make('read_only')
|
TextColumn::make('read_only')
|
||||||
->label(trans('admin/mount.table.read_only'))
|
->label(trans('admin/mount.table.read_only'))
|
||||||
->badge()
|
->badge()
|
||||||
->icon(fn ($state) => $state ? 'tabler-writing-off' : 'tabler-writing')
|
->icon(fn ($state) => $state ? TablerIcon::WritingOff : TablerIcon::Writing)
|
||||||
->color(fn ($state) => $state ? 'success' : 'warning')
|
->color(fn ($state) => $state ? 'success' : 'warning')
|
||||||
->formatStateUsing(fn ($state) => $state ? trans('admin/mount.toggles.read_only') : trans('admin/mount.toggles.writable')),
|
->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([
|
->recordActions([
|
||||||
ViewAction::make()
|
ViewAction::make()
|
||||||
->hidden(fn ($record) => static::getEditAuthorizationResponse($record)->allowed()),
|
->hidden(fn ($record) => static::getEditAuthorizationResponse($record)->allowed()),
|
||||||
EditAction::make(),
|
EditAction::make(),
|
||||||
])
|
])
|
||||||
->groupedBulkActions([
|
->toolbarActions([
|
||||||
DeleteBulkAction::make(),
|
CreateAction::make(),
|
||||||
|
BulkActionGroup::make([
|
||||||
|
DeleteBulkAction::make('exclude_bulk_delete'),
|
||||||
|
]),
|
||||||
])
|
])
|
||||||
->emptyStateIcon('tabler-layers-linked')
|
->emptyStateIcon(TablerIcon::LayersLinked)
|
||||||
->emptyStateDescription('')
|
->emptyStateDescription('')
|
||||||
->emptyStateHeading(trans('admin/mount.no_mounts'));
|
->emptyStateHeading(trans('admin/mount.no_mounts'));
|
||||||
}
|
}
|
||||||
@@ -118,7 +130,8 @@ class MountResource extends Resource
|
|||||||
->label(trans('admin/mount.name'))
|
->label(trans('admin/mount.name'))
|
||||||
->required()
|
->required()
|
||||||
->helperText(trans('admin/mount.name_help'))
|
->helperText(trans('admin/mount.name_help'))
|
||||||
->maxLength(64),
|
->maxLength(64)
|
||||||
|
->columnSpanFull(),
|
||||||
ToggleButtons::make('read_only')
|
ToggleButtons::make('read_only')
|
||||||
->label(trans('admin/mount.read_only'))
|
->label(trans('admin/mount.read_only'))
|
||||||
->helperText(trans('admin/mount.read_only_help'))
|
->helperText(trans('admin/mount.read_only_help'))
|
||||||
@@ -128,8 +141,8 @@ class MountResource extends Resource
|
|||||||
true => trans('admin/mount.toggles.read_only'),
|
true => trans('admin/mount.toggles.read_only'),
|
||||||
])
|
])
|
||||||
->icons([
|
->icons([
|
||||||
false => 'tabler-writing',
|
false => TablerIcon::Writing,
|
||||||
true => 'tabler-writing-off',
|
true => TablerIcon::WritingOff,
|
||||||
])
|
])
|
||||||
->colors([
|
->colors([
|
||||||
false => 'warning',
|
false => 'warning',
|
||||||
@@ -137,6 +150,24 @@ class MountResource extends Resource
|
|||||||
])
|
])
|
||||||
->inline()
|
->inline()
|
||||||
->default(false),
|
->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')
|
TextInput::make('source')
|
||||||
->label(trans('admin/mount.source'))
|
->label(trans('admin/mount.source'))
|
||||||
->required()
|
->required()
|
||||||
@@ -151,30 +182,32 @@ class MountResource extends Resource
|
|||||||
->label(trans('admin/mount.description'))
|
->label(trans('admin/mount.description'))
|
||||||
->helperText(trans('admin/mount.description_help'))
|
->helperText(trans('admin/mount.description_help'))
|
||||||
->columnSpanFull(),
|
->columnSpanFull(),
|
||||||
])->columnSpan(1)->columns([
|
])
|
||||||
'default' => 1,
|
->columnSpan([
|
||||||
'lg' => 2,
|
'default' => 1,
|
||||||
]),
|
'lg' => 2,
|
||||||
Group::make()->schema([
|
])
|
||||||
Section::make()->schema([
|
->columns([
|
||||||
Select::make('eggs')->multiple()
|
'default' => 1,
|
||||||
->label(trans('admin/mount.eggs'))
|
'xl' => 2,
|
||||||
// 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([
|
Section::make()->schema([
|
||||||
'default' => 1,
|
Select::make('eggs')
|
||||||
'lg' => 2,
|
->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([
|
])->columns([
|
||||||
'default' => 1,
|
'default' => 1,
|
||||||
'lg' => 2,
|
'lg' => 3,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,13 +2,13 @@
|
|||||||
|
|
||||||
namespace App\Filament\Admin\Resources\Mounts\Pages;
|
namespace App\Filament\Admin\Resources\Mounts\Pages;
|
||||||
|
|
||||||
|
use App\Enums\TablerIcon;
|
||||||
use App\Filament\Admin\Resources\Mounts\MountResource;
|
use App\Filament\Admin\Resources\Mounts\MountResource;
|
||||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||||
use Filament\Actions\Action;
|
use Filament\Actions\Action;
|
||||||
use Filament\Actions\ActionGroup;
|
use Filament\Actions\ActionGroup;
|
||||||
use Filament\Resources\Pages\CreateRecord;
|
use Filament\Resources\Pages\CreateRecord;
|
||||||
use Filament\Support\Enums\IconSize;
|
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
@@ -25,9 +25,12 @@ class CreateMount extends CreateRecord
|
|||||||
protected function getDefaultHeaderActions(): array
|
protected function getDefaultHeaderActions(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
$this->getCreateFormAction()->formId('form')
|
Action::make('create')
|
||||||
->iconButton()->iconSize(IconSize::ExtraLarge)
|
->hiddenLabel()
|
||||||
->icon('tabler-file-plus'),
|
->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
|
protected function handleRecordCreation(array $data): Model
|
||||||
{
|
{
|
||||||
$data['uuid'] ??= Str::uuid()->toString();
|
$data['uuid'] ??= Str::uuid()->toString();
|
||||||
$data['user_mountable'] = 1;
|
|
||||||
|
|
||||||
return parent::handleRecordCreation($data);
|
return parent::handleRecordCreation($data);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Filament\Admin\Resources\Mounts\Pages;
|
namespace App\Filament\Admin\Resources\Mounts\Pages;
|
||||||
|
|
||||||
|
use App\Enums\TablerIcon;
|
||||||
use App\Filament\Admin\Resources\Mounts\MountResource;
|
use App\Filament\Admin\Resources\Mounts\MountResource;
|
||||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||||
@@ -9,7 +10,6 @@ use Filament\Actions\Action;
|
|||||||
use Filament\Actions\ActionGroup;
|
use Filament\Actions\ActionGroup;
|
||||||
use Filament\Actions\DeleteAction;
|
use Filament\Actions\DeleteAction;
|
||||||
use Filament\Resources\Pages\EditRecord;
|
use Filament\Resources\Pages\EditRecord;
|
||||||
use Filament\Support\Enums\IconSize;
|
|
||||||
|
|
||||||
class EditMount extends EditRecord
|
class EditMount extends EditRecord
|
||||||
{
|
{
|
||||||
@@ -22,11 +22,13 @@ class EditMount extends EditRecord
|
|||||||
protected function getDefaultHeaderActions(): array
|
protected function getDefaultHeaderActions(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
DeleteAction::make()
|
DeleteAction::make(),
|
||||||
->iconButton()->iconSize(IconSize::ExtraLarge),
|
Action::make('save')
|
||||||
$this->getSaveFormAction()->formId('form')
|
->hiddenLabel()
|
||||||
->iconButton()->iconSize(IconSize::ExtraLarge)
|
->action('save')
|
||||||
->icon('tabler-device-floppy'),
|
->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\Filament\Admin\Resources\Mounts\MountResource;
|
||||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||||
use Filament\Actions\Action;
|
|
||||||
use Filament\Actions\ActionGroup;
|
|
||||||
use Filament\Actions\CreateAction;
|
|
||||||
use Filament\Resources\Pages\ListRecords;
|
use Filament\Resources\Pages\ListRecords;
|
||||||
use Filament\Support\Enums\IconSize;
|
|
||||||
|
|
||||||
class ListMounts extends ListRecords
|
class ListMounts extends ListRecords
|
||||||
{
|
{
|
||||||
@@ -17,14 +13,4 @@ class ListMounts extends ListRecords
|
|||||||
use CanCustomizeHeaderWidgets;
|
use CanCustomizeHeaderWidgets;
|
||||||
|
|
||||||
protected static string $resource = MountResource::class;
|
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\ActionGroup;
|
||||||
use Filament\Actions\EditAction;
|
use Filament\Actions\EditAction;
|
||||||
use Filament\Resources\Pages\ViewRecord;
|
use Filament\Resources\Pages\ViewRecord;
|
||||||
use Filament\Support\Enums\IconSize;
|
|
||||||
|
|
||||||
class ViewMount extends ViewRecord
|
class ViewMount extends ViewRecord
|
||||||
{
|
{
|
||||||
@@ -22,9 +21,7 @@ class ViewMount extends ViewRecord
|
|||||||
protected function getDefaultHeaderActions(): array
|
protected function getDefaultHeaderActions(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
EditAction::make()
|
EditAction::make(),
|
||||||
->iconSize(IconSize::ExtraLarge)
|
|
||||||
->iconButton(),
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
namespace App\Filament\Admin\Resources\Nodes;
|
namespace App\Filament\Admin\Resources\Nodes;
|
||||||
|
|
||||||
use App\Enums\CustomizationKey;
|
use App\Enums\CustomizationKey;
|
||||||
|
use App\Enums\TablerIcon;
|
||||||
use App\Filament\Admin\Resources\Nodes\Pages\CreateNode;
|
use App\Filament\Admin\Resources\Nodes\Pages\CreateNode;
|
||||||
use App\Filament\Admin\Resources\Nodes\Pages\EditNode;
|
use App\Filament\Admin\Resources\Nodes\Pages\EditNode;
|
||||||
use App\Filament\Admin\Resources\Nodes\Pages\ListNodes;
|
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\Models\Node;
|
||||||
use App\Traits\Filament\CanCustomizePages;
|
use App\Traits\Filament\CanCustomizePages;
|
||||||
use App\Traits\Filament\CanCustomizeRelations;
|
use App\Traits\Filament\CanCustomizeRelations;
|
||||||
|
use BackedEnum;
|
||||||
use Filament\Resources\Pages\PageRegistration;
|
use Filament\Resources\Pages\PageRegistration;
|
||||||
use Filament\Resources\RelationManagers\RelationManager;
|
use Filament\Resources\RelationManagers\RelationManager;
|
||||||
use Filament\Resources\Resource;
|
use Filament\Resources\Resource;
|
||||||
@@ -23,7 +25,7 @@ class NodeResource extends Resource
|
|||||||
|
|
||||||
protected static ?string $model = Node::class;
|
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';
|
protected static ?string $recordTitleAttribute = 'name';
|
||||||
|
|
||||||
|
|||||||
@@ -2,11 +2,12 @@
|
|||||||
|
|
||||||
namespace App\Filament\Admin\Resources\Nodes\Pages;
|
namespace App\Filament\Admin\Resources\Nodes\Pages;
|
||||||
|
|
||||||
|
use App\Enums\TablerIcon;
|
||||||
use App\Filament\Admin\Resources\Nodes\NodeResource;
|
use App\Filament\Admin\Resources\Nodes\NodeResource;
|
||||||
use App\Models\Node;
|
use App\Models\Node;
|
||||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||||
use Exception;
|
use App\Traits\Filament\CanCustomizeSteps;
|
||||||
use Filament\Actions\Action;
|
use Filament\Actions\Action;
|
||||||
use Filament\Forms\Components\Hidden;
|
use Filament\Forms\Components\Hidden;
|
||||||
use Filament\Forms\Components\TagsInput;
|
use Filament\Forms\Components\TagsInput;
|
||||||
@@ -27,394 +28,398 @@ class CreateNode extends CreateRecord
|
|||||||
{
|
{
|
||||||
use CanCustomizeHeaderActions;
|
use CanCustomizeHeaderActions;
|
||||||
use CanCustomizeHeaderWidgets;
|
use CanCustomizeHeaderWidgets;
|
||||||
|
use CanCustomizeSteps;
|
||||||
|
|
||||||
protected static string $resource = NodeResource::class;
|
protected static string $resource = NodeResource::class;
|
||||||
|
|
||||||
protected static bool $canCreateAnother = false;
|
protected static bool $canCreateAnother = false;
|
||||||
|
|
||||||
/**
|
|
||||||
* @throws Exception
|
|
||||||
*/
|
|
||||||
public function form(Schema $schema): Schema
|
public function form(Schema $schema): Schema
|
||||||
{
|
{
|
||||||
return $schema
|
return $schema
|
||||||
->components([
|
->components([
|
||||||
Wizard::make([
|
Wizard::make($this->getSteps())
|
||||||
Step::make('basic')
|
->columnSpanFull()
|
||||||
->label(trans('admin/node.tabs.basic_settings'))
|
->nextAction(fn (Action $action) => $action->tooltip(fn () => $action->getLabel())->iconButton()->iconSize(IconSize::ExtraLarge)->icon(TablerIcon::ArrowRight))
|
||||||
->icon('tabler-server')
|
->previousAction(fn (Action $action) => $action->tooltip(fn () => $action->getLabel())->iconButton()->iconSize(IconSize::ExtraLarge)->icon(TablerIcon::ArrowLeft))
|
||||||
->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'))
|
|
||||||
->submitAction(new HtmlString(Blade::render(<<<'BLADE'
|
->submitAction(new HtmlString(Blade::render(<<<'BLADE'
|
||||||
<x-filament::icon-button
|
<x-filament::icon-button
|
||||||
type="submit"
|
type="submit"
|
||||||
iconSize="xl"
|
iconSize="xl"
|
||||||
icon="tabler-file-plus"
|
icon="tabler-plus"
|
||||||
>
|
tooltip="{{ trans('admin/node.create') }}"
|
||||||
{{ trans('admin/node.create') }}
|
>
|
||||||
</x-filament::icon-button>
|
{{ trans('admin/node.create') }}
|
||||||
BLADE))),
|
</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' => 1,
|
||||||
|
'md' => 2,
|
||||||
|
'lg' => 4,
|
||||||
|
])
|
||||||
|
->schema([
|
||||||
|
TextInput::make('fqdn')
|
||||||
|
->columnSpan(2)
|
||||||
|
->required()
|
||||||
|
->autofocus()
|
||||||
|
->live(debounce: 1500)
|
||||||
|
->rules(Node::getRulesForField('fqdn'))
|
||||||
|
->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(fn () => request()->isSecure() ? trans('admin/node.fqdn_ssl') : null)
|
||||||
|
->validationMessages([
|
||||||
|
'prohibited' => trans('admin/node.dns_error'),
|
||||||
|
])
|
||||||
|
->prohibited(function ($state, Get $get) {
|
||||||
|
if (!$state) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_ip($state)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$ip = $get('ip');
|
||||||
|
|
||||||
|
return !is_ip($ip);
|
||||||
|
})
|
||||||
|
->hintColor(function ($state, Get $get) {
|
||||||
|
if (!$state) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_ip($state)) {
|
||||||
|
if (request()->isSecure()) {
|
||||||
|
return 'warning';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$ip = $get('ip');
|
||||||
|
|
||||||
|
return is_ip($ip) ? 'success' : 'danger';
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
})
|
||||||
|
->hint(function ($state, Get $get) {
|
||||||
|
if (!$state) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_ip($state)) {
|
||||||
|
if (request()->isSecure()) {
|
||||||
|
return trans('admin/node.ssl_ip');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$ip = $get('ip');
|
||||||
|
|
||||||
|
return is_ip($ip) ? trans('admin/node.valid') . ': ' . $ip : trans('admin/node.invalid');
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
})
|
||||||
|
->afterStateUpdated(function (Set $set, ?string $state) {
|
||||||
|
$set('ip', null);
|
||||||
|
|
||||||
|
if (!$state) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
[$subdomain] = str($state)->explode('.', 2);
|
||||||
|
if (!is_numeric($subdomain)) {
|
||||||
|
$set('name', $subdomain);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_ip($state)) {
|
||||||
|
$ip = get_ip_from_hostname($state);
|
||||||
|
if (is_ip($ip)) {
|
||||||
|
$set('ip', $ip);
|
||||||
|
} else {
|
||||||
|
$set('ip', null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
->maxLength(255),
|
||||||
|
Hidden::make('ip')
|
||||||
|
->saved(false),
|
||||||
|
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('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'),
|
||||||
|
TextInput::make('name')
|
||||||
|
->label(trans('admin/node.display_name'))
|
||||||
|
->columnSpan([
|
||||||
|
'default' => 1,
|
||||||
|
'sm' => 1,
|
||||||
|
'md' => 1,
|
||||||
|
'lg' => 2,
|
||||||
|
])
|
||||||
|
->required()
|
||||||
|
->maxLength(100),
|
||||||
|
ToggleButtons::make('connection')
|
||||||
|
->label(trans('admin/node.ssl'))
|
||||||
|
->columnSpan(2)
|
||||||
|
->inline()
|
||||||
|
->helperText(function () {
|
||||||
|
if (request()->isSecure()) {
|
||||||
|
return trans('admin/node.panel_on_ssl');
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
})
|
||||||
|
->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);
|
||||||
|
}),
|
||||||
|
Hidden::make('scheme')
|
||||||
|
->default(fn () => request()->isSecure() ? 'https' : 'http'),
|
||||||
|
Hidden::make('behind_proxy')
|
||||||
|
->default(false),
|
||||||
|
]),
|
||||||
|
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
|
protected function getRedirectUrlParameters(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -2,18 +2,17 @@
|
|||||||
|
|
||||||
namespace App\Filament\Admin\Resources\Nodes\Pages;
|
namespace App\Filament\Admin\Resources\Nodes\Pages;
|
||||||
|
|
||||||
|
use App\Enums\TablerIcon;
|
||||||
use App\Filament\Admin\Resources\Nodes\NodeResource;
|
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\Columns\NodeHealthColumn;
|
||||||
use App\Filament\Components\Tables\Filters\TagsFilter;
|
use App\Filament\Components\Tables\Filters\TagsFilter;
|
||||||
use App\Models\Node;
|
use App\Models\Node;
|
||||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||||
use Filament\Actions\Action;
|
|
||||||
use Filament\Actions\ActionGroup;
|
|
||||||
use Filament\Actions\CreateAction;
|
use Filament\Actions\CreateAction;
|
||||||
use Filament\Actions\EditAction;
|
use Filament\Actions\EditAction;
|
||||||
use Filament\Resources\Pages\ListRecords;
|
use Filament\Resources\Pages\ListRecords;
|
||||||
use Filament\Support\Enums\IconSize;
|
|
||||||
use Filament\Tables\Columns\IconColumn;
|
use Filament\Tables\Columns\IconColumn;
|
||||||
use Filament\Tables\Columns\TextColumn;
|
use Filament\Tables\Columns\TextColumn;
|
||||||
use Filament\Tables\Table;
|
use Filament\Tables\Table;
|
||||||
@@ -36,6 +35,7 @@ class ListNodes extends ListRecords
|
|||||||
->searchable()
|
->searchable()
|
||||||
->hidden(),
|
->hidden(),
|
||||||
NodeHealthColumn::make('health'),
|
NodeHealthColumn::make('health'),
|
||||||
|
NodeClientHealthColumn::make('reachable'),
|
||||||
TextColumn::make('name')
|
TextColumn::make('name')
|
||||||
->label(trans('admin/node.table.name'))
|
->label(trans('admin/node.table.name'))
|
||||||
->sortable()
|
->sortable()
|
||||||
@@ -48,14 +48,14 @@ class ListNodes extends ListRecords
|
|||||||
IconColumn::make('scheme')
|
IconColumn::make('scheme')
|
||||||
->visibleFrom('xl')
|
->visibleFrom('xl')
|
||||||
->label('SSL')
|
->label('SSL')
|
||||||
->trueIcon('tabler-lock')
|
->trueIcon(TablerIcon::Lock)
|
||||||
->falseIcon('tabler-lock-open-off')
|
->falseIcon(TablerIcon::LockOpenOff)
|
||||||
->state(fn (Node $node) => $node->scheme === 'https'),
|
->state(fn (Node $node) => $node->scheme === 'https'),
|
||||||
IconColumn::make('public')
|
IconColumn::make('public')
|
||||||
->label(trans('admin/node.table.public'))
|
->label(trans('admin/node.table.public'))
|
||||||
->visibleFrom('lg')
|
->visibleFrom('lg')
|
||||||
->trueIcon('tabler-eye-check')
|
->trueIcon(TablerIcon::EyeCheck)
|
||||||
->falseIcon('tabler-eye-cancel'),
|
->falseIcon(TablerIcon::EyeCancel),
|
||||||
TextColumn::make('servers_count')
|
TextColumn::make('servers_count')
|
||||||
->visibleFrom('sm')
|
->visibleFrom('sm')
|
||||||
->counts('servers')
|
->counts('servers')
|
||||||
@@ -65,7 +65,10 @@ class ListNodes extends ListRecords
|
|||||||
->recordActions([
|
->recordActions([
|
||||||
EditAction::make(),
|
EditAction::make(),
|
||||||
])
|
])
|
||||||
->emptyStateIcon('tabler-server-2')
|
->toolbarActions([
|
||||||
|
CreateAction::make(),
|
||||||
|
])
|
||||||
|
->emptyStateIcon(TablerIcon::Server2)
|
||||||
->emptyStateDescription('')
|
->emptyStateDescription('')
|
||||||
->emptyStateHeading(trans('admin/node.no_nodes'))
|
->emptyStateHeading(trans('admin/node.no_nodes'))
|
||||||
->filters([
|
->filters([
|
||||||
@@ -73,14 +76,4 @@ class ListNodes extends ListRecords
|
|||||||
->model(Node::class),
|
->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;
|
namespace App\Filament\Admin\Resources\Nodes\RelationManagers;
|
||||||
|
|
||||||
|
use App\Enums\TablerIcon;
|
||||||
use App\Filament\Admin\Resources\Servers\Pages\CreateServer;
|
use App\Filament\Admin\Resources\Servers\Pages\CreateServer;
|
||||||
use App\Filament\Components\Actions\UpdateNodeAllocations;
|
use App\Filament\Components\Actions\UpdateNodeAllocations;
|
||||||
use App\Models\Allocation;
|
use App\Models\Allocation;
|
||||||
use App\Models\Node;
|
use App\Models\Node;
|
||||||
use App\Services\Allocations\AssignmentService;
|
use App\Services\Allocations\AssignmentService;
|
||||||
|
use BackedEnum;
|
||||||
use Exception;
|
use Exception;
|
||||||
use Filament\Actions\Action;
|
use Filament\Actions\Action;
|
||||||
use Filament\Actions\DeleteBulkAction;
|
use Filament\Actions\DeleteBulkAction;
|
||||||
@@ -16,7 +18,6 @@ use Filament\Forms\Components\TextInput;
|
|||||||
use Filament\Resources\RelationManagers\RelationManager;
|
use Filament\Resources\RelationManagers\RelationManager;
|
||||||
use Filament\Schemas\Components\Utilities\Get;
|
use Filament\Schemas\Components\Utilities\Get;
|
||||||
use Filament\Schemas\Components\Utilities\Set;
|
use Filament\Schemas\Components\Utilities\Set;
|
||||||
use Filament\Support\Enums\IconSize;
|
|
||||||
use Filament\Tables\Columns\SelectColumn;
|
use Filament\Tables\Columns\SelectColumn;
|
||||||
use Filament\Tables\Columns\TextColumn;
|
use Filament\Tables\Columns\TextColumn;
|
||||||
use Filament\Tables\Columns\TextInputColumn;
|
use Filament\Tables\Columns\TextInputColumn;
|
||||||
@@ -29,7 +30,7 @@ class AllocationsRelationManager extends RelationManager
|
|||||||
{
|
{
|
||||||
protected static string $relationship = 'allocations';
|
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
|
public function setTitle(): string
|
||||||
{
|
{
|
||||||
@@ -57,7 +58,7 @@ class AllocationsRelationManager extends RelationManager
|
|||||||
->label(trans('admin/node.ports')),
|
->label(trans('admin/node.ports')),
|
||||||
TextColumn::make('server.name')
|
TextColumn::make('server.name')
|
||||||
->label(trans('admin/node.table.servers'))
|
->label(trans('admin/node.table.servers'))
|
||||||
->icon('tabler-brand-docker')
|
->icon(TablerIcon::BrandDocker)
|
||||||
->visibleFrom('md')
|
->visibleFrom('md')
|
||||||
->searchable()
|
->searchable()
|
||||||
->url(fn (Allocation $allocation): string => $allocation->server ? route('filament.admin.resources.servers.edit', ['record' => $allocation->server]) : ''),
|
->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()
|
DeleteBulkAction::make()
|
||||||
->authorize(fn () => user()?->can('update', $this->getOwnerRecord())),
|
->authorize(fn () => user()?->can('update', $this->getOwnerRecord())),
|
||||||
Action::make('create new allocation')
|
Action::make('create new allocation')
|
||||||
->label(trans('admin/node.create_allocation'))
|
->tooltip(trans('admin/node.create_allocation'))
|
||||||
->icon('tabler-world-plus')
|
->icon(TablerIcon::WorldPlus)
|
||||||
->iconButton()->iconSize(IconSize::ExtraLarge)
|
|
||||||
->schema(fn () => [
|
->schema(fn () => [
|
||||||
Select::make('allocation_ip')
|
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'))
|
->label(trans('admin/node.ip_address'))
|
||||||
->inlineLabel()
|
->inlineLabel()
|
||||||
->ip()
|
->ip()
|
||||||
@@ -99,14 +102,26 @@ class AllocationsRelationManager extends RelationManager
|
|||||||
->afterStateUpdated(fn (Set $set) => $set('allocation_ports', []))
|
->afterStateUpdated(fn (Set $set) => $set('allocation_ports', []))
|
||||||
->live()
|
->live()
|
||||||
->hintAction(
|
->hintAction(
|
||||||
Action::make('refresh')
|
Action::make('hint_refresh')
|
||||||
->iconButton()
|
->hiddenLabel()
|
||||||
->icon('tabler-refresh')
|
->icon(TablerIcon::Refresh)
|
||||||
->tooltip(trans('admin/node.refresh'))
|
->tooltip(trans('admin/node.refresh'))
|
||||||
->action(function () {
|
->action(function () {
|
||||||
cache()->forget("nodes.{$this->getOwnerRecord()->id}.ips");
|
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(),
|
->required(),
|
||||||
TextInput::make('allocation_alias')
|
TextInput::make('allocation_alias')
|
||||||
->label(trans('admin/node.table.alias'))
|
->label(trans('admin/node.table.alias'))
|
||||||
|
|||||||
@@ -3,7 +3,9 @@
|
|||||||
namespace App\Filament\Admin\Resources\Nodes\RelationManagers;
|
namespace App\Filament\Admin\Resources\Nodes\RelationManagers;
|
||||||
|
|
||||||
use App\Enums\ServerResourceType;
|
use App\Enums\ServerResourceType;
|
||||||
|
use App\Enums\TablerIcon;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
|
use BackedEnum;
|
||||||
use Filament\Resources\RelationManagers\RelationManager;
|
use Filament\Resources\RelationManagers\RelationManager;
|
||||||
use Filament\Tables\Columns\SelectColumn;
|
use Filament\Tables\Columns\SelectColumn;
|
||||||
use Filament\Tables\Columns\TextColumn;
|
use Filament\Tables\Columns\TextColumn;
|
||||||
@@ -13,7 +15,7 @@ class ServersRelationManager extends RelationManager
|
|||||||
{
|
{
|
||||||
protected static string $relationship = 'servers';
|
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
|
public function setTitle(): string
|
||||||
{
|
{
|
||||||
|
|||||||
43
app/Filament/Admin/Resources/Plugins/Pages/ListPlugins.php
Normal file
43
app/Filament/Admin/Resources/Plugins/Pages/ListPlugins.php
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Admin\Resources\Plugins\Pages;
|
||||||
|
|
||||||
|
use App\Enums\PluginCategory;
|
||||||
|
use App\Filament\Admin\Resources\Plugins\PluginResource;
|
||||||
|
use App\Models\Plugin;
|
||||||
|
use App\Services\Helpers\PluginService;
|
||||||
|
use Filament\Resources\Pages\ListRecords;
|
||||||
|
use Filament\Schemas\Components\Tabs\Tab;
|
||||||
|
|
||||||
|
class ListPlugins extends ListRecords
|
||||||
|
{
|
||||||
|
protected static string $resource = PluginResource::class;
|
||||||
|
|
||||||
|
public function reorderTable(array $order, int|string|null $draggedRecordKey = null): void
|
||||||
|
{
|
||||||
|
/** @var PluginService $pluginService */
|
||||||
|
$pluginService = app(PluginService::class); // @phpstan-ignore myCustomRules.forbiddenGlobalFunctions
|
||||||
|
|
||||||
|
$pluginService->updateLoadOrder($order);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTabs(): array
|
||||||
|
{
|
||||||
|
$tabs = [
|
||||||
|
'all' => Tab::make('all')
|
||||||
|
->label(trans('admin/plugin.all'))
|
||||||
|
->badge(Plugin::count()),
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach (PluginCategory::cases() as $category) {
|
||||||
|
$query = Plugin::whereCategory($category->value);
|
||||||
|
$tabs[$category->value] = Tab::make($category->value)
|
||||||
|
->label($category->getLabel())
|
||||||
|
->icon($category->getIcon())
|
||||||
|
->badge($query->count())
|
||||||
|
->modifyQueryUsing(fn () => $query);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $tabs;
|
||||||
|
}
|
||||||
|
}
|
||||||
336
app/Filament/Admin/Resources/Plugins/PluginResource.php
Normal file
336
app/Filament/Admin/Resources/Plugins/PluginResource.php
Normal file
@@ -0,0 +1,336 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
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;
|
||||||
|
use Filament\Forms\Components\FileUpload;
|
||||||
|
use Filament\Forms\Components\TextInput;
|
||||||
|
use Filament\Infolists\Components\TextEntry;
|
||||||
|
use Filament\Notifications\Notification;
|
||||||
|
use Filament\Resources\Resource;
|
||||||
|
use Filament\Tables\Columns\TextColumn;
|
||||||
|
use Filament\Tables\Table;
|
||||||
|
use Illuminate\Http\UploadedFile;
|
||||||
|
|
||||||
|
class PluginResource extends Resource
|
||||||
|
{
|
||||||
|
protected static ?string $model = Plugin::class;
|
||||||
|
|
||||||
|
protected static string|BackedEnum|null $navigationIcon = TablerIcon::Packages;
|
||||||
|
|
||||||
|
protected static ?string $recordTitleAttribute = 'name';
|
||||||
|
|
||||||
|
public static function getNavigationLabel(): string
|
||||||
|
{
|
||||||
|
return trans('admin/plugin.nav_title');
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getModelLabel(): string
|
||||||
|
{
|
||||||
|
return trans('admin/plugin.model_label');
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getPluralModelLabel(): string
|
||||||
|
{
|
||||||
|
return trans('admin/plugin.model_label_plural');
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getNavigationBadge(): ?string
|
||||||
|
{
|
||||||
|
return (string) static::getEloquentQuery()->count() ?: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function table(Table $table): Table
|
||||||
|
{
|
||||||
|
return $table
|
||||||
|
->openRecordUrlInNewTab()
|
||||||
|
->reorderable('load_order')
|
||||||
|
->authorizeReorder(fn () => user()?->can('update plugin'))
|
||||||
|
->reorderRecordsTriggerAction(fn (Action $action, bool $isReordering) => $action->hiddenLabel()->tooltip($isReordering ? trans('admin/plugin.apply_load_order') : trans('admin/plugin.change_load_order')))
|
||||||
|
->defaultSort('load_order')
|
||||||
|
->columns([
|
||||||
|
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() ? 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()
|
||||||
|
->searchable(),
|
||||||
|
TextColumn::make('author')
|
||||||
|
->label(trans('admin/plugin.author'))
|
||||||
|
->sortable(),
|
||||||
|
TextColumn::make('version')
|
||||||
|
->label(trans('admin/plugin.version'))
|
||||||
|
->sortable(),
|
||||||
|
TextColumn::make('category')
|
||||||
|
->label(trans('admin/plugin.category'))
|
||||||
|
->badge()
|
||||||
|
->sortable()
|
||||||
|
->visible(fn ($livewire) => $livewire->activeTab === 'all'),
|
||||||
|
TextColumn::make('status')
|
||||||
|
->label(trans('admin/plugin.status'))
|
||||||
|
->badge()
|
||||||
|
->tooltip(fn (Plugin $plugin) => $plugin->status_message)
|
||||||
|
->sortable(),
|
||||||
|
])
|
||||||
|
->recordActions([
|
||||||
|
Action::make('exclude_view')
|
||||||
|
->label(trans('filament-actions::view.single.label'))
|
||||||
|
->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('exclude_visit_website')
|
||||||
|
->label(trans('admin/plugin.visit_website'))
|
||||||
|
->visible(!is_null($plugin->url))
|
||||||
|
->url($plugin->url, true)
|
||||||
|
)
|
||||||
|
->modalCancelActionLabel(trans('filament::components/modal.actions.close.label'))
|
||||||
|
->schema(fn (Plugin $plugin) => $plugin->getReadme() ? [
|
||||||
|
TextEntry::make('readme')
|
||||||
|
->hiddenLabel()
|
||||||
|
->markdown()
|
||||||
|
->state(fn (Plugin $plugin) => $plugin->getReadme()),
|
||||||
|
] : null),
|
||||||
|
Action::make('exclude_settings')
|
||||||
|
->label(trans('admin/plugin.settings'))
|
||||||
|
->authorize(fn (Plugin $plugin) => user()?->can('update', $plugin))
|
||||||
|
->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('exclude_install')
|
||||||
|
->label(trans('admin/plugin.install'))
|
||||||
|
->authorize(fn (Plugin $plugin) => user()?->can('update', $plugin))
|
||||||
|
->icon(TablerIcon::Terminal)
|
||||||
|
->color('success')
|
||||||
|
->hidden(fn (Plugin $plugin) => $plugin->status !== PluginStatus::NotInstalled)
|
||||||
|
->action(function (Plugin $plugin) {
|
||||||
|
try {
|
||||||
|
InstallPlugin::dispatch(user(), $plugin);
|
||||||
|
|
||||||
|
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('exclude_update')
|
||||||
|
->label(trans('admin/plugin.update'))
|
||||||
|
->authorize(fn (Plugin $plugin) => user()?->can('update', $plugin))
|
||||||
|
->icon(TablerIcon::Download)
|
||||||
|
->color('success')
|
||||||
|
->visible(fn (Plugin $plugin) => $plugin->status !== PluginStatus::NotInstalled && $plugin->isUpdateAvailable())
|
||||||
|
->action(function (Plugin $plugin) {
|
||||||
|
try {
|
||||||
|
UpdatePlugin::dispatch(user(), $plugin);
|
||||||
|
|
||||||
|
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('exclude_enable')
|
||||||
|
->label(trans('admin/plugin.enable'))
|
||||||
|
->authorize(fn (Plugin $plugin) => user()?->can('update', $plugin))
|
||||||
|
->icon(TablerIcon::Check)
|
||||||
|
->color('success')
|
||||||
|
->visible(fn (Plugin $plugin) => $plugin->canEnable())
|
||||||
|
->requiresConfirmation(fn (Plugin $plugin, PluginService $pluginService) => $plugin->isTheme() && $pluginService->hasThemePluginEnabled())
|
||||||
|
->modalHeading(fn (Plugin $plugin, PluginService $pluginService) => $plugin->isTheme() && $pluginService->hasThemePluginEnabled() ? trans('admin/plugin.enable_theme_modal.heading') : null)
|
||||||
|
->modalDescription(fn (Plugin $plugin, PluginService $pluginService) => $plugin->isTheme() && $pluginService->hasThemePluginEnabled() ? trans('admin/plugin.enable_theme_modal.description') : null)
|
||||||
|
->action(function (Plugin $plugin, $livewire, PluginService $pluginService) {
|
||||||
|
$pluginService->enablePlugin($plugin);
|
||||||
|
|
||||||
|
redirect(ListPlugins::getUrl(['tab' => $livewire->activeTab]));
|
||||||
|
|
||||||
|
Notification::make()
|
||||||
|
->success()
|
||||||
|
->title(trans('admin/plugin.notifications.enabled'))
|
||||||
|
->send();
|
||||||
|
}),
|
||||||
|
Action::make('exclude_disable')
|
||||||
|
->label(trans('admin/plugin.disable'))
|
||||||
|
->authorize(fn (Plugin $plugin) => user()?->can('update', $plugin))
|
||||||
|
->icon(TablerIcon::X)
|
||||||
|
->color('warning')
|
||||||
|
->visible(fn (Plugin $plugin) => $plugin->canDisable())
|
||||||
|
->action(function (Plugin $plugin, $livewire, PluginService $pluginService) {
|
||||||
|
$pluginService->disablePlugin($plugin);
|
||||||
|
|
||||||
|
redirect(ListPlugins::getUrl(['tab' => $livewire->activeTab]));
|
||||||
|
|
||||||
|
Notification::make()
|
||||||
|
->success()
|
||||||
|
->title(trans('admin/plugin.notifications.disabled'))
|
||||||
|
->send();
|
||||||
|
}),
|
||||||
|
Action::make('exclude_delete')
|
||||||
|
->label(trans('filament-actions::delete.single.label'))
|
||||||
|
->authorize(fn (Plugin $plugin) => user()?->can('delete', $plugin))
|
||||||
|
->icon(TablerIcon::Trash)
|
||||||
|
->color('danger')
|
||||||
|
->requiresConfirmation()
|
||||||
|
->visible(fn (Plugin $plugin) => $plugin->status === PluginStatus::NotInstalled || $plugin->status === PluginStatus::Errored)
|
||||||
|
->action(function (Plugin $plugin, $livewire, PluginService $pluginService) {
|
||||||
|
$pluginService->deletePlugin($plugin);
|
||||||
|
|
||||||
|
redirect(ListPlugins::getUrl(['tab' => $livewire->activeTab]));
|
||||||
|
|
||||||
|
Notification::make()
|
||||||
|
->success()
|
||||||
|
->title(trans('admin/plugin.notifications.deleted'))
|
||||||
|
->send();
|
||||||
|
}),
|
||||||
|
Action::make('exclude_uninstall')
|
||||||
|
->label(trans('admin/plugin.uninstall'))
|
||||||
|
->authorize(fn (Plugin $plugin) => user()?->can('update', $plugin))
|
||||||
|
->icon(TablerIcon::Terminal)
|
||||||
|
->color('danger')
|
||||||
|
->requiresConfirmation()
|
||||||
|
->hidden(fn (Plugin $plugin) => $plugin->status === PluginStatus::NotInstalled || $plugin->status === PluginStatus::Errored)
|
||||||
|
->action(function (Plugin $plugin) {
|
||||||
|
try {
|
||||||
|
UninstallPlugin::dispatch(user(), $plugin);
|
||||||
|
|
||||||
|
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')
|
||||||
|
->hiddenLabel()
|
||||||
|
->tooltip(trans('admin/plugin.import_from_file'))
|
||||||
|
->authorize(fn () => user()?->can('create', Plugin::class))
|
||||||
|
->icon(TablerIcon::FileDownload)
|
||||||
|
->schema([
|
||||||
|
// TODO: switch to new file upload
|
||||||
|
FileUpload::make('file')
|
||||||
|
->required()
|
||||||
|
->acceptedFileTypes(['application/zip', 'application/zip-compressed', 'application/x-zip-compressed'])
|
||||||
|
->preserveFilenames()
|
||||||
|
->previewable(false)
|
||||||
|
->storeFiles(false),
|
||||||
|
])
|
||||||
|
->action(function ($data, $livewire, PluginService $pluginService) {
|
||||||
|
try {
|
||||||
|
/** @var UploadedFile $file */
|
||||||
|
$file = $data['file'];
|
||||||
|
|
||||||
|
$pluginName = str($file->getClientOriginalName())->before('.zip')->toString();
|
||||||
|
|
||||||
|
if (Plugin::where('id', $pluginName)->exists()) {
|
||||||
|
throw new Exception(trans('admin/plugin.notifications.import_exists'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$pluginService->downloadPluginFromFile($file);
|
||||||
|
|
||||||
|
Notification::make()
|
||||||
|
->success()
|
||||||
|
->title(trans('admin/plugin.notifications.imported'))
|
||||||
|
->send();
|
||||||
|
|
||||||
|
redirect(ListPlugins::getUrl(['tab' => $livewire->activeTab]));
|
||||||
|
} catch (Exception $exception) {
|
||||||
|
report($exception);
|
||||||
|
|
||||||
|
Notification::make()
|
||||||
|
->danger()
|
||||||
|
->title(trans('admin/plugin.notifications.import_failed'))
|
||||||
|
->body($exception->getMessage())
|
||||||
|
->send();
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
Action::make('import_from_url')
|
||||||
|
->hiddenLabel()
|
||||||
|
->tooltip(trans('admin/plugin.import_from_url'))
|
||||||
|
->authorize(fn () => user()?->can('create', Plugin::class))
|
||||||
|
->icon(TablerIcon::WorldDownload)
|
||||||
|
->schema([
|
||||||
|
TextInput::make('url')
|
||||||
|
->required()
|
||||||
|
->url()
|
||||||
|
->endsWith('.zip'),
|
||||||
|
])
|
||||||
|
->action(function ($data, $livewire, PluginService $pluginService) {
|
||||||
|
try {
|
||||||
|
$pluginName = str($data['url'])->before('.zip')->explode('/')->last();
|
||||||
|
|
||||||
|
if (Plugin::where('id', $pluginName)->exists()) {
|
||||||
|
throw new Exception(trans('admin/plugin.notifications.import_exists'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$pluginService->downloadPluginFromUrl($data['url']);
|
||||||
|
|
||||||
|
Notification::make()
|
||||||
|
->success()
|
||||||
|
->title(trans('admin/plugin.notifications.imported'))
|
||||||
|
->send();
|
||||||
|
|
||||||
|
redirect(ListPlugins::getUrl(['tab' => $livewire->activeTab]));
|
||||||
|
} catch (Exception $exception) {
|
||||||
|
report($exception);
|
||||||
|
|
||||||
|
Notification::make()
|
||||||
|
->danger()
|
||||||
|
->title(trans('admin/plugin.notifications.import_failed'))
|
||||||
|
->body($exception->getMessage())
|
||||||
|
->send();
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
->emptyStateIcon(TablerIcon::Packages)
|
||||||
|
->emptyStateDescription('')
|
||||||
|
->emptyStateHeading(trans('admin/plugin.no_plugins'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getPages(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'index' => ListPlugins::route('/'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Filament\Admin\Resources\Roles\Pages;
|
namespace App\Filament\Admin\Resources\Roles\Pages;
|
||||||
|
|
||||||
|
use App\Enums\TablerIcon;
|
||||||
use App\Filament\Admin\Resources\Roles\RoleResource;
|
use App\Filament\Admin\Resources\Roles\RoleResource;
|
||||||
use App\Models\Role;
|
use App\Models\Role;
|
||||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||||
@@ -9,7 +10,6 @@ use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
|||||||
use Filament\Actions\Action;
|
use Filament\Actions\Action;
|
||||||
use Filament\Actions\ActionGroup;
|
use Filament\Actions\ActionGroup;
|
||||||
use Filament\Resources\Pages\CreateRecord;
|
use Filament\Resources\Pages\CreateRecord;
|
||||||
use Filament\Support\Enums\IconSize;
|
|
||||||
use Illuminate\Support\Arr;
|
use Illuminate\Support\Arr;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
use Spatie\Permission\Models\Permission;
|
use Spatie\Permission\Models\Permission;
|
||||||
@@ -32,9 +32,12 @@ class CreateRole extends CreateRecord
|
|||||||
protected function getDefaultHeaderActions(): array
|
protected function getDefaultHeaderActions(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
$this->getCreateFormAction()->formId('form')
|
Action::make('create')
|
||||||
->iconButton()->iconSize(IconSize::ExtraLarge)
|
->hiddenLabel()
|
||||||
->icon('tabler-plus'),
|
->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;
|
namespace App\Filament\Admin\Resources\Roles\Pages;
|
||||||
|
|
||||||
|
use App\Enums\TablerIcon;
|
||||||
use App\Filament\Admin\Resources\Roles\RoleResource;
|
use App\Filament\Admin\Resources\Roles\RoleResource;
|
||||||
use App\Models\Role;
|
use App\Models\Role;
|
||||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||||
@@ -10,7 +11,6 @@ use Filament\Actions\Action;
|
|||||||
use Filament\Actions\ActionGroup;
|
use Filament\Actions\ActionGroup;
|
||||||
use Filament\Actions\DeleteAction;
|
use Filament\Actions\DeleteAction;
|
||||||
use Filament\Resources\Pages\EditRecord;
|
use Filament\Resources\Pages\EditRecord;
|
||||||
use Filament\Support\Enums\IconSize;
|
|
||||||
use Illuminate\Support\Arr;
|
use Illuminate\Support\Arr;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
use Spatie\Permission\Models\Permission;
|
use Spatie\Permission\Models\Permission;
|
||||||
@@ -58,12 +58,14 @@ class EditRole extends EditRecord
|
|||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
DeleteAction::make()
|
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')))
|
->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)
|
->disabled(fn (Role $role) => $role->isRootAdmin() || $role->users_count >= 1),
|
||||||
->iconButton()->iconSize(IconSize::ExtraLarge),
|
Action::make('save')
|
||||||
$this->getSaveFormAction()->formId('form')
|
->hiddenLabel()
|
||||||
->iconButton()->iconSize(IconSize::ExtraLarge)
|
->action('save')
|
||||||
->icon('tabler-device-floppy'),
|
->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\Filament\Admin\Resources\Roles\RoleResource;
|
||||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||||
use Filament\Actions\Action;
|
|
||||||
use Filament\Actions\ActionGroup;
|
|
||||||
use Filament\Actions\CreateAction;
|
|
||||||
use Filament\Resources\Pages\ListRecords;
|
use Filament\Resources\Pages\ListRecords;
|
||||||
use Filament\Support\Enums\IconSize;
|
|
||||||
|
|
||||||
class ListRoles extends ListRecords
|
class ListRoles extends ListRecords
|
||||||
{
|
{
|
||||||
@@ -17,14 +13,4 @@ class ListRoles extends ListRecords
|
|||||||
use CanCustomizeHeaderWidgets;
|
use CanCustomizeHeaderWidgets;
|
||||||
|
|
||||||
protected static string $resource = RoleResource::class;
|
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\ActionGroup;
|
||||||
use Filament\Actions\EditAction;
|
use Filament\Actions\EditAction;
|
||||||
use Filament\Resources\Pages\ViewRecord;
|
use Filament\Resources\Pages\ViewRecord;
|
||||||
use Filament\Support\Enums\IconSize;
|
|
||||||
|
|
||||||
class ViewRole extends ViewRecord
|
class ViewRole extends ViewRecord
|
||||||
{
|
{
|
||||||
@@ -22,8 +21,7 @@ class ViewRole extends ViewRecord
|
|||||||
protected function getDefaultHeaderActions(): array
|
protected function getDefaultHeaderActions(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
EditAction::make()
|
EditAction::make(),
|
||||||
->iconButton()->iconSize(IconSize::ExtraLarge),
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
namespace App\Filament\Admin\Resources\Roles;
|
namespace App\Filament\Admin\Resources\Roles;
|
||||||
|
|
||||||
use App\Enums\CustomizationKey;
|
use App\Enums\CustomizationKey;
|
||||||
|
use App\Enums\TablerIcon;
|
||||||
use App\Filament\Admin\Resources\Roles\Pages\CreateRole;
|
use App\Filament\Admin\Resources\Roles\Pages\CreateRole;
|
||||||
use App\Filament\Admin\Resources\Roles\Pages\EditRole;
|
use App\Filament\Admin\Resources\Roles\Pages\EditRole;
|
||||||
use App\Filament\Admin\Resources\Roles\Pages\ListRoles;
|
use App\Filament\Admin\Resources\Roles\Pages\ListRoles;
|
||||||
@@ -15,6 +16,8 @@ use App\Traits\Filament\CanModifyTable;
|
|||||||
use BackedEnum;
|
use BackedEnum;
|
||||||
use Exception;
|
use Exception;
|
||||||
use Filament\Actions\Action;
|
use Filament\Actions\Action;
|
||||||
|
use Filament\Actions\BulkActionGroup;
|
||||||
|
use Filament\Actions\CreateAction;
|
||||||
use Filament\Actions\DeleteBulkAction;
|
use Filament\Actions\DeleteBulkAction;
|
||||||
use Filament\Actions\EditAction;
|
use Filament\Actions\EditAction;
|
||||||
use Filament\Actions\ViewAction;
|
use Filament\Actions\ViewAction;
|
||||||
@@ -43,7 +46,7 @@ class RoleResource extends Resource
|
|||||||
|
|
||||||
protected static ?string $model = Role::class;
|
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';
|
protected static ?string $recordTitleAttribute = 'name';
|
||||||
|
|
||||||
@@ -100,10 +103,13 @@ class RoleResource extends Resource
|
|||||||
->hidden(fn ($record) => static::getEditAuthorizationResponse($record)->allowed()),
|
->hidden(fn ($record) => static::getEditAuthorizationResponse($record)->allowed()),
|
||||||
EditAction::make(),
|
EditAction::make(),
|
||||||
])
|
])
|
||||||
->checkIfRecordIsSelectableUsing(fn (Role $role) => !$role->isRootAdmin() && $role->users_count <= 0)
|
->toolbarActions([
|
||||||
->groupedBulkActions([
|
CreateAction::make(),
|
||||||
DeleteBulkAction::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
@@ -2,17 +2,16 @@
|
|||||||
|
|
||||||
namespace App\Filament\Admin\Resources\Servers\Pages;
|
namespace App\Filament\Admin\Resources\Servers\Pages;
|
||||||
|
|
||||||
|
use App\Enums\TablerIcon;
|
||||||
use App\Filament\Admin\Resources\Servers\ServerResource;
|
use App\Filament\Admin\Resources\Servers\ServerResource;
|
||||||
use App\Filament\Server\Pages\Console;
|
use App\Filament\Server\Pages\Console;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||||
use Filament\Actions\Action;
|
use Filament\Actions\Action;
|
||||||
use Filament\Actions\ActionGroup;
|
|
||||||
use Filament\Actions\CreateAction;
|
use Filament\Actions\CreateAction;
|
||||||
use Filament\Actions\EditAction;
|
use Filament\Actions\EditAction;
|
||||||
use Filament\Resources\Pages\ListRecords;
|
use Filament\Resources\Pages\ListRecords;
|
||||||
use Filament\Support\Enums\IconSize;
|
|
||||||
use Filament\Tables\Columns\SelectColumn;
|
use Filament\Tables\Columns\SelectColumn;
|
||||||
use Filament\Tables\Columns\TextColumn;
|
use Filament\Tables\Columns\TextColumn;
|
||||||
use Filament\Tables\Grouping\Group;
|
use Filament\Tables\Grouping\Group;
|
||||||
@@ -91,28 +90,19 @@ class ListServers extends ListRecords
|
|||||||
->sortable(),
|
->sortable(),
|
||||||
])
|
])
|
||||||
->recordActions([
|
->recordActions([
|
||||||
Action::make('View')
|
Action::make('view')
|
||||||
->label(trans('admin/server.view'))
|
->tooltip(trans('admin/server.view'))
|
||||||
->iconButton()
|
->icon(TablerIcon::Terminal)
|
||||||
->icon('tabler-terminal')
|
|
||||||
->iconSize(IconSize::Large)
|
|
||||||
->url(fn (Server $server) => Console::getUrl(panel: 'server', tenant: $server))
|
->url(fn (Server $server) => Console::getUrl(panel: 'server', tenant: $server))
|
||||||
->authorize(fn (Server $server) => user()?->canAccessTenant($server)),
|
->authorize(fn (Server $server) => user()?->canAccessTenant($server)),
|
||||||
EditAction::make(),
|
EditAction::make(),
|
||||||
])
|
])
|
||||||
->emptyStateIcon('tabler-brand-docker')
|
->toolbarActions([
|
||||||
|
CreateAction::make(),
|
||||||
|
])
|
||||||
->searchable()
|
->searchable()
|
||||||
|
->emptyStateIcon(TablerIcon::BrandDocker)
|
||||||
->emptyStateDescription('')
|
->emptyStateDescription('')
|
||||||
->emptyStateHeading(trans('admin/server.no_servers'));
|
->emptyStateHeading(trans('admin/server.no_servers'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @return array<Action|ActionGroup> */
|
|
||||||
protected function getDefaultHeaderActions(): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
CreateAction::make()
|
|
||||||
->iconButton()->iconSize(IconSize::ExtraLarge)
|
|
||||||
->icon('tabler-file-plus'),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Filament\Admin\Resources\Servers\RelationManagers;
|
namespace App\Filament\Admin\Resources\Servers\RelationManagers;
|
||||||
|
|
||||||
|
use App\Enums\TablerIcon;
|
||||||
use App\Filament\Admin\Resources\Servers\Pages\CreateServer;
|
use App\Filament\Admin\Resources\Servers\Pages\CreateServer;
|
||||||
use App\Models\Allocation;
|
use App\Models\Allocation;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
@@ -51,8 +52,8 @@ class AllocationsRelationManager extends RelationManager
|
|||||||
->placeholder(trans('admin/server.no_notes')),
|
->placeholder(trans('admin/server.no_notes')),
|
||||||
IconColumn::make('primary')
|
IconColumn::make('primary')
|
||||||
->icon(fn ($state) => match ($state) {
|
->icon(fn ($state) => match ($state) {
|
||||||
true => 'tabler-star-filled',
|
true => TablerIcon::StarFilled,
|
||||||
default => 'tabler-star',
|
default => TablerIcon::Star,
|
||||||
})
|
})
|
||||||
->color(fn ($state) => match ($state) {
|
->color(fn ($state) => match ($state) {
|
||||||
true => 'warning',
|
true => 'warning',
|
||||||
@@ -65,8 +66,8 @@ class AllocationsRelationManager extends RelationManager
|
|||||||
IconColumn::make('is_locked')
|
IconColumn::make('is_locked')
|
||||||
->label(trans('admin/server.locked'))
|
->label(trans('admin/server.locked'))
|
||||||
->tooltip(trans('admin/server.locked_helper'))
|
->tooltip(trans('admin/server.locked_helper'))
|
||||||
->trueIcon('tabler-lock')
|
->trueIcon(TablerIcon::Lock)
|
||||||
->falseIcon('tabler-lock-open'),
|
->falseIcon(TablerIcon::LockOpen),
|
||||||
])
|
])
|
||||||
->recordActions([
|
->recordActions([
|
||||||
Action::make('make-primary')
|
Action::make('make-primary')
|
||||||
@@ -106,26 +107,40 @@ class AllocationsRelationManager extends RelationManager
|
|||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
CreateAction::make()
|
CreateAction::make()
|
||||||
->label(trans('admin/server.create_allocation'))
|
->hiddenLabel()
|
||||||
->icon('tabler-network')
|
->tooltip(trans('admin/server.create_allocation'))
|
||||||
->iconButton()->iconSize(IconSize::ExtraLarge)
|
->icon(TablerIcon::Network)
|
||||||
->createAnother(false)
|
->createAnother(false)
|
||||||
->schema(fn () => [
|
->schema(fn () => [
|
||||||
Select::make('allocation_ip')
|
Select::make('allocation_ip')
|
||||||
->options(fn () => collect($this->getOwnerRecord()->node->ipAddresses())->mapWithKeys(fn (string $ip) => [$ip => $ip]))
|
->options(fn (Get $get) => collect($this->getOwnerRecord()->node->ipAddresses())
|
||||||
|
->when($get('allocation_ip'), fn ($ips, $current) => $ips->push($current))
|
||||||
|
->unique()
|
||||||
|
->mapWithKeys(fn (string $ip) => [$ip => $ip]))
|
||||||
->label(trans('admin/server.ip_address'))
|
->label(trans('admin/server.ip_address'))
|
||||||
->inlineLabel()
|
->inlineLabel()
|
||||||
->ip()
|
->ip()
|
||||||
->live()
|
->live()
|
||||||
->hintAction(
|
->hintAction(
|
||||||
Action::make('refresh')
|
Action::make('refresh')
|
||||||
->iconButton()
|
->icon(TablerIcon::Refresh)
|
||||||
->icon('tabler-refresh')
|
|
||||||
->tooltip(trans('admin/node.refresh'))
|
->tooltip(trans('admin/node.refresh'))
|
||||||
->action(function () {
|
->action(function () {
|
||||||
cache()->forget("nodes.{$this->getOwnerRecord()->node->id}.ips");
|
cache()->forget("nodes.{$this->getOwnerRecord()->node->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']))
|
||||||
|
)
|
||||||
->afterStateUpdated(fn (Set $set) => $set('allocation_ports', []))
|
->afterStateUpdated(fn (Set $set) => $set('allocation_ports', []))
|
||||||
->required(),
|
->required(),
|
||||||
TextInput::make('allocation_alias')
|
TextInput::make('allocation_alias')
|
||||||
@@ -147,14 +162,14 @@ class AllocationsRelationManager extends RelationManager
|
|||||||
])
|
])
|
||||||
->action(fn (array $data, AssignmentService $service) => $service->handle($this->getOwnerRecord()->node, $data, $this->getOwnerRecord())),
|
->action(fn (array $data, AssignmentService $service) => $service->handle($this->getOwnerRecord()->node, $data, $this->getOwnerRecord())),
|
||||||
AssociateAction::make()
|
AssociateAction::make()
|
||||||
->icon('tabler-file-plus')
|
->icon(TablerIcon::FilePlus)
|
||||||
->iconButton()->iconSize(IconSize::ExtraLarge)
|
->iconButton()->iconSize(IconSize::ExtraLarge)
|
||||||
->multiple()
|
->multiple()
|
||||||
->associateAnother(false)
|
->associateAnother(false)
|
||||||
->preloadRecordSelect()
|
->preloadRecordSelect()
|
||||||
->recordSelectOptionsQuery(fn ($query) => $query->whereBelongsTo($this->getOwnerRecord()->node)->whereNull('server_id'))
|
->recordSelectOptionsQuery(fn ($query) => $query->whereBelongsTo($this->getOwnerRecord()->node)->whereNull('server_id'))
|
||||||
->recordSelectSearchColumns(['ip', 'port'])
|
->recordSelectSearchColumns(['ip', 'port'])
|
||||||
->label(trans('admin/server.add_allocation'))
|
->tooltip(trans('admin/server.add_allocation'))
|
||||||
->after(function (array $data) {
|
->after(function (array $data) {
|
||||||
Allocation::whereIn('id', array_values(array_unique($data['recordId'])))->update(['is_locked' => true]);
|
Allocation::whereIn('id', array_values(array_unique($data['recordId'])))->update(['is_locked' => true]);
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Filament\Admin\Resources\Servers\RelationManagers;
|
namespace App\Filament\Admin\Resources\Servers\RelationManagers;
|
||||||
|
|
||||||
|
use App\Enums\TablerIcon;
|
||||||
use App\Filament\Components\Actions\RotateDatabasePasswordAction;
|
use App\Filament\Components\Actions\RotateDatabasePasswordAction;
|
||||||
use App\Filament\Components\Tables\Columns\DateTimeColumn;
|
use App\Filament\Components\Tables\Columns\DateTimeColumn;
|
||||||
use App\Models\Database;
|
use App\Models\Database;
|
||||||
@@ -18,7 +19,6 @@ use Filament\Forms\Components\TextInput;
|
|||||||
use Filament\Notifications\Notification;
|
use Filament\Notifications\Notification;
|
||||||
use Filament\Resources\RelationManagers\RelationManager;
|
use Filament\Resources\RelationManagers\RelationManager;
|
||||||
use Filament\Schemas\Schema;
|
use Filament\Schemas\Schema;
|
||||||
use Filament\Support\Enums\IconSize;
|
|
||||||
use Filament\Support\Exceptions\Halt;
|
use Filament\Support\Exceptions\Halt;
|
||||||
use Filament\Tables\Columns\TextColumn;
|
use Filament\Tables\Columns\TextColumn;
|
||||||
use Filament\Tables\Table;
|
use Filament\Tables\Table;
|
||||||
@@ -49,7 +49,7 @@ class DatabasesRelationManager extends RelationManager
|
|||||||
->formatStateUsing(fn (Database $record) => $record->remote === '%' ? trans('admin/databasehost.anywhere'). ' ( % )' : $record->remote),
|
->formatStateUsing(fn (Database $record) => $record->remote === '%' ? trans('admin/databasehost.anywhere'). ' ( % )' : $record->remote),
|
||||||
TextInput::make('max_connections')
|
TextInput::make('max_connections')
|
||||||
->label(trans('admin/databasehost.table.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')
|
TextInput::make('jdbc')
|
||||||
->label(trans('admin/databasehost.table.connection_string'))
|
->label(trans('admin/databasehost.table.connection_string'))
|
||||||
->columnSpanFull()
|
->columnSpanFull()
|
||||||
@@ -75,7 +75,7 @@ class DatabasesRelationManager extends RelationManager
|
|||||||
->url(fn (Database $database) => route('filament.admin.resources.servers.edit', ['record' => $database->server_id])),
|
->url(fn (Database $database) => route('filament.admin.resources.servers.edit', ['record' => $database->server_id])),
|
||||||
TextColumn::make('max_connections')
|
TextColumn::make('max_connections')
|
||||||
->label(trans('admin/databasehost.table.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('admin/databasehost.unlimited')),
|
||||||
DateTimeColumn::make('created_at')
|
DateTimeColumn::make('created_at')
|
||||||
->label(trans('admin/databasehost.table.created_at')),
|
->label(trans('admin/databasehost.table.created_at')),
|
||||||
])
|
])
|
||||||
@@ -83,7 +83,6 @@ class DatabasesRelationManager extends RelationManager
|
|||||||
ViewAction::make()
|
ViewAction::make()
|
||||||
->color('primary'),
|
->color('primary'),
|
||||||
DeleteAction::make()
|
DeleteAction::make()
|
||||||
->iconButton()->iconSize(IconSize::ExtraLarge)
|
|
||||||
->successNotificationTitle(null)
|
->successNotificationTitle(null)
|
||||||
->using(function (Database $database, DatabaseManagementService $service) {
|
->using(function (Database $database, DatabaseManagementService $service) {
|
||||||
try {
|
try {
|
||||||
@@ -105,11 +104,11 @@ class DatabasesRelationManager extends RelationManager
|
|||||||
])
|
])
|
||||||
->toolbarActions([
|
->toolbarActions([
|
||||||
CreateAction::make()
|
CreateAction::make()
|
||||||
|
->hiddenLabel()
|
||||||
->disabled(fn () => DatabaseHost::count() < 1)
|
->disabled(fn () => DatabaseHost::count() < 1)
|
||||||
->label(fn () => DatabaseHost::count() < 1 ? trans('admin/server.no_db_hosts') : trans('admin/server.create_database'))
|
->tooltip(fn () => DatabaseHost::count() < 1 ? trans('admin/server.no_db_hosts') : trans('admin/server.create_database'))
|
||||||
->color(fn () => DatabaseHost::count() < 1 ? 'danger' : 'primary')
|
->color(fn () => DatabaseHost::count() < 1 ? 'danger' : 'primary')
|
||||||
->icon(fn () => DatabaseHost::count() < 1 ? 'tabler-database-x' : 'tabler-database-plus')
|
->icon(fn () => DatabaseHost::count() < 1 ? TablerIcon::DatabaseX : TablerIcon::DatabasePlus)
|
||||||
->iconButton()->iconSize(IconSize::ExtraLarge)
|
|
||||||
->createAnother(false)
|
->createAnother(false)
|
||||||
->action(function (array $data, DatabaseManagementService $service, RandomWordService $randomWordService) {
|
->action(function (array $data, DatabaseManagementService $service, RandomWordService $randomWordService) {
|
||||||
$data['database'] ??= $randomWordService->word() . random_int(1, 420);
|
$data['database'] ??= $randomWordService->word() . random_int(1, 420);
|
||||||
@@ -143,13 +142,13 @@ class DatabasesRelationManager extends RelationManager
|
|||||||
->label(trans('admin/server.name'))
|
->label(trans('admin/server.name'))
|
||||||
->alphaDash()
|
->alphaDash()
|
||||||
->prefix(fn () => 's' . $this->getOwnerRecord()->id . '_')
|
->prefix(fn () => 's' . $this->getOwnerRecord()->id . '_')
|
||||||
->hintIcon('tabler-question-mark', trans('admin/databasehost.table.name_helper')),
|
->hintIcon(TablerIcon::QuestionMark, trans('admin/databasehost.table.name_helper')),
|
||||||
TextInput::make('remote')
|
TextInput::make('remote')
|
||||||
->columnSpan(1)
|
->columnSpan(1)
|
||||||
->regex('/^[\w\-\/.%:]+$/')
|
->regex('/^[\w\-\/.%:]+$/')
|
||||||
->label(trans('admin/databasehost.table.remote'))
|
->label(trans('admin/databasehost.table.remote'))
|
||||||
->default('%')
|
->default('%')
|
||||||
->hintIcon('tabler-question-mark', trans('admin/databasehost.table.remote_helper')),
|
->hintIcon(TablerIcon::QuestionMark, trans('admin/databasehost.table.remote_helper')),
|
||||||
]),
|
]),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user